@meridial/react 0.1.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.
@@ -0,0 +1,1933 @@
1
+ import {
2
+ Button,
3
+ DragHandle,
4
+ EMPTY_ARRAY,
5
+ EMPTY_OBJECT,
6
+ STORAGE_KEYS,
7
+ SafeReact,
8
+ Separator,
9
+ __export,
10
+ apiWorkflowsResponseSchema,
11
+ cn,
12
+ cva,
13
+ error,
14
+ formatErrorMessage,
15
+ useButton,
16
+ useCompositeRootContext,
17
+ useElementTracker,
18
+ useIsoLayoutEffect,
19
+ useMergedRefs,
20
+ useRenderElement,
21
+ useStableCallback
22
+ } from "./chunk-QCWLFL7O.js";
23
+
24
+ // src/voicebox.tsx
25
+ import {
26
+ useSession,
27
+ useAgent,
28
+ useSessionContext as useSessionContext3,
29
+ useEnsureRoom as useEnsureRoom3
30
+ } from "@livekit/components-react";
31
+ import { AnimatePresence as AnimatePresence3, motion as motion3 } from "motion/react";
32
+ import { TokenSource, RpcError as RpcError2 } from "livekit-client";
33
+ import { useDraggable } from "@neodrag/react";
34
+ import {
35
+ useRef as useRef6,
36
+ useState as useState9,
37
+ useMemo as useMemo5,
38
+ useEffect as useEffect5,
39
+ useCallback as useCallback5
40
+ } from "react";
41
+ import { ChatFeedback01Icon, Loading03Icon as Loading03Icon3 } from "@hugeicons/core-free-icons";
42
+ import { HugeiconsIcon as HugeiconsIcon7 } from "@hugeicons/react";
43
+
44
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/toggle/Toggle.js
45
+ import * as React7 from "react";
46
+
47
+ // ../../node_modules/.pnpm/@base-ui+utils@0.2.5_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/useControlled.js
48
+ import * as React from "react";
49
+ function useControlled({
50
+ controlled,
51
+ default: defaultProp,
52
+ name,
53
+ state = "value"
54
+ }) {
55
+ const {
56
+ current: isControlled
57
+ } = React.useRef(controlled !== void 0);
58
+ const [valueState, setValue] = React.useState(defaultProp);
59
+ const value = isControlled ? controlled : valueState;
60
+ if (process.env.NODE_ENV !== "production") {
61
+ React.useEffect(() => {
62
+ if (isControlled !== (controlled !== void 0)) {
63
+ console.error([`Base UI: A component is changing the ${isControlled ? "" : "un"}controlled ${state} state of ${name} to be ${isControlled ? "un" : ""}controlled.`, "Elements should not switch from uncontrolled to controlled (or vice versa).", `Decide between using a controlled or uncontrolled ${name} element for the lifetime of the component.`, "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", "More info: https://fb.me/react-controlled-components"].join("\n"));
64
+ }
65
+ }, [state, name, controlled]);
66
+ const {
67
+ current: defaultValue
68
+ } = React.useRef(defaultProp);
69
+ React.useEffect(() => {
70
+ if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
71
+ console.error([`Base UI: A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. To suppress this warning opt to use a controlled ${name}.`].join("\n"));
72
+ }
73
+ }, [JSON.stringify(defaultProp)]);
74
+ }
75
+ const setValueIfUncontrolled = React.useCallback((newValue) => {
76
+ if (!isControlled) {
77
+ setValue(newValue);
78
+ }
79
+ }, []);
80
+ return [value, setValueIfUncontrolled];
81
+ }
82
+
83
+ // ../../node_modules/.pnpm/@base-ui+utils@0.2.5_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/utils/esm/useId.js
84
+ import * as React2 from "react";
85
+ var globalId = 0;
86
+ function useGlobalId(idOverride, prefix = "mui") {
87
+ const [defaultId, setDefaultId] = React2.useState(idOverride);
88
+ const id = idOverride || defaultId;
89
+ React2.useEffect(() => {
90
+ if (defaultId == null) {
91
+ globalId += 1;
92
+ setDefaultId(`${prefix}-${globalId}`);
93
+ }
94
+ }, [defaultId, prefix]);
95
+ return id;
96
+ }
97
+ var maybeReactUseId = SafeReact.useId;
98
+ function useId(idOverride, prefix) {
99
+ if (maybeReactUseId !== void 0) {
100
+ const reactId = maybeReactUseId();
101
+ return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
102
+ }
103
+ return useGlobalId(idOverride, prefix);
104
+ }
105
+
106
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/useBaseUiId.js
107
+ function useBaseUiId(idOverride) {
108
+ return useId(idOverride, "base-ui");
109
+ }
110
+
111
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/toggle-group/ToggleGroupContext.js
112
+ import * as React3 from "react";
113
+ var ToggleGroupContext = /* @__PURE__ */ React3.createContext(void 0);
114
+ if (process.env.NODE_ENV !== "production") ToggleGroupContext.displayName = "ToggleGroupContext";
115
+ function useToggleGroupContext(optional = true) {
116
+ const context = React3.useContext(ToggleGroupContext);
117
+ if (context === void 0 && !optional) {
118
+ throw new Error(process.env.NODE_ENV !== "production" ? "Base UI: ToggleGroupContext is missing. ToggleGroup parts must be placed within <ToggleGroup>." : formatErrorMessage(7));
119
+ }
120
+ return context;
121
+ }
122
+
123
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/item/useCompositeItem.js
124
+ import * as React6 from "react";
125
+
126
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/list/useCompositeListItem.js
127
+ import * as React5 from "react";
128
+
129
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/list/CompositeListContext.js
130
+ import * as React4 from "react";
131
+ var CompositeListContext = /* @__PURE__ */ React4.createContext({
132
+ register: () => {
133
+ },
134
+ unregister: () => {
135
+ },
136
+ subscribeMapChange: () => {
137
+ return () => {
138
+ };
139
+ },
140
+ elementsRef: {
141
+ current: []
142
+ },
143
+ nextIndexRef: {
144
+ current: 0
145
+ }
146
+ });
147
+ if (process.env.NODE_ENV !== "production") CompositeListContext.displayName = "CompositeListContext";
148
+ function useCompositeListContext() {
149
+ return React4.useContext(CompositeListContext);
150
+ }
151
+
152
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/list/useCompositeListItem.js
153
+ var IndexGuessBehavior = /* @__PURE__ */ (function(IndexGuessBehavior2) {
154
+ IndexGuessBehavior2[IndexGuessBehavior2["None"] = 0] = "None";
155
+ IndexGuessBehavior2[IndexGuessBehavior2["GuessFromOrder"] = 1] = "GuessFromOrder";
156
+ return IndexGuessBehavior2;
157
+ })({});
158
+ function useCompositeListItem(params = {}) {
159
+ const {
160
+ label,
161
+ metadata,
162
+ textRef,
163
+ indexGuessBehavior,
164
+ index: externalIndex
165
+ } = params;
166
+ const {
167
+ register,
168
+ unregister,
169
+ subscribeMapChange,
170
+ elementsRef,
171
+ labelsRef,
172
+ nextIndexRef
173
+ } = useCompositeListContext();
174
+ const indexRef = React5.useRef(-1);
175
+ const [index, setIndex] = React5.useState(externalIndex ?? (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder ? () => {
176
+ if (indexRef.current === -1) {
177
+ const newIndex = nextIndexRef.current;
178
+ nextIndexRef.current += 1;
179
+ indexRef.current = newIndex;
180
+ }
181
+ return indexRef.current;
182
+ } : -1));
183
+ const componentRef = React5.useRef(null);
184
+ const ref = React5.useCallback((node) => {
185
+ componentRef.current = node;
186
+ if (index !== -1 && node !== null) {
187
+ elementsRef.current[index] = node;
188
+ if (labelsRef) {
189
+ const isLabelDefined = label !== void 0;
190
+ labelsRef.current[index] = isLabelDefined ? label : textRef?.current?.textContent ?? node.textContent;
191
+ }
192
+ }
193
+ }, [index, elementsRef, labelsRef, label, textRef]);
194
+ useIsoLayoutEffect(() => {
195
+ if (externalIndex != null) {
196
+ return void 0;
197
+ }
198
+ const node = componentRef.current;
199
+ if (node) {
200
+ register(node, metadata);
201
+ return () => {
202
+ unregister(node);
203
+ };
204
+ }
205
+ return void 0;
206
+ }, [externalIndex, register, unregister, metadata]);
207
+ useIsoLayoutEffect(() => {
208
+ if (externalIndex != null) {
209
+ return void 0;
210
+ }
211
+ return subscribeMapChange((map) => {
212
+ const i = componentRef.current ? map.get(componentRef.current)?.index : null;
213
+ if (i != null) {
214
+ setIndex(i);
215
+ }
216
+ });
217
+ }, [externalIndex, subscribeMapChange, setIndex]);
218
+ return React5.useMemo(() => ({
219
+ ref,
220
+ index
221
+ }), [index, ref]);
222
+ }
223
+
224
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/item/useCompositeItem.js
225
+ function useCompositeItem(params = {}) {
226
+ const {
227
+ highlightItemOnHover,
228
+ highlightedIndex,
229
+ onHighlightedIndexChange
230
+ } = useCompositeRootContext();
231
+ const {
232
+ ref,
233
+ index
234
+ } = useCompositeListItem(params);
235
+ const isHighlighted = highlightedIndex === index;
236
+ const itemRef = React6.useRef(null);
237
+ const mergedRef = useMergedRefs(ref, itemRef);
238
+ const compositeProps = React6.useMemo(() => ({
239
+ tabIndex: isHighlighted ? 0 : -1,
240
+ onFocus() {
241
+ onHighlightedIndexChange(index);
242
+ },
243
+ onMouseMove() {
244
+ const item = itemRef.current;
245
+ if (!highlightItemOnHover || !item) {
246
+ return;
247
+ }
248
+ const disabled2 = item.hasAttribute("disabled") || item.ariaDisabled === "true";
249
+ if (!isHighlighted && !disabled2) {
250
+ item.focus();
251
+ }
252
+ }
253
+ }), [isHighlighted, onHighlightedIndexChange, index, highlightItemOnHover]);
254
+ return {
255
+ compositeProps,
256
+ compositeRef: mergedRef,
257
+ index
258
+ };
259
+ }
260
+
261
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/composite/item/CompositeItem.js
262
+ function CompositeItem(componentProps) {
263
+ const {
264
+ render,
265
+ className,
266
+ state = EMPTY_OBJECT,
267
+ props = EMPTY_ARRAY,
268
+ refs = EMPTY_ARRAY,
269
+ metadata,
270
+ stateAttributesMapping,
271
+ tag = "div",
272
+ ...elementProps
273
+ } = componentProps;
274
+ const {
275
+ compositeProps,
276
+ compositeRef
277
+ } = useCompositeItem({
278
+ metadata
279
+ });
280
+ return useRenderElement(tag, componentProps, {
281
+ state,
282
+ ref: [...refs, compositeRef],
283
+ props: [compositeProps, ...props, elementProps],
284
+ stateAttributesMapping
285
+ });
286
+ }
287
+
288
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/reason-parts.js
289
+ var reason_parts_exports = {};
290
+ __export(reason_parts_exports, {
291
+ cancelOpen: () => cancelOpen,
292
+ chipRemovePress: () => chipRemovePress,
293
+ clearPress: () => clearPress,
294
+ closePress: () => closePress,
295
+ closeWatcher: () => closeWatcher,
296
+ decrementPress: () => decrementPress,
297
+ disabled: () => disabled,
298
+ drag: () => drag,
299
+ escapeKey: () => escapeKey,
300
+ focusOut: () => focusOut,
301
+ imperativeAction: () => imperativeAction,
302
+ incrementPress: () => incrementPress,
303
+ inputBlur: () => inputBlur,
304
+ inputChange: () => inputChange,
305
+ inputClear: () => inputClear,
306
+ inputPaste: () => inputPaste,
307
+ inputPress: () => inputPress,
308
+ itemPress: () => itemPress,
309
+ keyboard: () => keyboard,
310
+ linkPress: () => linkPress,
311
+ listNavigation: () => listNavigation,
312
+ none: () => none,
313
+ outsidePress: () => outsidePress,
314
+ pointer: () => pointer,
315
+ scrub: () => scrub,
316
+ siblingOpen: () => siblingOpen,
317
+ swipe: () => swipe,
318
+ trackPress: () => trackPress,
319
+ triggerFocus: () => triggerFocus,
320
+ triggerHover: () => triggerHover,
321
+ triggerPress: () => triggerPress,
322
+ wheel: () => wheel,
323
+ windowResize: () => windowResize
324
+ });
325
+ var none = "none";
326
+ var triggerPress = "trigger-press";
327
+ var triggerHover = "trigger-hover";
328
+ var triggerFocus = "trigger-focus";
329
+ var outsidePress = "outside-press";
330
+ var itemPress = "item-press";
331
+ var closePress = "close-press";
332
+ var linkPress = "link-press";
333
+ var clearPress = "clear-press";
334
+ var chipRemovePress = "chip-remove-press";
335
+ var trackPress = "track-press";
336
+ var incrementPress = "increment-press";
337
+ var decrementPress = "decrement-press";
338
+ var inputChange = "input-change";
339
+ var inputClear = "input-clear";
340
+ var inputBlur = "input-blur";
341
+ var inputPaste = "input-paste";
342
+ var inputPress = "input-press";
343
+ var focusOut = "focus-out";
344
+ var escapeKey = "escape-key";
345
+ var closeWatcher = "close-watcher";
346
+ var listNavigation = "list-navigation";
347
+ var keyboard = "keyboard";
348
+ var pointer = "pointer";
349
+ var drag = "drag";
350
+ var wheel = "wheel";
351
+ var scrub = "scrub";
352
+ var cancelOpen = "cancel-open";
353
+ var siblingOpen = "sibling-open";
354
+ var disabled = "disabled";
355
+ var imperativeAction = "imperative-action";
356
+ var swipe = "swipe";
357
+ var windowResize = "window-resize";
358
+
359
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/utils/createBaseUIEventDetails.js
360
+ function createChangeEventDetails(reason, event, trigger, customProperties) {
361
+ let canceled = false;
362
+ let allowPropagation = false;
363
+ const custom = customProperties ?? EMPTY_OBJECT;
364
+ const details = {
365
+ reason,
366
+ event: event ?? new Event("base-ui"),
367
+ cancel() {
368
+ canceled = true;
369
+ },
370
+ allowPropagation() {
371
+ allowPropagation = true;
372
+ },
373
+ get isCanceled() {
374
+ return canceled;
375
+ },
376
+ get isPropagationAllowed() {
377
+ return allowPropagation;
378
+ },
379
+ trigger,
380
+ ...custom
381
+ };
382
+ return details;
383
+ }
384
+
385
+ // ../../node_modules/.pnpm/@base-ui+react@1.2.0_@types+react@19.2.14_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/@base-ui/react/esm/toggle/Toggle.js
386
+ import { jsx as _jsx } from "react/jsx-runtime";
387
+ var Toggle = /* @__PURE__ */ React7.forwardRef(function Toggle2(componentProps, forwardedRef) {
388
+ const {
389
+ className,
390
+ defaultPressed: defaultPressedProp = false,
391
+ disabled: disabledProp = false,
392
+ form,
393
+ // never participates in form validation
394
+ onPressedChange: onPressedChangeProp,
395
+ pressed: pressedProp,
396
+ render,
397
+ type,
398
+ // cannot change button type
399
+ value: valueProp,
400
+ nativeButton = true,
401
+ ...elementProps
402
+ } = componentProps;
403
+ const value = useBaseUiId(valueProp || void 0);
404
+ const groupContext = useToggleGroupContext();
405
+ const groupValue = groupContext?.value ?? [];
406
+ const defaultPressed = groupContext ? void 0 : defaultPressedProp;
407
+ const disabled2 = (disabledProp || groupContext?.disabled) ?? false;
408
+ if (process.env.NODE_ENV !== "production") {
409
+ useIsoLayoutEffect(() => {
410
+ if (groupContext && valueProp === void 0 && groupContext.isValueInitialized) {
411
+ error("A `<Toggle>` component rendered in a `<ToggleGroup>` has no explicit `value` prop.", "This will cause issues between the Toggle Group and Toggle values.", "Provide the `<Toggle>` with a `value` prop matching the `<ToggleGroup>` values prop type.");
412
+ }
413
+ }, [groupContext, valueProp, groupContext?.isValueInitialized]);
414
+ }
415
+ const [pressed, setPressedState] = useControlled({
416
+ controlled: groupContext ? value !== void 0 && groupValue.indexOf(value) > -1 : pressedProp,
417
+ default: defaultPressed,
418
+ name: "Toggle",
419
+ state: "pressed"
420
+ });
421
+ const onPressedChange = useStableCallback((nextPressed, eventDetails) => {
422
+ if (value) {
423
+ groupContext?.setGroupValue?.(value, nextPressed, eventDetails);
424
+ }
425
+ onPressedChangeProp?.(nextPressed, eventDetails);
426
+ });
427
+ const {
428
+ getButtonProps,
429
+ buttonRef
430
+ } = useButton({
431
+ disabled: disabled2,
432
+ native: nativeButton
433
+ });
434
+ const state = {
435
+ disabled: disabled2,
436
+ pressed
437
+ };
438
+ const refs = [buttonRef, forwardedRef];
439
+ const props = [{
440
+ "aria-pressed": pressed,
441
+ onClick(event) {
442
+ const nextPressed = !pressed;
443
+ const details = createChangeEventDetails(reason_parts_exports.none, event.nativeEvent);
444
+ onPressedChange(nextPressed, details);
445
+ if (details.isCanceled) {
446
+ return;
447
+ }
448
+ setPressedState(nextPressed);
449
+ }
450
+ }, elementProps, getButtonProps];
451
+ const element = useRenderElement("button", componentProps, {
452
+ enabled: !groupContext,
453
+ state,
454
+ ref: refs,
455
+ props
456
+ });
457
+ if (groupContext) {
458
+ return /* @__PURE__ */ _jsx(CompositeItem, {
459
+ tag: "button",
460
+ render,
461
+ className,
462
+ state,
463
+ refs,
464
+ props
465
+ });
466
+ }
467
+ return element;
468
+ });
469
+ if (process.env.NODE_ENV !== "production") Toggle.displayName = "Toggle";
470
+
471
+ // ../ui/src/components/toggle.tsx
472
+ import { jsx } from "react/jsx-runtime";
473
+ var toggleVariants = cva(
474
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
475
+ {
476
+ variants: {
477
+ variant: {
478
+ default: "bg-transparent",
479
+ outline: "border border-input bg-transparent hover:bg-muted"
480
+ },
481
+ size: {
482
+ default: "h-8 min-w-8 px-2",
483
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]",
484
+ lg: "h-9 min-w-9 px-2.5"
485
+ }
486
+ },
487
+ defaultVariants: {
488
+ variant: "default",
489
+ size: "default"
490
+ }
491
+ }
492
+ );
493
+ function Toggle3({
494
+ className,
495
+ variant = "default",
496
+ size = "default",
497
+ ...props
498
+ }) {
499
+ return /* @__PURE__ */ jsx(
500
+ Toggle,
501
+ {
502
+ "data-slot": "toggle",
503
+ className: cn(toggleVariants({ variant, size, className })),
504
+ ...props
505
+ }
506
+ );
507
+ }
508
+
509
+ // src/lib/schema-serializer.ts
510
+ import { z } from "zod";
511
+ function serializeTools(tools) {
512
+ return tools.map((tool) => ({
513
+ name: tool.name,
514
+ description: tool.description,
515
+ parameters: z.toJSONSchema(tool.parameters, {
516
+ target: "openapi-3.0"
517
+ })
518
+ }));
519
+ }
520
+
521
+ // src/hooks/use-tool-registration.ts
522
+ import { useEffect as useEffect3 } from "react";
523
+ import { RpcError } from "livekit-client";
524
+ import { useEnsureRoom } from "@livekit/components-react";
525
+ function useToolRegistration(tools, onToolComplete) {
526
+ const room = useEnsureRoom();
527
+ useEffect3(() => {
528
+ if (!tools || tools.length === 0) return;
529
+ const cleanups = [];
530
+ for (const tool of tools) {
531
+ const methodName = `tool:${tool.name}`;
532
+ room.registerRpcMethod(methodName, async (data) => {
533
+ try {
534
+ const params = JSON.parse(data.payload);
535
+ const parsed = tool.parameters.parse(params);
536
+ const result = await tool.execute(parsed);
537
+ onToolComplete?.();
538
+ return JSON.stringify(result);
539
+ } catch (error2) {
540
+ const message = error2 instanceof Error ? error2.message : "Tool execution failed";
541
+ throw new RpcError(1, message);
542
+ }
543
+ });
544
+ cleanups.push(() => {
545
+ room.unregisterRpcMethod(methodName);
546
+ });
547
+ }
548
+ return () => {
549
+ for (const cleanup of cleanups) {
550
+ cleanup();
551
+ }
552
+ };
553
+ }, [room, tools, onToolComplete]);
554
+ }
555
+
556
+ // src/components/agent-session-provider.tsx
557
+ import {
558
+ SessionProvider,
559
+ RoomAudioRenderer
560
+ } from "@livekit/components-react";
561
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
562
+ function AgentSessionProvider({
563
+ session,
564
+ children,
565
+ ...roomAudioRendererProps
566
+ }) {
567
+ return /* @__PURE__ */ jsxs(SessionProvider, { session, children: [
568
+ children,
569
+ /* @__PURE__ */ jsx2(RoomAudioRenderer, { ...roomAudioRendererProps })
570
+ ] });
571
+ }
572
+
573
+ // src/components/agent-audio-visualizer-bar.tsx
574
+ import { Fragment, useMemo as useMemo3 } from "react";
575
+ import {
576
+ useMultibandTrackVolume
577
+ } from "@livekit/components-react";
578
+
579
+ // src/hooks/use-agent-audio-visualizer-bar.ts
580
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
581
+ function generateConnectingSequenceBar(columns) {
582
+ const seq = [];
583
+ for (let x = 0; x < columns; x++) {
584
+ seq.push([x, columns - 1 - x]);
585
+ }
586
+ return seq;
587
+ }
588
+ function generateListeningSequenceBar(columns) {
589
+ const center = Math.floor(columns / 2);
590
+ return [[center], [-1]];
591
+ }
592
+ function useAgentAudioVisualizerBarAnimator(state, columns, interval) {
593
+ const [index, setIndex] = useState4(0);
594
+ const [sequence, setSequence] = useState4([[]]);
595
+ useEffect4(() => {
596
+ if (state === "thinking") {
597
+ setSequence(generateListeningSequenceBar(columns));
598
+ } else if (state === "connecting" || state === "initializing") {
599
+ setSequence([...generateConnectingSequenceBar(columns)]);
600
+ } else if (state === "listening") {
601
+ setSequence(generateListeningSequenceBar(columns));
602
+ } else if (state === void 0 || state === "speaking") {
603
+ setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);
604
+ } else {
605
+ setSequence([[]]);
606
+ }
607
+ setIndex(0);
608
+ }, [state, columns]);
609
+ const animationFrameId = useRef4(null);
610
+ useEffect4(() => {
611
+ let startTime = performance.now();
612
+ const animate = (time) => {
613
+ if (time - startTime >= interval) {
614
+ setIndex((prev) => prev + 1);
615
+ startTime = time;
616
+ }
617
+ animationFrameId.current = requestAnimationFrame(animate);
618
+ };
619
+ animationFrameId.current = requestAnimationFrame(animate);
620
+ return () => {
621
+ if (animationFrameId.current !== null) {
622
+ cancelAnimationFrame(animationFrameId.current);
623
+ }
624
+ };
625
+ }, [interval, columns, state, sequence.length]);
626
+ return sequence[index % sequence.length] ?? [];
627
+ }
628
+
629
+ // src/components/agent-audio-visualizer-bar.tsx
630
+ import { jsx as jsx3 } from "react/jsx-runtime";
631
+ function AgentAudioVisualizerBar({
632
+ state = "connecting",
633
+ barCount = 3,
634
+ audioTrack,
635
+ className,
636
+ children
637
+ }) {
638
+ const sequencerInterval = useMemo3(() => {
639
+ switch (state) {
640
+ case "connecting":
641
+ return 2e3 / barCount;
642
+ case "initializing":
643
+ return 2e3;
644
+ case "listening":
645
+ return 500;
646
+ case "thinking":
647
+ return 150;
648
+ default:
649
+ return 1e3;
650
+ }
651
+ }, [state, barCount]);
652
+ const highlightedIndices = useAgentAudioVisualizerBarAnimator(
653
+ state,
654
+ barCount,
655
+ sequencerInterval
656
+ );
657
+ const volumeBands = useMultibandTrackVolume(audioTrack, {
658
+ bands: barCount,
659
+ loPass: 100,
660
+ hiPass: 200
661
+ });
662
+ const bands = useMemo3(
663
+ () => state === "speaking" ? volumeBands : new Array(barCount).fill(0),
664
+ [state, volumeBands, barCount]
665
+ );
666
+ return /* @__PURE__ */ jsx3(
667
+ "div",
668
+ {
669
+ "data-lk-state": state,
670
+ className: cn(
671
+ "relative flex h-6 items-center justify-center gap-0.5",
672
+ className
673
+ ),
674
+ children: bands.map(
675
+ (band, idx) => children ? /* @__PURE__ */ jsx3(Fragment, { children }, idx) : /* @__PURE__ */ jsx3(
676
+ "div",
677
+ {
678
+ "data-lk-index": idx,
679
+ "data-lk-highlighted": highlightedIndices.includes(idx),
680
+ style: { height: `${Math.max(4, band * 24)}px` },
681
+ className: "min-h-1 w-1 rounded-full bg-primary/20 transition-[height] duration-100 ease-linear data-[lk-highlighted=true]:bg-primary"
682
+ },
683
+ idx
684
+ )
685
+ )
686
+ }
687
+ );
688
+ }
689
+
690
+ // src/components/agent-track-toggle.tsx
691
+ import { useMemo as useMemo4, useState as useState5 } from "react";
692
+ import { Track } from "livekit-client";
693
+ import {
694
+ MicIcon,
695
+ MicOffIcon,
696
+ Loading03Icon,
697
+ VideoIcon,
698
+ VideoOffIcon
699
+ } from "@hugeicons/core-free-icons";
700
+ import { HugeiconsIcon } from "@hugeicons/react";
701
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
702
+ function getSourceIcon(source, enabled, pending = false) {
703
+ if (pending) return Loading03Icon;
704
+ switch (source) {
705
+ case Track.Source.Microphone:
706
+ return enabled ? MicIcon : MicOffIcon;
707
+ case Track.Source.Camera:
708
+ return enabled ? VideoIcon : VideoOffIcon;
709
+ default:
710
+ return MicIcon;
711
+ }
712
+ }
713
+ function AgentTrackToggle({
714
+ source,
715
+ pending = false,
716
+ pressed,
717
+ defaultPressed = false,
718
+ className,
719
+ onPressedChange,
720
+ ...props
721
+ }) {
722
+ const [uncontrolledPressed, setUncontrolledPressed] = useState5(
723
+ defaultPressed ?? false
724
+ );
725
+ const isControlled = pressed !== void 0;
726
+ const resolvedPressed = useMemo4(
727
+ () => (isControlled ? pressed : uncontrolledPressed) ?? false,
728
+ [isControlled, pressed, uncontrolledPressed]
729
+ );
730
+ const IconComponent = getSourceIcon(
731
+ source,
732
+ resolvedPressed,
733
+ pending
734
+ );
735
+ const handlePressedChange = (nextPressed) => {
736
+ if (!isControlled) setUncontrolledPressed(nextPressed);
737
+ onPressedChange?.(nextPressed);
738
+ };
739
+ return /* @__PURE__ */ jsxs2(
740
+ Toggle3,
741
+ {
742
+ pressed: isControlled ? pressed : void 0,
743
+ defaultPressed: isControlled ? void 0 : defaultPressed,
744
+ "aria-label": `Toggle ${source}`,
745
+ onPressedChange: handlePressedChange,
746
+ className: cn(
747
+ "size-9 rounded-lg",
748
+ "data-[state=on]:bg-muted data-[state=on]:text-foreground",
749
+ "data-[state=off]:bg-destructive/10 data-[state=off]:text-destructive",
750
+ className
751
+ ),
752
+ ...props,
753
+ children: [
754
+ /* @__PURE__ */ jsx4(
755
+ HugeiconsIcon,
756
+ {
757
+ icon: IconComponent,
758
+ className: cn(pending && "animate-spin")
759
+ }
760
+ ),
761
+ props.children
762
+ ]
763
+ }
764
+ );
765
+ }
766
+
767
+ // src/components/agent-track-control.tsx
768
+ import { jsx as jsx5 } from "react/jsx-runtime";
769
+ function AgentTrackControl({
770
+ source,
771
+ pressed,
772
+ pending,
773
+ disabled: disabled2,
774
+ className,
775
+ audioTrack,
776
+ onPressedChange
777
+ }) {
778
+ return /* @__PURE__ */ jsx5("div", { className: cn("flex items-center gap-0 rounded-md", className), children: /* @__PURE__ */ jsx5(
779
+ AgentTrackToggle,
780
+ {
781
+ source,
782
+ pressed,
783
+ pending,
784
+ disabled: disabled2,
785
+ onPressedChange,
786
+ className: "peer/track group/track focus:z-10 has-[.audiovisualizer]:w-auto has-[.audiovisualizer]:px-3 has-[~_button]:rounded-r-none has-[~_button]:border-r-0 has-[~_button]:pr-2 has-[~_button]:pl-3",
787
+ children: audioTrack && /* @__PURE__ */ jsx5(
788
+ AgentAudioVisualizerBar,
789
+ {
790
+ barCount: 3,
791
+ state: pressed ? "speaking" : "disconnected",
792
+ audioTrack: pressed ? audioTrack : void 0,
793
+ className: "audiovisualizer"
794
+ }
795
+ )
796
+ }
797
+ ) });
798
+ }
799
+
800
+ // src/components/agent-disconnect-button.tsx
801
+ import { useSessionContext } from "@livekit/components-react";
802
+ import { CallEnd03Icon } from "@hugeicons/core-free-icons";
803
+ import { HugeiconsIcon as HugeiconsIcon2 } from "@hugeicons/react";
804
+ import { jsx as jsx6 } from "react/jsx-runtime";
805
+ function AgentDisconnectButton({
806
+ children,
807
+ onClick,
808
+ className,
809
+ ...props
810
+ }) {
811
+ const { end } = useSessionContext();
812
+ const handleClick = (event) => {
813
+ onClick?.(event);
814
+ if (typeof end === "function") end();
815
+ };
816
+ return /* @__PURE__ */ jsx6(
817
+ Button,
818
+ {
819
+ variant: "destructive",
820
+ size: "icon",
821
+ onClick: handleClick,
822
+ className,
823
+ ...props,
824
+ children: children ?? /* @__PURE__ */ jsx6(HugeiconsIcon2, { icon: CallEnd03Icon })
825
+ }
826
+ );
827
+ }
828
+
829
+ // src/components/agent-transcript-panel.tsx
830
+ import { useState as useState6 } from "react";
831
+ import { useChat } from "@livekit/components-react";
832
+ import { HugeiconsIcon as HugeiconsIcon3 } from "@hugeicons/react";
833
+ import { Loading03Icon as Loading03Icon2, Navigation03Icon } from "@hugeicons/core-free-icons";
834
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
835
+ function AgentTranscriptPanel() {
836
+ const { send } = useChat();
837
+ const [message, setMessage] = useState6("");
838
+ const [isSending, setIsSending] = useState6(false);
839
+ const isDisabled = isSending || message.trim().length === 0;
840
+ const handleSend = async () => {
841
+ if (isDisabled) return;
842
+ try {
843
+ setIsSending(true);
844
+ await send(message.trim());
845
+ setMessage("");
846
+ } catch {
847
+ } finally {
848
+ setIsSending(false);
849
+ }
850
+ };
851
+ const handleKeyDown = (e) => {
852
+ if (e.key === "Enter" && !e.shiftKey) {
853
+ e.preventDefault();
854
+ handleSend();
855
+ }
856
+ };
857
+ return /* @__PURE__ */ jsx7("div", { className: "flex h-full flex-col border-b", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-end gap-1 px-2 py-1", children: [
858
+ /* @__PURE__ */ jsx7(
859
+ "textarea",
860
+ {
861
+ autoFocus: true,
862
+ value: message,
863
+ disabled: isSending,
864
+ placeholder: "Type something...",
865
+ onKeyDown: handleKeyDown,
866
+ onChange: (e) => setMessage(e.target.value),
867
+ className: "field-sizing-content max-h-20 min-h-6 flex-1 resize-none py-1 text-xs [scrollbar-width:thin] focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
868
+ }
869
+ ),
870
+ /* @__PURE__ */ jsx7(
871
+ "button",
872
+ {
873
+ type: "button",
874
+ disabled: isDisabled,
875
+ onClick: handleSend,
876
+ title: isSending ? "Sending\u2026" : "Send",
877
+ className: cn(
878
+ "flex size-6 flex-shrink-0 items-center justify-center self-end rounded",
879
+ isDisabled ? "cursor-not-allowed text-muted-foreground" : "cursor-pointer text-foreground hover:opacity-80"
880
+ ),
881
+ children: isSending ? /* @__PURE__ */ jsx7(
882
+ HugeiconsIcon3,
883
+ {
884
+ icon: Loading03Icon2,
885
+ size: 18,
886
+ className: "animate-spin"
887
+ }
888
+ ) : /* @__PURE__ */ jsx7(
889
+ HugeiconsIcon3,
890
+ {
891
+ icon: Navigation03Icon,
892
+ size: 18,
893
+ className: "fill-primary/50 text-primary"
894
+ }
895
+ )
896
+ }
897
+ )
898
+ ] }) });
899
+ }
900
+
901
+ // src/hooks/use-input-controls.ts
902
+ import { useCallback as useCallback3 } from "react";
903
+ import { Track as Track2 } from "livekit-client";
904
+ import {
905
+ useTrackToggle,
906
+ usePersistentUserChoices,
907
+ useLocalParticipantPermissions,
908
+ useSessionContext as useSessionContext2
909
+ } from "@livekit/components-react";
910
+ function useInputControls({
911
+ saveUserChoices = true,
912
+ onDeviceError
913
+ } = {}) {
914
+ const {
915
+ local: { microphoneTrack }
916
+ } = useSessionContext2();
917
+ const microphoneToggle = useTrackToggle({
918
+ source: Track2.Source.Microphone,
919
+ onDeviceError: (error2) => onDeviceError?.({ source: Track2.Source.Microphone, error: error2 })
920
+ });
921
+ const cameraToggle = useTrackToggle({
922
+ source: Track2.Source.Camera,
923
+ onDeviceError: (error2) => onDeviceError?.({ source: Track2.Source.Camera, error: error2 })
924
+ });
925
+ const screenShareToggle = useTrackToggle({
926
+ source: Track2.Source.ScreenShare,
927
+ onDeviceError: (error2) => onDeviceError?.({ source: Track2.Source.ScreenShare, error: error2 })
928
+ });
929
+ const {
930
+ saveAudioInputEnabled,
931
+ saveVideoInputEnabled,
932
+ saveAudioInputDeviceId,
933
+ saveVideoInputDeviceId
934
+ } = usePersistentUserChoices({ preventSave: !saveUserChoices });
935
+ const handleAudioDeviceChange = useCallback3(
936
+ (deviceId) => {
937
+ saveAudioInputDeviceId(deviceId ?? "default");
938
+ },
939
+ [saveAudioInputDeviceId]
940
+ );
941
+ const handleVideoDeviceChange = useCallback3(
942
+ (deviceId) => {
943
+ saveVideoInputDeviceId(deviceId ?? "default");
944
+ },
945
+ [saveVideoInputDeviceId]
946
+ );
947
+ const handleToggleCamera = useCallback3(
948
+ async (enabled) => {
949
+ if (screenShareToggle.enabled) {
950
+ screenShareToggle.toggle(false);
951
+ }
952
+ await cameraToggle.toggle(enabled);
953
+ saveVideoInputEnabled(!cameraToggle.enabled);
954
+ },
955
+ [cameraToggle, screenShareToggle, saveVideoInputEnabled]
956
+ );
957
+ const handleToggleMicrophone = useCallback3(
958
+ async (enabled) => {
959
+ await microphoneToggle.toggle(enabled);
960
+ saveAudioInputEnabled(!microphoneToggle.enabled);
961
+ },
962
+ [microphoneToggle, saveAudioInputEnabled]
963
+ );
964
+ const handleToggleScreenShare = useCallback3(
965
+ async (enabled) => {
966
+ if (cameraToggle.enabled) {
967
+ cameraToggle.toggle(false);
968
+ }
969
+ await screenShareToggle.toggle(enabled);
970
+ },
971
+ [cameraToggle, screenShareToggle]
972
+ );
973
+ const handleMicrophoneDeviceSelectError = useCallback3(
974
+ (error2) => onDeviceError?.({ source: Track2.Source.Microphone, error: error2 }),
975
+ [onDeviceError]
976
+ );
977
+ const handleCameraDeviceSelectError = useCallback3(
978
+ (error2) => onDeviceError?.({ source: Track2.Source.Camera, error: error2 }),
979
+ [onDeviceError]
980
+ );
981
+ return {
982
+ microphoneTrack,
983
+ cameraToggle: {
984
+ ...cameraToggle,
985
+ toggle: handleToggleCamera
986
+ },
987
+ microphoneToggle: {
988
+ ...microphoneToggle,
989
+ toggle: handleToggleMicrophone
990
+ },
991
+ screenShareToggle: {
992
+ ...screenShareToggle,
993
+ toggle: handleToggleScreenShare
994
+ },
995
+ handleAudioDeviceChange,
996
+ handleVideoDeviceChange,
997
+ handleMicrophoneDeviceSelectError,
998
+ handleCameraDeviceSelectError
999
+ };
1000
+ }
1001
+
1002
+ // src/components/start-audio-button.tsx
1003
+ import { useEnsureRoom as useEnsureRoom2, useStartAudio } from "@livekit/components-react";
1004
+ import { jsx as jsx8 } from "react/jsx-runtime";
1005
+ function StartAudioButton({
1006
+ size = "default",
1007
+ variant = "default",
1008
+ label,
1009
+ room,
1010
+ ...props
1011
+ }) {
1012
+ const roomEnsured = useEnsureRoom2(room);
1013
+ const { mergedProps } = useStartAudio({ room: roomEnsured, props });
1014
+ return /* @__PURE__ */ jsx8(Button, { size, variant, ...props, ...mergedProps, children: label });
1015
+ }
1016
+
1017
+ // src/components/workflow-execution.tsx
1018
+ import { useState as useState7, useCallback as useCallback4, useRef as useRef5 } from "react";
1019
+ import { createPortal } from "react-dom";
1020
+ import {
1021
+ motion,
1022
+ useMotionValue,
1023
+ useSpring,
1024
+ AnimatePresence
1025
+ } from "motion/react";
1026
+ import {
1027
+ Cursor02Icon,
1028
+ ArrowLeft01Icon,
1029
+ ArrowRight01Icon,
1030
+ Tick01Icon
1031
+ } from "@hugeicons/core-free-icons";
1032
+ import { HugeiconsIcon as HugeiconsIcon4 } from "@hugeicons/react";
1033
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1034
+ var SPRING_CONFIG = { stiffness: 120, damping: 20, mass: 0.8 };
1035
+ var CURSOR_OFFSET = 16;
1036
+ function WorkflowExecution({
1037
+ workflow,
1038
+ cursor,
1039
+ onClose,
1040
+ onError
1041
+ }) {
1042
+ const [currentIndex, setCurrentIndex] = useState7(0);
1043
+ const steps = workflow.steps;
1044
+ const currentStep = steps[currentIndex];
1045
+ const isFirstStep = currentIndex === 0;
1046
+ const isLastStep = currentIndex === steps.length - 1;
1047
+ const handleBack = useCallback4(() => {
1048
+ setCurrentIndex((prev) => Math.max(0, prev - 1));
1049
+ }, []);
1050
+ const handleNext = useCallback4(() => {
1051
+ setCurrentIndex((prev) => {
1052
+ if (prev >= steps.length - 1) {
1053
+ onClose();
1054
+ return prev;
1055
+ }
1056
+ return prev + 1;
1057
+ });
1058
+ }, [steps.length, onClose]);
1059
+ if (!currentStep) return null;
1060
+ return /* @__PURE__ */ jsx9(
1061
+ ExecutionOverlay,
1062
+ {
1063
+ step: currentStep,
1064
+ stepIndex: currentIndex,
1065
+ totalSteps: steps.length,
1066
+ isFirstStep,
1067
+ isLastStep,
1068
+ cursor,
1069
+ onBack: handleBack,
1070
+ onNext: handleNext,
1071
+ onError
1072
+ }
1073
+ );
1074
+ }
1075
+ function ExecutionOverlay({
1076
+ step,
1077
+ stepIndex,
1078
+ totalSteps,
1079
+ isFirstStep,
1080
+ isLastStep,
1081
+ cursor,
1082
+ onBack,
1083
+ onNext,
1084
+ onError
1085
+ }) {
1086
+ const { rect } = useElementTracker({
1087
+ selector: step.elementId,
1088
+ urlPath: step.urlPath,
1089
+ onError: onError ? (msg) => {
1090
+ if (msg !== null) onError(msg);
1091
+ } : void 0,
1092
+ onClick: onNext
1093
+ });
1094
+ const hasAnimated = useRef5(false);
1095
+ const springX = useSpring(useMotionValue(0), SPRING_CONFIG);
1096
+ const springY = useSpring(useMotionValue(0), SPRING_CONFIG);
1097
+ if (rect) {
1098
+ const targetX = rect.right + CURSOR_OFFSET;
1099
+ const targetY = rect.bottom + CURSOR_OFFSET;
1100
+ if (!hasAnimated.current) {
1101
+ springX.jump(targetX);
1102
+ springY.jump(targetY);
1103
+ hasAnimated.current = true;
1104
+ } else {
1105
+ springX.set(targetX);
1106
+ springY.set(targetY);
1107
+ }
1108
+ } else {
1109
+ hasAnimated.current = false;
1110
+ }
1111
+ return createPortal(
1112
+ /* @__PURE__ */ jsx9(AnimatePresence, { children: rect && /* @__PURE__ */ jsxs4(
1113
+ motion.div,
1114
+ {
1115
+ "data-meridial-ui": true,
1116
+ className: "pointer-events-none fixed z-[99999] flex items-start gap-3",
1117
+ style: { top: springY, left: springX },
1118
+ initial: { opacity: 0, scale: 0.6 },
1119
+ animate: { opacity: 1, scale: 1 },
1120
+ exit: { opacity: 0, scale: 0.6 },
1121
+ transition: { duration: 0.2 },
1122
+ children: [
1123
+ cursor ?? /* @__PURE__ */ jsx9(
1124
+ HugeiconsIcon4,
1125
+ {
1126
+ icon: Cursor02Icon,
1127
+ strokeWidth: 2,
1128
+ size: 32,
1129
+ className: "mt-1 shrink-0 fill-primary/60 text-primary"
1130
+ }
1131
+ ),
1132
+ /* @__PURE__ */ jsxs4(
1133
+ "div",
1134
+ {
1135
+ "data-meridial-ui": true,
1136
+ className: "pointer-events-auto w-72 overflow-hidden rounded-lg border border-border bg-card shadow-lg",
1137
+ children: [
1138
+ /* @__PURE__ */ jsx9(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxs4(
1139
+ motion.div,
1140
+ {
1141
+ initial: { opacity: 0, y: 8 },
1142
+ animate: { opacity: 1, y: 0 },
1143
+ exit: { opacity: 0, y: -8 },
1144
+ transition: { duration: 0.2, ease: "easeInOut" },
1145
+ className: "px-4 pt-3 pb-2",
1146
+ children: [
1147
+ /* @__PURE__ */ jsx9("p", { className: "text-sm leading-relaxed text-foreground", children: step.description || /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground italic", children: "No description" }) }),
1148
+ /* @__PURE__ */ jsxs4("span", { className: "mt-1 block text-xs text-muted-foreground", children: [
1149
+ "Step ",
1150
+ stepIndex + 1,
1151
+ " of ",
1152
+ totalSteps
1153
+ ] })
1154
+ ]
1155
+ },
1156
+ step.id
1157
+ ) }),
1158
+ /* @__PURE__ */ jsxs4(
1159
+ "div",
1160
+ {
1161
+ "data-meridial-ui": true,
1162
+ className: "flex justify-end gap-2 border-t px-3 py-2",
1163
+ children: [
1164
+ /* @__PURE__ */ jsxs4(
1165
+ Button,
1166
+ {
1167
+ variant: "outline",
1168
+ size: "sm",
1169
+ "data-meridial-ui": true,
1170
+ onClick: onBack,
1171
+ disabled: isFirstStep,
1172
+ children: [
1173
+ /* @__PURE__ */ jsx9(HugeiconsIcon4, { icon: ArrowLeft01Icon, size: 14 }),
1174
+ "Back"
1175
+ ]
1176
+ }
1177
+ ),
1178
+ /* @__PURE__ */ jsx9(
1179
+ Button,
1180
+ {
1181
+ variant: "default",
1182
+ size: "sm",
1183
+ "data-meridial-ui": true,
1184
+ onClick: onNext,
1185
+ children: isLastStep ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1186
+ "Finish",
1187
+ /* @__PURE__ */ jsx9(HugeiconsIcon4, { icon: Tick01Icon, size: 14 })
1188
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1189
+ "Next",
1190
+ /* @__PURE__ */ jsx9(HugeiconsIcon4, { icon: ArrowRight01Icon, size: 14 })
1191
+ ] })
1192
+ }
1193
+ )
1194
+ ]
1195
+ }
1196
+ )
1197
+ ]
1198
+ }
1199
+ )
1200
+ ]
1201
+ }
1202
+ ) }),
1203
+ document.body
1204
+ );
1205
+ }
1206
+
1207
+ // src/components/outcome-bar.tsx
1208
+ import {
1209
+ ThumbsUpIcon,
1210
+ ThumbsDownIcon,
1211
+ MultiplicationSignIcon
1212
+ } from "@hugeicons/core-free-icons";
1213
+ import { HugeiconsIcon as HugeiconsIcon5 } from "@hugeicons/react";
1214
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1215
+ function OutcomeBar({ onSubmit }) {
1216
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
1217
+ /* @__PURE__ */ jsx10("span", { className: "text-sm text-muted-foreground", children: "Did we help?" }),
1218
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", children: [
1219
+ /* @__PURE__ */ jsx10(
1220
+ Button,
1221
+ {
1222
+ variant: "ghost",
1223
+ size: "icon-sm",
1224
+ onClick: () => onSubmit("POSITIVE"),
1225
+ "aria-label": "Yes, this helped",
1226
+ children: /* @__PURE__ */ jsx10(HugeiconsIcon5, { icon: ThumbsUpIcon, className: "size-4" })
1227
+ }
1228
+ ),
1229
+ /* @__PURE__ */ jsx10(
1230
+ Button,
1231
+ {
1232
+ variant: "ghost",
1233
+ size: "icon-sm",
1234
+ onClick: () => onSubmit("NEGATIVE"),
1235
+ "aria-label": "No, this didn't help",
1236
+ children: /* @__PURE__ */ jsx10(HugeiconsIcon5, { icon: ThumbsDownIcon, className: "size-4" })
1237
+ }
1238
+ ),
1239
+ /* @__PURE__ */ jsx10(
1240
+ Button,
1241
+ {
1242
+ variant: "ghost",
1243
+ size: "icon-sm",
1244
+ onClick: () => onSubmit("NEUTRAL"),
1245
+ "aria-label": "Dismiss",
1246
+ children: /* @__PURE__ */ jsx10(HugeiconsIcon5, { icon: MultiplicationSignIcon, className: "size-4" })
1247
+ }
1248
+ )
1249
+ ] })
1250
+ ] });
1251
+ }
1252
+
1253
+ // src/components/meridial-logo.tsx
1254
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1255
+ function MeridialLogo(props) {
1256
+ return /* @__PURE__ */ jsxs6(
1257
+ "svg",
1258
+ {
1259
+ viewBox: "0 0 120 120",
1260
+ fill: "none",
1261
+ xmlns: "http://www.w3.org/2000/svg",
1262
+ ...props,
1263
+ children: [
1264
+ /* @__PURE__ */ jsx11(
1265
+ "path",
1266
+ {
1267
+ d: "M13.281 74.0881L15.7251 69.8003L37.7371 89.6218L83.4484 98.732L102.441 97.8991L83.2815 101.205L35.3287 95.2458L13.281 74.0881Z",
1268
+ fill: "#009EBC",
1269
+ stroke: "#009EBC",
1270
+ strokeWidth: "0.5"
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx11(
1274
+ "path",
1275
+ {
1276
+ d: "M88.6986 90.1613L15.4758 69.5032L36.8477 90.3775L83.0111 99.2024L102.445 97.7944L88.6986 90.1613Z",
1277
+ fill: "url(#fl-g0)"
1278
+ }
1279
+ ),
1280
+ /* @__PURE__ */ jsx11(
1281
+ "path",
1282
+ {
1283
+ d: "M9.59212 38.8499L12.934 33.2319L34.6462 62.7104L86.7973 80.1624L108.813 81.1994L86.6098 82.9394L33.6228 69.8917L9.59212 38.8499Z",
1284
+ fill: "#009EBC",
1285
+ stroke: "#009EBC",
1286
+ strokeWidth: "0.5"
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ jsx11(
1290
+ "path",
1291
+ {
1292
+ d: "M94.1734 69.4478L12.7627 33.0524L34.7293 62.7357L86.5626 80.2897L109.003 81.1016L94.1734 69.4478Z",
1293
+ fill: "url(#fl-g1)"
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsx11(
1297
+ "path",
1298
+ {
1299
+ d: "M28.3329 17.4895L33.7347 13.5331L45.9901 38.7534L78.8505 55.5351L93.5023 57.1106L78.3627 57.1381L38.9556 40.7981L28.3329 17.4895Z",
1300
+ fill: "#009EBC",
1301
+ stroke: "#009EBC",
1302
+ strokeWidth: "0.5"
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ jsx11(
1306
+ "path",
1307
+ {
1308
+ d: "M84.6112 46.7121L33.6396 12.9639L46.1488 38.6786L78.9538 55.4676L93.5814 57.0605L84.6112 46.7121Z",
1309
+ fill: "url(#fl-g2)"
1310
+ }
1311
+ ),
1312
+ /* @__PURE__ */ jsxs6("defs", { children: [
1313
+ /* @__PURE__ */ jsxs6(
1314
+ "linearGradient",
1315
+ {
1316
+ id: "fl-g0",
1317
+ x1: "57.7988",
1318
+ y1: "66.4369",
1319
+ x2: "60.2919",
1320
+ y2: "100.848",
1321
+ gradientUnits: "userSpaceOnUse",
1322
+ children: [
1323
+ /* @__PURE__ */ jsx11("stop", { stopColor: "#C3F6FF" }),
1324
+ /* @__PURE__ */ jsx11("stop", { offset: "1", stopColor: "#007B92" })
1325
+ ]
1326
+ }
1327
+ ),
1328
+ /* @__PURE__ */ jsxs6(
1329
+ "linearGradient",
1330
+ {
1331
+ id: "fl-g1",
1332
+ x1: "61.633",
1333
+ y1: "34.8205",
1334
+ x2: "60.0227",
1335
+ y2: "79.3295",
1336
+ gradientUnits: "userSpaceOnUse",
1337
+ children: [
1338
+ /* @__PURE__ */ jsx11("stop", { stopColor: "#B8F3FF" }),
1339
+ /* @__PURE__ */ jsx11("stop", { offset: "1", stopColor: "#007F99" })
1340
+ ]
1341
+ }
1342
+ ),
1343
+ /* @__PURE__ */ jsxs6(
1344
+ "linearGradient",
1345
+ {
1346
+ id: "fl-g2",
1347
+ x1: "65.4953",
1348
+ y1: "16.4329",
1349
+ x2: "61.4521",
1350
+ y2: "53.5618",
1351
+ gradientUnits: "userSpaceOnUse",
1352
+ children: [
1353
+ /* @__PURE__ */ jsx11("stop", { stopColor: "#D0F8FF" }),
1354
+ /* @__PURE__ */ jsx11("stop", { offset: "1", stopColor: "#009EBC" })
1355
+ ]
1356
+ }
1357
+ )
1358
+ ] })
1359
+ ]
1360
+ }
1361
+ );
1362
+ }
1363
+
1364
+ // src/components/voicebox-menu.tsx
1365
+ import { useState as useState8 } from "react";
1366
+ import { motion as motion2, AnimatePresence as AnimatePresence2 } from "motion/react";
1367
+ import { HugeiconsIcon as HugeiconsIcon6 } from "@hugeicons/react";
1368
+ import {
1369
+ Call02Icon,
1370
+ ViewOffSlashIcon,
1371
+ ArrowDown01Icon,
1372
+ PlayIcon
1373
+ } from "@hugeicons/core-free-icons";
1374
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1375
+ var POSITIONS = [
1376
+ "top-left",
1377
+ "top-right",
1378
+ "bottom-left",
1379
+ "bottom-right"
1380
+ ];
1381
+ var POSITION_LABELS = {
1382
+ "top-left": "TL",
1383
+ "top-right": "TR",
1384
+ "bottom-left": "BL",
1385
+ "bottom-right": "BR"
1386
+ };
1387
+ var MENU_PLACEMENT = {
1388
+ "bottom-right": {
1389
+ originX: 1,
1390
+ originY: 1,
1391
+ className: "right-0 bottom-full mb-3"
1392
+ },
1393
+ "bottom-left": {
1394
+ originX: 0,
1395
+ originY: 1,
1396
+ className: "left-0 bottom-full mb-3"
1397
+ },
1398
+ "top-right": {
1399
+ originX: 1,
1400
+ originY: 0,
1401
+ className: "right-0 top-full mt-3"
1402
+ },
1403
+ "top-left": {
1404
+ originX: 0,
1405
+ originY: 0,
1406
+ className: "left-0 top-full mt-3"
1407
+ }
1408
+ };
1409
+ function VoiceboxMenu({
1410
+ favoriteWorkflows,
1411
+ position,
1412
+ onPositionChange,
1413
+ onHide,
1414
+ onAskMeridial,
1415
+ onPlayGuide
1416
+ }) {
1417
+ const placement = MENU_PLACEMENT[position];
1418
+ const [guidesExpanded, setGuidesExpanded] = useState8(false);
1419
+ const hasFavorites = favoriteWorkflows.length > 0;
1420
+ return /* @__PURE__ */ jsxs7(
1421
+ motion2.div,
1422
+ {
1423
+ "data-meridial-ui": true,
1424
+ className: cn(
1425
+ "absolute z-50 w-56 overflow-hidden rounded-lg border border-border bg-card shadow-lg",
1426
+ placement.className
1427
+ ),
1428
+ initial: { opacity: 0, scale: 0.9 },
1429
+ animate: { opacity: 1, scale: 1 },
1430
+ exit: { opacity: 0, scale: 0.9 },
1431
+ style: {
1432
+ transformOrigin: `${placement.originX * 100}% ${placement.originY * 100}%`
1433
+ },
1434
+ transition: { duration: 0.15, ease: "easeOut" },
1435
+ children: [
1436
+ hasFavorites && /* @__PURE__ */ jsxs7("div", { "data-meridial-ui": true, className: "border-b border-border", children: [
1437
+ /* @__PURE__ */ jsxs7(
1438
+ "button",
1439
+ {
1440
+ "data-meridial-ui": true,
1441
+ onClick: () => setGuidesExpanded((prev) => !prev),
1442
+ className: "flex w-full cursor-pointer items-center justify-between px-3 py-2",
1443
+ children: [
1444
+ /* @__PURE__ */ jsx12("span", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: "Top Guides" }),
1445
+ /* @__PURE__ */ jsx12(
1446
+ HugeiconsIcon6,
1447
+ {
1448
+ icon: ArrowDown01Icon,
1449
+ size: 14,
1450
+ className: cn(
1451
+ "shrink-0 text-muted-foreground transition-transform duration-200",
1452
+ guidesExpanded && "rotate-180"
1453
+ )
1454
+ }
1455
+ )
1456
+ ]
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ jsx12(AnimatePresence2, { initial: false, children: guidesExpanded && /* @__PURE__ */ jsx12(
1460
+ motion2.div,
1461
+ {
1462
+ initial: { height: 0, opacity: 0 },
1463
+ animate: { height: "auto", opacity: 1 },
1464
+ exit: { height: 0, opacity: 0 },
1465
+ transition: { duration: 0.15, ease: "easeOut" },
1466
+ style: { overflow: "hidden" },
1467
+ children: /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-0.5 px-3 pb-2", children: favoriteWorkflows.map((wf) => /* @__PURE__ */ jsxs7(
1468
+ "button",
1469
+ {
1470
+ "data-meridial-ui": true,
1471
+ onClick: () => onPlayGuide(wf),
1472
+ className: "flex w-full cursor-pointer items-center gap-2 rounded px-1.5 py-1 text-left text-sm text-foreground hover:bg-muted/50",
1473
+ children: [
1474
+ /* @__PURE__ */ jsx12(
1475
+ HugeiconsIcon6,
1476
+ {
1477
+ icon: PlayIcon,
1478
+ size: 16,
1479
+ className: "shrink-0 text-primary fill-primary/60"
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: wf.name })
1483
+ ]
1484
+ },
1485
+ wf.id
1486
+ )) })
1487
+ }
1488
+ ) })
1489
+ ] }),
1490
+ /* @__PURE__ */ jsxs7("div", { "data-meridial-ui": true, className: "border-b border-border px-3 py-2", children: [
1491
+ /* @__PURE__ */ jsx12("span", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: "Position" }),
1492
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 grid grid-cols-4 gap-1", children: POSITIONS.map((pos) => /* @__PURE__ */ jsx12(
1493
+ "button",
1494
+ {
1495
+ "data-meridial-ui": true,
1496
+ onClick: () => onPositionChange(pos),
1497
+ className: cn(
1498
+ "cursor-pointer rounded px-2 py-1 text-xs font-medium transition-colors",
1499
+ pos === position ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"
1500
+ ),
1501
+ children: POSITION_LABELS[pos]
1502
+ },
1503
+ pos
1504
+ )) })
1505
+ ] }),
1506
+ /* @__PURE__ */ jsxs7("div", { className: "px-3 py-2 flex items-center justify-between", children: [
1507
+ /* @__PURE__ */ jsx12("span", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Hide Until Next Visit" }),
1508
+ /* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", "data-meridial-ui": true, onClick: onHide, children: /* @__PURE__ */ jsx12(
1509
+ HugeiconsIcon6,
1510
+ {
1511
+ icon: ViewOffSlashIcon,
1512
+ size: 16,
1513
+ className: "shrink-0"
1514
+ }
1515
+ ) })
1516
+ ] }),
1517
+ /* @__PURE__ */ jsx12(Separator, {}),
1518
+ /* @__PURE__ */ jsxs7("div", { className: "px-3 py-2 flex items-center justify-between", children: [
1519
+ /* @__PURE__ */ jsx12("span", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: "Ask For Help" }),
1520
+ /* @__PURE__ */ jsx12(
1521
+ Button,
1522
+ {
1523
+ "data-meridial-ui": true,
1524
+ onClick: onAskMeridial,
1525
+ variant: "ghost",
1526
+ size: "sm",
1527
+ children: /* @__PURE__ */ jsx12(
1528
+ HugeiconsIcon6,
1529
+ {
1530
+ icon: Call02Icon,
1531
+ size: 16,
1532
+ className: "shrink-0 fill-primary/60 text-primary"
1533
+ }
1534
+ )
1535
+ }
1536
+ )
1537
+ ] })
1538
+ ]
1539
+ }
1540
+ );
1541
+ }
1542
+
1543
+ // src/voicebox.tsx
1544
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1545
+ var BADGE_POSITION_CLASSES = {
1546
+ "bottom-right": "right-6 bottom-6",
1547
+ "bottom-left": "left-6 bottom-6",
1548
+ "top-right": "right-6 top-6",
1549
+ "top-left": "left-6 top-6"
1550
+ };
1551
+ var BAR_POSITION_CLASSES = {
1552
+ "bottom-right": "right-4 bottom-4",
1553
+ "bottom-left": "left-4 bottom-4",
1554
+ "top-right": "right-4 top-4",
1555
+ "top-left": "left-4 top-4"
1556
+ };
1557
+ function VoiceboxBadge({
1558
+ triggerIcon,
1559
+ onClick
1560
+ }) {
1561
+ return /* @__PURE__ */ jsx13(
1562
+ "button",
1563
+ {
1564
+ "data-meridial-ui": true,
1565
+ onClick,
1566
+ "aria-label": "Open assistant menu",
1567
+ className: "group size-10 flex cursor-pointer items-center justify-center rounded-full border border-border bg-background shadow-lg transition-transform duration-200 ease-out hover:scale-110",
1568
+ children: triggerIcon ?? /* @__PURE__ */ jsx13(MeridialLogo, { className: "size-6" })
1569
+ }
1570
+ );
1571
+ }
1572
+ function VoiceboxContent({
1573
+ baseUrl,
1574
+ publishableKey,
1575
+ tools,
1576
+ cursor,
1577
+ barPosition,
1578
+ initialWorkflow,
1579
+ onDisconnect,
1580
+ onError
1581
+ }) {
1582
+ const [showTranscript, setShowTranscript] = useState9(false);
1583
+ const [showOutcomeBar, setShowOutcomeBar] = useState9(false);
1584
+ const [activeWorkflow, setActiveWorkflow] = useState9(initialWorkflow ?? null);
1585
+ const workflowsRef = useRef6([]);
1586
+ const room = useEnsureRoom3();
1587
+ const autoStarted = useRef6(false);
1588
+ const outcomeShown = useRef6(false);
1589
+ const draggableRef = useRef6(null);
1590
+ const triggerOutcomeBar = useCallback5(() => {
1591
+ if (!outcomeShown.current) {
1592
+ outcomeShown.current = true;
1593
+ setShowOutcomeBar(true);
1594
+ }
1595
+ }, []);
1596
+ const handleOutcomeSubmit = useCallback5(
1597
+ async (outcome) => {
1598
+ setShowOutcomeBar(false);
1599
+ try {
1600
+ const sessionId = await room.getSid();
1601
+ await fetch(`${baseUrl}/api/outcome`, {
1602
+ method: "POST",
1603
+ headers: { "Content-Type": "application/json" },
1604
+ body: JSON.stringify({ publishableKey, sessionId, outcome })
1605
+ });
1606
+ } catch {
1607
+ }
1608
+ },
1609
+ [room, baseUrl, publishableKey]
1610
+ );
1611
+ useToolRegistration(tools, triggerOutcomeBar);
1612
+ useDraggable(draggableRef);
1613
+ useEffect5(() => {
1614
+ if (!publishableKey) return;
1615
+ fetch(`${baseUrl}/api/workflows`, {
1616
+ headers: { Authorization: `Bearer ${publishableKey}` }
1617
+ }).then((r) => r.json()).then((data) => {
1618
+ if (data.workflows) workflowsRef.current = data.workflows;
1619
+ }).catch((error2) => {
1620
+ onError?.(
1621
+ error2 instanceof Error ? error2.message : "Workflows fetch failed"
1622
+ );
1623
+ });
1624
+ }, [baseUrl, publishableKey]);
1625
+ useEffect5(() => {
1626
+ room.registerRpcMethod("workflow:execute", async (rpcData) => {
1627
+ try {
1628
+ const { slug } = JSON.parse(rpcData.payload);
1629
+ const apiWf = workflowsRef.current.find((w) => w.slug === slug);
1630
+ if (!apiWf) throw new Error(`Workflow '${slug}' not found`);
1631
+ const workflow = {
1632
+ id: apiWf.id,
1633
+ name: apiWf.name,
1634
+ steps: apiWf.steps,
1635
+ configured: true,
1636
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1637
+ };
1638
+ setActiveWorkflow({ id: apiWf.id, workflow });
1639
+ triggerOutcomeBar();
1640
+ return JSON.stringify({ success: true });
1641
+ } catch (error2) {
1642
+ onError?.(
1643
+ error2 instanceof Error ? error2.message : "Workflow execution failed"
1644
+ );
1645
+ throw new RpcError2(
1646
+ 1,
1647
+ error2 instanceof Error ? error2.message : "Workflow execution failed"
1648
+ );
1649
+ }
1650
+ });
1651
+ return () => {
1652
+ room.unregisterRpcMethod("workflow:execute");
1653
+ };
1654
+ }, [room]);
1655
+ const { state } = useAgent();
1656
+ const { start } = useSessionContext3();
1657
+ useEffect5(() => {
1658
+ if (!autoStarted.current) {
1659
+ autoStarted.current = true;
1660
+ start();
1661
+ }
1662
+ }, [start]);
1663
+ const isConnecting = state === "connecting" || state === "initializing" || state === "pre-connect-buffering";
1664
+ const isDisconnected = state === "disconnected" || state === "failed";
1665
+ const { microphoneTrack, microphoneToggle } = useInputControls();
1666
+ const handleDisconnect = () => {
1667
+ onDisconnect();
1668
+ };
1669
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1670
+ activeWorkflow && /* @__PURE__ */ jsx13(
1671
+ WorkflowExecution,
1672
+ {
1673
+ workflow: activeWorkflow.workflow,
1674
+ cursor,
1675
+ onClose: () => setActiveWorkflow(null),
1676
+ onError
1677
+ }
1678
+ ),
1679
+ /* @__PURE__ */ jsxs8(
1680
+ "div",
1681
+ {
1682
+ ref: draggableRef,
1683
+ "data-meridial-ui": true,
1684
+ className: cn(
1685
+ "fixed z-50 w-96 items-stretch rounded border border-border bg-card shadow-md",
1686
+ BAR_POSITION_CLASSES[barPosition]
1687
+ ),
1688
+ children: [
1689
+ /* @__PURE__ */ jsx13(AnimatePresence3, { initial: false, children: showTranscript && /* @__PURE__ */ jsx13(
1690
+ motion3.div,
1691
+ {
1692
+ initial: { height: 0, opacity: 0 },
1693
+ animate: { height: "auto", opacity: 1 },
1694
+ exit: { height: 0, opacity: 0 },
1695
+ transition: { duration: 0.24, ease: "easeOut" },
1696
+ style: { overflow: "hidden" },
1697
+ children: /* @__PURE__ */ jsx13(AgentTranscriptPanel, {})
1698
+ }
1699
+ ) }),
1700
+ /* @__PURE__ */ jsx13(AnimatePresence3, { initial: false, children: showOutcomeBar && /* @__PURE__ */ jsx13(
1701
+ motion3.div,
1702
+ {
1703
+ initial: { height: 0, opacity: 0 },
1704
+ animate: { height: "auto", opacity: 1 },
1705
+ exit: { height: 0, opacity: 0 },
1706
+ transition: { duration: 0.24, ease: "easeOut" },
1707
+ style: { overflow: "hidden" },
1708
+ children: /* @__PURE__ */ jsx13(OutcomeBar, { onSubmit: handleOutcomeSubmit })
1709
+ }
1710
+ ) }),
1711
+ /* @__PURE__ */ jsxs8("div", { "data-meridial-ui": true, className: "flex h-12 items-center", children: [
1712
+ /* @__PURE__ */ jsx13(DragHandle, { className: "px-2" }),
1713
+ /* @__PURE__ */ jsx13(Separator, { orientation: "vertical", className: "h-full" }),
1714
+ /* @__PURE__ */ jsxs8("div", { className: "relative flex h-full flex-1 items-center justify-between px-2", children: [
1715
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1716
+ /* @__PURE__ */ jsx13(
1717
+ AgentTrackControl,
1718
+ {
1719
+ source: "microphone",
1720
+ pressed: microphoneToggle.enabled,
1721
+ pending: microphoneToggle.pending,
1722
+ disabled: microphoneToggle.pending,
1723
+ audioTrack: microphoneTrack,
1724
+ onPressedChange: microphoneToggle.toggle
1725
+ }
1726
+ ),
1727
+ /* @__PURE__ */ jsx13(
1728
+ Toggle3,
1729
+ {
1730
+ disabled: isDisconnected || isConnecting,
1731
+ pressed: showTranscript,
1732
+ "aria-label": "Toggle transcript",
1733
+ onPressedChange: setShowTranscript,
1734
+ className: cn(
1735
+ "size-9 rounded-lg",
1736
+ showTranscript ? "bg-muted text-foreground" : "bg-transparent text-muted-foreground hover:text-foreground"
1737
+ ),
1738
+ children: /* @__PURE__ */ jsx13(HugeiconsIcon7, { icon: ChatFeedback01Icon })
1739
+ }
1740
+ ),
1741
+ /* @__PURE__ */ jsx13(
1742
+ AgentAudioVisualizerBar,
1743
+ {
1744
+ barCount: 12,
1745
+ state,
1746
+ audioTrack: microphoneTrack,
1747
+ className: "mx-4"
1748
+ }
1749
+ )
1750
+ ] }),
1751
+ isConnecting ? /* @__PURE__ */ jsxs8(Button, { variant: "outline", children: [
1752
+ /* @__PURE__ */ jsx13("span", { children: "Connecting" }),
1753
+ /* @__PURE__ */ jsx13(HugeiconsIcon7, { icon: Loading03Icon3, className: "animate-spin" })
1754
+ ] }) : /* @__PURE__ */ jsx13(AgentDisconnectButton, { onClick: handleDisconnect }),
1755
+ /* @__PURE__ */ jsx13(StartAudioButton, { label: "Start Audio" })
1756
+ ] })
1757
+ ] })
1758
+ ]
1759
+ }
1760
+ )
1761
+ ] });
1762
+ }
1763
+ function Voicebox({
1764
+ baseUrl = "",
1765
+ publishableKey,
1766
+ identifier,
1767
+ tools,
1768
+ triggerIcon,
1769
+ cursor,
1770
+ firstMessage,
1771
+ instructions,
1772
+ metadata,
1773
+ onError
1774
+ }) {
1775
+ const [view, setView] = useState9("badge");
1776
+ const [hidden, setHidden] = useState9(false);
1777
+ const [badgePosition, setBadgePosition] = useState9(() => {
1778
+ if (typeof window === "undefined") return "bottom-right";
1779
+ return localStorage.getItem(STORAGE_KEYS.triggerPos) || "bottom-right";
1780
+ });
1781
+ const [favoriteWorkflows, setFavoriteWorkflows] = useState9([]);
1782
+ const [initialWorkflow, setInitialWorkflow] = useState9(null);
1783
+ const [guideWorkflow, setGuideWorkflow] = useState9(null);
1784
+ useEffect5(() => {
1785
+ if (sessionStorage.getItem(STORAGE_KEYS.hidden) === "true") {
1786
+ setHidden(true);
1787
+ }
1788
+ }, []);
1789
+ useEffect5(() => {
1790
+ if (!publishableKey) return;
1791
+ fetch(`${baseUrl}/api/workflows`, {
1792
+ headers: { Authorization: `Bearer ${publishableKey}` }
1793
+ }).then((r) => r.json()).then((data) => {
1794
+ const parsed = apiWorkflowsResponseSchema.safeParse(data);
1795
+ if (parsed.success && parsed.data.workflows) {
1796
+ setFavoriteWorkflows(
1797
+ parsed.data.workflows.filter((w) => w.isFavorite)
1798
+ );
1799
+ }
1800
+ }).catch(() => {
1801
+ });
1802
+ }, [baseUrl, publishableKey]);
1803
+ const serializedTools = useMemo5(
1804
+ () => tools && tools.length > 0 ? serializeTools(tools) : [],
1805
+ [tools]
1806
+ );
1807
+ const tokenSource = useMemo5(
1808
+ () => TokenSource.custom(async () => {
1809
+ const response = await fetch(`${baseUrl}/api/auth/livekit`, {
1810
+ method: "POST",
1811
+ body: JSON.stringify({
1812
+ publishableKey,
1813
+ identifier,
1814
+ firstMessage,
1815
+ instructions,
1816
+ tools: serializedTools,
1817
+ metadata
1818
+ })
1819
+ });
1820
+ if (!response.ok) {
1821
+ const body = await response.json().catch(() => ({}));
1822
+ onError?.(body.error ?? `Token request failed (${response.status})`);
1823
+ return null;
1824
+ }
1825
+ return response.json();
1826
+ }),
1827
+ [
1828
+ baseUrl,
1829
+ publishableKey,
1830
+ identifier,
1831
+ firstMessage,
1832
+ instructions,
1833
+ serializedTools,
1834
+ metadata,
1835
+ onError
1836
+ ]
1837
+ );
1838
+ const session = useSession(tokenSource);
1839
+ const handleDisconnect = () => {
1840
+ setView("badge");
1841
+ setInitialWorkflow(null);
1842
+ };
1843
+ const handlePositionChange = (pos) => {
1844
+ setBadgePosition(pos);
1845
+ localStorage.setItem(STORAGE_KEYS.triggerPos, pos);
1846
+ };
1847
+ const handleHide = () => {
1848
+ sessionStorage.setItem(STORAGE_KEYS.hidden, "true");
1849
+ setHidden(true);
1850
+ setView("badge");
1851
+ };
1852
+ const handleAskMeridial = () => {
1853
+ setInitialWorkflow(null);
1854
+ setView("call");
1855
+ };
1856
+ const handlePlayGuide = (wf) => {
1857
+ setGuideWorkflow({
1858
+ id: wf.id,
1859
+ name: wf.name,
1860
+ steps: wf.steps,
1861
+ configured: true,
1862
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1863
+ });
1864
+ setView("guide");
1865
+ };
1866
+ const handleBadgeClick = () => {
1867
+ setView((prev) => prev === "menu" ? "badge" : "menu");
1868
+ };
1869
+ if (hidden) return null;
1870
+ return /* @__PURE__ */ jsx13(AgentSessionProvider, { session, children: view === "call" ? /* @__PURE__ */ jsx13(
1871
+ VoiceboxContent,
1872
+ {
1873
+ baseUrl,
1874
+ publishableKey,
1875
+ tools,
1876
+ cursor,
1877
+ barPosition: badgePosition,
1878
+ initialWorkflow,
1879
+ onDisconnect: handleDisconnect,
1880
+ onError
1881
+ }
1882
+ ) : view === "guide" && guideWorkflow ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
1883
+ /* @__PURE__ */ jsx13(
1884
+ WorkflowExecution,
1885
+ {
1886
+ workflow: guideWorkflow,
1887
+ cursor,
1888
+ onClose: () => {
1889
+ setGuideWorkflow(null);
1890
+ setView("badge");
1891
+ },
1892
+ onError
1893
+ }
1894
+ ),
1895
+ /* @__PURE__ */ jsx13(
1896
+ "div",
1897
+ {
1898
+ className: cn("fixed z-50", BADGE_POSITION_CLASSES[badgePosition]),
1899
+ children: /* @__PURE__ */ jsx13(
1900
+ VoiceboxBadge,
1901
+ {
1902
+ triggerIcon,
1903
+ onClick: handleBadgeClick
1904
+ }
1905
+ )
1906
+ }
1907
+ )
1908
+ ] }) : /* @__PURE__ */ jsxs8(
1909
+ "div",
1910
+ {
1911
+ className: cn("fixed z-50", BADGE_POSITION_CLASSES[badgePosition]),
1912
+ children: [
1913
+ /* @__PURE__ */ jsx13(AnimatePresence3, { children: view === "menu" && /* @__PURE__ */ jsx13(
1914
+ VoiceboxMenu,
1915
+ {
1916
+ favoriteWorkflows,
1917
+ position: badgePosition,
1918
+ onPositionChange: handlePositionChange,
1919
+ onHide: handleHide,
1920
+ onAskMeridial: handleAskMeridial,
1921
+ onPlayGuide: handlePlayGuide
1922
+ }
1923
+ ) }),
1924
+ /* @__PURE__ */ jsx13(VoiceboxBadge, { triggerIcon, onClick: handleBadgeClick })
1925
+ ]
1926
+ }
1927
+ ) });
1928
+ }
1929
+
1930
+ export {
1931
+ Voicebox
1932
+ };
1933
+ //# sourceMappingURL=chunk-WCRZUGN4.js.map