@cntyclub/agent-react 0.11.0 → 0.12.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.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-voice-recorder.d.ts","sourceRoot":"","sources":["../../src/components/agent-voice-recorder.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAkBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,uBAAuB,qBAkLhH"}
1
+ {"version":3,"file":"agent-voice-recorder.d.ts","sourceRoot":"","sources":["../../src/components/agent-voice-recorder.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAqBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,uBAAuB,qBAwRhH"}
package/dist/index.js CHANGED
@@ -312,6 +312,7 @@ function AgentImageLightbox({ src, alt, onClose }) {
312
312
  document.body
313
313
  );
314
314
  }
315
+ var BAR_COUNT = 40;
315
316
  function formatClock(totalSeconds) {
316
317
  const minutes = Math.floor(totalSeconds / 60);
317
318
  const seconds = totalSeconds % 60;
@@ -335,8 +336,24 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
335
336
  const audioUrlRef = React11.useRef(null);
336
337
  const audioRef = React11.useRef(null);
337
338
  const timerRef = React11.useRef(null);
339
+ const canvasRef = React11.useRef(null);
340
+ const audioContextRef = React11.useRef(null);
341
+ const analyserRef = React11.useRef(null);
342
+ const rafRef = React11.useRef(null);
343
+ const levelsRef = React11.useRef(new Array(BAR_COUNT).fill(0));
338
344
  const onErrorRef = React11.useRef(onError);
339
345
  onErrorRef.current = onError;
346
+ const stopMeter = React11.useCallback(() => {
347
+ if (rafRef.current != null) {
348
+ cancelAnimationFrame(rafRef.current);
349
+ rafRef.current = null;
350
+ }
351
+ analyserRef.current = null;
352
+ const ctx = audioContextRef.current;
353
+ audioContextRef.current = null;
354
+ if (ctx && ctx.state !== "closed") void ctx.close().catch(() => {
355
+ });
356
+ }, []);
340
357
  const stopTracks = React11.useCallback(() => {
341
358
  streamRef.current?.getTracks().forEach((track) => track.stop());
342
359
  streamRef.current = null;
@@ -344,6 +361,73 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
344
361
  clearInterval(timerRef.current);
345
362
  timerRef.current = null;
346
363
  }
364
+ stopMeter();
365
+ }, [stopMeter]);
366
+ const startMeter = React11.useCallback((stream) => {
367
+ const Ctor = typeof window === "undefined" ? void 0 : window.AudioContext ?? window.webkitAudioContext;
368
+ if (!Ctor) return;
369
+ let context;
370
+ try {
371
+ context = new Ctor();
372
+ } catch {
373
+ return;
374
+ }
375
+ audioContextRef.current = context;
376
+ const source = context.createMediaStreamSource(stream);
377
+ const analyser = context.createAnalyser();
378
+ analyser.fftSize = 1024;
379
+ analyser.smoothingTimeConstant = 0.75;
380
+ source.connect(analyser);
381
+ analyserRef.current = analyser;
382
+ levelsRef.current = new Array(BAR_COUNT).fill(0);
383
+ const buffer = new Uint8Array(analyser.fftSize);
384
+ const draw = () => {
385
+ const canvas = canvasRef.current;
386
+ const activeAnalyser = analyserRef.current;
387
+ if (!canvas || !activeAnalyser) return;
388
+ const ctx2d = canvas.getContext("2d");
389
+ if (!ctx2d) return;
390
+ activeAnalyser.getByteTimeDomainData(buffer);
391
+ let sumSquares = 0;
392
+ for (let i = 0; i < buffer.length; i += 1) {
393
+ const centered = (buffer[i] - 128) / 128;
394
+ sumSquares += centered * centered;
395
+ }
396
+ const rms = Math.sqrt(sumSquares / buffer.length);
397
+ const level = Math.min(1, rms * 3.2);
398
+ const levels = levelsRef.current;
399
+ levels.push(level);
400
+ levels.shift();
401
+ const dpr = Math.min(2, typeof window === "undefined" ? 1 : window.devicePixelRatio || 1);
402
+ const cssWidth = canvas.clientWidth || 1;
403
+ const cssHeight = canvas.clientHeight || 1;
404
+ const pxWidth = Math.round(cssWidth * dpr);
405
+ const pxHeight = Math.round(cssHeight * dpr);
406
+ if (canvas.width !== pxWidth) canvas.width = pxWidth;
407
+ if (canvas.height !== pxHeight) canvas.height = pxHeight;
408
+ ctx2d.clearRect(0, 0, pxWidth, pxHeight);
409
+ ctx2d.fillStyle = getComputedStyle(canvas).color || "currentColor";
410
+ const gap = Math.max(1, pxWidth / BAR_COUNT / 3);
411
+ const barWidth = Math.max(1, (pxWidth - gap * (BAR_COUNT - 1)) / BAR_COUNT);
412
+ const midY = pxHeight / 2;
413
+ const minBar = Math.max(1, dpr);
414
+ const radius = barWidth / 2;
415
+ for (let i = 0; i < BAR_COUNT; i += 1) {
416
+ const value = levels[i];
417
+ const barHeight = Math.max(minBar, value * pxHeight);
418
+ const x = i * (barWidth + gap);
419
+ const y = midY - barHeight / 2;
420
+ if (typeof ctx2d.roundRect === "function") {
421
+ ctx2d.beginPath();
422
+ ctx2d.roundRect(x, y, barWidth, barHeight, Math.min(radius, barHeight / 2));
423
+ ctx2d.fill();
424
+ } else {
425
+ ctx2d.fillRect(x, y, barWidth, barHeight);
426
+ }
427
+ }
428
+ rafRef.current = requestAnimationFrame(draw);
429
+ };
430
+ rafRef.current = requestAnimationFrame(draw);
347
431
  }, []);
348
432
  React11.useEffect(() => {
349
433
  let cancelled = false;
@@ -360,6 +444,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
360
444
  return;
361
445
  }
362
446
  streamRef.current = stream;
447
+ startMeter(stream);
363
448
  const mimeType = pickMimeType();
364
449
  const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
365
450
  recorderRef.current = recorder;
@@ -444,7 +529,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
444
529
  transition: { duration: 0.15 },
445
530
  children: [
446
531
  phase === "recording" ? /* @__PURE__ */ jsxs(Fragment, { children: [
447
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm", children: [
532
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 text-sm", children: [
448
533
  /* @__PURE__ */ jsx(
449
534
  motion.span,
450
535
  {
@@ -453,13 +538,9 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
453
538
  transition: { duration: 1.2, repeat: Infinity }
454
539
  }
455
540
  ),
456
- /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatClock(elapsed) }),
457
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
458
- "/ ",
459
- formatClock(maxSeconds)
460
- ] })
541
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatClock(elapsed) })
461
542
  ] }),
462
- /* @__PURE__ */ jsx("span", { className: "flex-1" }),
543
+ /* @__PURE__ */ jsx("canvas", { "aria-hidden": true, className: "h-6 min-w-0 flex-1 text-foreground/80", ref: canvasRef }),
463
544
  /* @__PURE__ */ jsx(Button, { "aria-label": "Discard recording", onClick: onCancel, size: "icon-sm", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
464
545
  /* @__PURE__ */ jsx(Button, { "aria-label": "Stop recording", onClick: stopRecording, size: "icon-sm", type: "button", children: /* @__PURE__ */ jsx(SquareIcon, {}) })
465
546
  ] }) : null,