@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;
|
|
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;
|
|
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("
|
|
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({
|
|
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 (
|
|
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
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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({
|