@cntyclub/agent-react 0.11.0 → 0.13.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"}
@@ -5,6 +5,17 @@ export interface AgentWidgetProps {
5
5
  /** Controlled open state (optional). */
6
6
  open?: boolean;
7
7
  onOpenChange?: (open: boolean) => void;
8
+ /**
9
+ * How the panel is presented:
10
+ * - `"floating"` (default): a launcher button + a popup/fullscreen panel,
11
+ * positioned by the widget itself (fixed to a screen corner).
12
+ * - `"docked"`: no launcher; the panel fills its parent element so the host
13
+ * can place it in the layout (e.g. a resizable side pane on desktop, or a
14
+ * fullscreen overlay on smaller screens). Open state is controlled by the
15
+ * host via `open`/`onOpenChange`. Page-follow navigation stays enabled so
16
+ * the agent can drive the main view alongside the chat.
17
+ */
18
+ presentation?: "floating" | "docked";
8
19
  }
9
20
  /**
10
21
  * The embeddable Agent Mode widget.
@@ -19,5 +30,5 @@ export interface AgentWidgetProps {
19
30
  * corner; size morph between popup and fullscreen). All colors come from the
20
31
  * host app's UI-kit theme tokens.
21
32
  */
22
- export declare function AgentWidget({ config, onOpenChange, open: controlledOpen }: AgentWidgetProps): React.JSX.Element;
33
+ export declare function AgentWidget({ config, onOpenChange, open: controlledOpen, presentation, }: AgentWidgetProps): React.JSX.Element;
23
34
  //# sourceMappingURL=agent-widget.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-widget.d.ts","sourceRoot":"","sources":["../../src/components/agent-widget.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,UAAU,CAAC;AAK1D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,wCAAwC;IACxC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,gBAAgB,qBAqR3F"}
1
+ {"version":3,"file":"agent-widget.d.ts","sourceRoot":"","sources":["../../src/components/agent-widget.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,UAAU,CAAC;AAK1D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,wCAAwC;IACxC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;CACtC;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,IAAI,EAAE,cAAc,EACpB,YAAyB,GAC1B,EAAE,gBAAgB,qBA2TlB"}
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,
@@ -2039,10 +2120,16 @@ function isUnreadable(message) {
2039
2120
  if (message.id.startsWith("streaming-")) return false;
2040
2121
  return message.role === "assistant" || message.role === "tool";
2041
2122
  }
2042
- function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2123
+ function AgentWidget({
2124
+ config,
2125
+ onOpenChange,
2126
+ open: controlledOpen,
2127
+ presentation = "floating"
2128
+ }) {
2043
2129
  const behavior = config.behavior ?? {};
2044
2130
  const position = config.appearance?.position ?? "bottom-right";
2045
2131
  const isControlled = controlledOpen !== void 0;
2132
+ const isDocked = presentation === "docked";
2046
2133
  const storageKey2 = `cc-agent-open:${config.clientId}`;
2047
2134
  const [uncontrolledOpen, setUncontrolledOpen] = React11.useState(false);
2048
2135
  const open = controlledOpen ?? uncontrolledOpen;
@@ -2073,7 +2160,8 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2073
2160
  const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
2074
2161
  const bootRef = React11.useRef(false);
2075
2162
  React11.useEffect(() => {
2076
- if (isControlled || bootRef.current) return;
2163
+ if (bootRef.current) return;
2164
+ if (isControlled && !isDocked) return;
2077
2165
  bootRef.current = true;
2078
2166
  let cancelled = false;
2079
2167
  void (async () => {
@@ -2083,17 +2171,19 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2083
2171
  if (list.length) await chat.openConversation(list[0].id);
2084
2172
  if (cancelled) return;
2085
2173
  }
2086
- let shouldOpen = behavior.initialOpen ?? false;
2087
- if (behavior.persistOpenState) {
2088
- try {
2089
- const stored = window.localStorage.getItem(storageKey2);
2090
- if (stored !== null) shouldOpen = stored === "1";
2091
- } catch {
2174
+ if (!isControlled) {
2175
+ let shouldOpen = behavior.initialOpen ?? false;
2176
+ if (behavior.persistOpenState) {
2177
+ try {
2178
+ const stored = window.localStorage.getItem(storageKey2);
2179
+ if (stored !== null) shouldOpen = stored === "1";
2180
+ } catch {
2181
+ }
2182
+ }
2183
+ if (shouldOpen) {
2184
+ setUncontrolledOpen(true);
2185
+ onOpenChange?.(true);
2092
2186
  }
2093
- }
2094
- if (shouldOpen) {
2095
- setUncontrolledOpen(true);
2096
- onOpenChange?.(true);
2097
2187
  }
2098
2188
  setBootstrapDone(true);
2099
2189
  })();
@@ -2163,7 +2253,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2163
2253
  setAgentMode(false);
2164
2254
  }, [setOpen]);
2165
2255
  const transformOrigin = position === "bottom-left" ? "bottom left" : "bottom right";
2166
- const launcherHidden = open || config.appearance?.hideLauncher;
2256
+ const launcherHidden = open || config.appearance?.hideLauncher || isDocked;
2167
2257
  return /* @__PURE__ */ jsxs(TooltipProvider, { children: [
2168
2258
  /* @__PURE__ */ jsx(AnimatePresence, { children: !launcherHidden ? /* @__PURE__ */ jsxs(
2169
2259
  motion.div,
@@ -2208,7 +2298,32 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2208
2298
  },
2209
2299
  "agent-launcher"
2210
2300
  ) : null }),
2211
- /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
2301
+ isDocked ? /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
2302
+ motion.div,
2303
+ {
2304
+ initial: { opacity: 0, x: 28 },
2305
+ animate: { opacity: 1, x: 0 },
2306
+ exit: { opacity: 0, x: 28 },
2307
+ transition: { type: "spring", stiffness: 260, damping: 30 },
2308
+ className: "flex h-full w-full flex-col overflow-hidden bg-background",
2309
+ children: /* @__PURE__ */ jsx(
2310
+ AgentPanel,
2311
+ {
2312
+ agentMode: false,
2313
+ canToggleAgentMode: false,
2314
+ chat,
2315
+ className: "flex-1",
2316
+ config,
2317
+ newMessageAnchorId: anchorId,
2318
+ onAlwaysApprove: alwaysApprove.setAlwaysApprove,
2319
+ onClose: closePanel,
2320
+ onToggleAgentMode: () => {
2321
+ }
2322
+ }
2323
+ )
2324
+ },
2325
+ "agent-docked"
2326
+ ) : null }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
2212
2327
  motion.div,
2213
2328
  {
2214
2329
  layout: true,
@@ -2248,7 +2363,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
2248
2363
  )
2249
2364
  },
2250
2365
  "agent-panel"
2251
- ) : null })
2366
+ ) : null }) })
2252
2367
  ] });
2253
2368
  }
2254
2369
  function StatusChip({