@livelayer/react 0.10.7 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -22
- package/dist/index.d.ts +105 -41
- package/dist/index.js +3 -3
- package/dist/index.mjs +1571 -1452
- package/package.json +23 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fusion Studios Code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -48,6 +48,67 @@ That's it. The widget docks bottom-right, the agent can navigate users to other
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
+
## Structured data collection (0.12.0)
|
|
52
|
+
|
|
53
|
+
The agent can run guided, typed Q&A flows backed by LiveKit's [`voice.AgentTask`](https://docs.livekit.io/agents/logic/tasks/) + `beta.workflows.TaskGroup` primitives. **No new components, no special attributes — just write regular HTML forms.** Every `<form>` is auto-discovered.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { AvatarWidget } from "@livelayer/react";
|
|
57
|
+
|
|
58
|
+
function App() {
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<AvatarWidget
|
|
62
|
+
agentId="agent_abc"
|
|
63
|
+
onCollect={(result) => {
|
|
64
|
+
// result.results keyed by field name — ship to your CRM.
|
|
65
|
+
fetch("/api/leads", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: JSON.stringify(result),
|
|
68
|
+
});
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
{/* Plain HTML — the agent finds this. */}
|
|
73
|
+
<form>
|
|
74
|
+
<label>Email <input name="email" type="email" required /></label>
|
|
75
|
+
<label>Company <input name="company" /></label>
|
|
76
|
+
<button type="submit">Subscribe</button>
|
|
77
|
+
</form>
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The agent infers each field's label from the wrapping `<label>` / `aria-label` / `placeholder`, infers the kind from `type=`, runs a TaskGroup when the visitor wants to fill the form by voice, normalizes each spoken answer per-kind, and paints values into the matching `[name="..."]` inputs live as it records each one. `onCollect` fires once with the typed payload when the run finishes.
|
|
84
|
+
|
|
85
|
+
**Streaming per-field updates** for a progress UI:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { useCollect } from "@livelayer/react";
|
|
89
|
+
|
|
90
|
+
function Progress() {
|
|
91
|
+
const { fields, isCollecting, lastResult } = useCollect();
|
|
92
|
+
return Object.entries(fields).map(([name, value]) => (
|
|
93
|
+
<div key={name}>{name}: {value}</div>
|
|
94
|
+
));
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Opt-out** when you don't want a form / input visible to the agent:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<form data-ll-skip>...</form> // exclude the whole form
|
|
102
|
+
<input data-ll-private /> // exclude one input
|
|
103
|
+
<form data-ll-intent="request a demo"> // disambiguate (still visible)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`type="password"`, `autocomplete="cc-*"`, and `autocomplete="off"` are ALWAYS excluded — you don't have to mark them.
|
|
107
|
+
|
|
108
|
+
See [docs.livelayer.studio/develop/data-collection](https://docs.livelayer.studio/develop/data-collection) for the full result shape, dashboard-declared field lists, slide-level data collection, capability gating, and webhook delivery.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
51
112
|
## Recipes
|
|
52
113
|
|
|
53
114
|
### 1. Voice navigation in Next.js / React Router
|
|
@@ -167,36 +228,25 @@ The agent emits `{ type: "click", selector: "[data-ll-action='open-pricing-modal
|
|
|
167
228
|
|
|
168
229
|
**Page scrolling**: the agent can call `scroll_page` with `direction: "up" | "down" | "top" | "bottom"`. Default behavior scrolls the window by ±1 viewport height. Override with `onScrollPage` for custom scroll containers.
|
|
169
230
|
|
|
170
|
-
**Forms** —
|
|
231
|
+
**Forms** — auto-discovered. Just write regular HTML:
|
|
171
232
|
|
|
172
233
|
```tsx
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
<
|
|
177
|
-
<LiveLayerField name="email" label="Email" type="email" />
|
|
178
|
-
<LiveLayerField name="message" as="textarea" label="Message" />
|
|
179
|
-
<button type="submit">Send</button>
|
|
180
|
-
</LiveLayerForm>
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Or use raw HTML with `data-ll-form` / `data-ll-field`:
|
|
184
|
-
|
|
185
|
-
```html
|
|
186
|
-
<form data-ll-form="contact" data-ll-intent="contact us">
|
|
187
|
-
<input data-ll-field="name" name="name" />
|
|
188
|
-
<input data-ll-field="email" name="email" type="email" />
|
|
189
|
-
<textarea data-ll-field="message" name="message"></textarea>
|
|
234
|
+
<form onSubmit={handleSubmit}>
|
|
235
|
+
<label>Name <input name="name" /></label>
|
|
236
|
+
<label>Email <input name="email" type="email" /></label>
|
|
237
|
+
<label>Message <textarea name="message" /></label>
|
|
190
238
|
<button type="submit">Send</button>
|
|
191
239
|
</form>
|
|
192
240
|
```
|
|
193
241
|
|
|
194
242
|
The agent sees these in `PageContext.forms` and calls:
|
|
195
|
-
- `fill_form` — sets values via the
|
|
196
|
-
- `
|
|
197
|
-
- `submit_form` — calls `form.requestSubmit()`.
|
|
243
|
+
- `fill_form` — sets values via the canonical native-setter pattern (your `onChange` listeners fire correctly). Use when the agent already has all the answers.
|
|
244
|
+
- `collect_from_page` — runs a guided sub-conversation that asks for each field one at a time, normalizes spoken input per kind (email letter-by-letter, phone digit grouping, etc), and delivers a typed `onCollect` payload. Use when the visitor wants to fill the form by voice. See [Structured data collection](#structured-data-collection-0120) above.
|
|
245
|
+
- `submit_form` — calls `form.requestSubmit()`. Publishes `{ type: "form_submitted", formId }` on success or `{ type: "form_submit_blocked", formId, reason: "validation" }` on HTML5 validation failure.
|
|
246
|
+
|
|
247
|
+
Form IDs are inferred from the form's existing `id` / `name` attribute, falling back to a `data-ll-intent` slug, finally `form_<index>`.
|
|
198
248
|
|
|
199
|
-
**
|
|
249
|
+
**Opt-out for privacy**: `<form data-ll-skip>...</form>` and `<input data-ll-private />` keep things out of the agent's view. `type="password"`, `autocomplete="cc-*"`, and `autocomplete="off"` are ALWAYS excluded — card fields belong in Stripe Elements; we will not be the rail.
|
|
200
250
|
|
|
201
251
|
**Routes**: the agent can call `request_routes` to get up to 200 deduped `<a href>` entries from the page (internal flagged separately from external). Useful for "where can I go?" prompts.
|
|
202
252
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,17 +6,13 @@ import { CSSProperties } from 'react';
|
|
|
6
6
|
import { ElementType } from 'react';
|
|
7
7
|
import { ErrorInfo } from 'react';
|
|
8
8
|
import { FC } from 'react';
|
|
9
|
-
import { FormHTMLAttributes } from 'react';
|
|
10
9
|
import { ForwardRefExoticComponent } from 'react';
|
|
11
|
-
import { InputHTMLAttributes } from 'react';
|
|
12
10
|
import { JSX } from 'react/jsx-runtime';
|
|
13
11
|
import { LiveKitSession } from '@livelayer/sdk';
|
|
14
12
|
import { ReactNode } from 'react';
|
|
15
13
|
import { RefAttributes } from 'react';
|
|
16
14
|
import { Room } from 'livekit-client';
|
|
17
|
-
import { SelectHTMLAttributes } from 'react';
|
|
18
15
|
import { SessionOptions } from '@livelayer/sdk';
|
|
19
|
-
import { TextareaHTMLAttributes } from 'react';
|
|
20
16
|
import { TranscriptEntry } from '@livelayer/sdk';
|
|
21
17
|
|
|
22
18
|
/**
|
|
@@ -33,7 +29,13 @@ import { TranscriptEntry } from '@livelayer/sdk';
|
|
|
33
29
|
*
|
|
34
30
|
* Default (undefined): everything enabled (matches 0.3.x behavior).
|
|
35
31
|
*/
|
|
36
|
-
export declare type AgentCapability = "navigate" | "scroll" | "click" | "fill_forms" | "submit_forms" | "read_page"
|
|
32
|
+
export declare type AgentCapability = "navigate" | "scroll" | "click" | "fill_forms" | "submit_forms" | "read_page"
|
|
33
|
+
/**
|
|
34
|
+
* 0.11.0 — LiveKit Agent Tasks structured collection (page-form,
|
|
35
|
+
* agent-declared field list, slide form). The detailed result types
|
|
36
|
+
* live in `./hooks/useCollect` to keep co-located with the consumer.
|
|
37
|
+
*/
|
|
38
|
+
| "collect_data";
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Agent commands streamed over the LiveKit data channel. The base package
|
|
@@ -326,6 +328,47 @@ export declare interface AvatarWidgetProps {
|
|
|
326
328
|
onConnectionStateChange?: (state: ConnectionState) => void;
|
|
327
329
|
onAgentEvent?: (e: AgentEventDetail) => void;
|
|
328
330
|
onAgentCommand?: (cmd: AgentCommand) => void;
|
|
331
|
+
/**
|
|
332
|
+
* Fires once when the agent finishes a structured collection run.
|
|
333
|
+
* One callback covers every collection surface:
|
|
334
|
+
*
|
|
335
|
+
* - `source: "page"` — agent walked the visitor through an
|
|
336
|
+
* on-page <form> (auto-discovered). The
|
|
337
|
+
* values already painted into the matching
|
|
338
|
+
* inputs live as they were recorded.
|
|
339
|
+
* - `source: "agent"` — dashboard-declared field list (Behavior →
|
|
340
|
+
* Data collection in the agent editor).
|
|
341
|
+
* - `source: "slide"` — slide-level form_fields (slide editor's
|
|
342
|
+
* Data collection toggle).
|
|
343
|
+
*
|
|
344
|
+
* Typical use: ship the typed payload to your backend.
|
|
345
|
+
*
|
|
346
|
+
* <AvatarWidget
|
|
347
|
+
* agentId="..."
|
|
348
|
+
* onCollect={(r) => fetch("/api/leads", { method: "POST",
|
|
349
|
+
* body: JSON.stringify(r) })}
|
|
350
|
+
* />
|
|
351
|
+
*
|
|
352
|
+
* For streaming per-field updates use the `useCollect()` hook
|
|
353
|
+
* instead. Don't write to `<input value=...>` from inside this
|
|
354
|
+
* callback — the SDK already auto-painted matching inputs by their
|
|
355
|
+
* `name` attribute before this fires.
|
|
356
|
+
*/
|
|
357
|
+
onCollect?: (result: {
|
|
358
|
+
sessionId: string;
|
|
359
|
+
startedAt: string;
|
|
360
|
+
endedAt: string;
|
|
361
|
+
source: "agent" | "slide" | "page";
|
|
362
|
+
slideId?: string;
|
|
363
|
+
formId?: string;
|
|
364
|
+
results: Record<string, {
|
|
365
|
+
fieldId: string;
|
|
366
|
+
fieldName: string;
|
|
367
|
+
value: string;
|
|
368
|
+
kind: string;
|
|
369
|
+
}>;
|
|
370
|
+
summary?: string;
|
|
371
|
+
}) => void;
|
|
329
372
|
/**
|
|
330
373
|
* When provided, the widget does not create its own LiveKit session.
|
|
331
374
|
* The consumer owns connection lifecycle. Use for cross-page
|
|
@@ -368,13 +411,25 @@ export declare function clearPageContextCache(): void;
|
|
|
368
411
|
|
|
369
412
|
export declare function clearRoutesCache(): void;
|
|
370
413
|
|
|
371
|
-
declare interface
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
414
|
+
export declare interface CollectedField {
|
|
415
|
+
fieldId: string;
|
|
416
|
+
fieldName: string;
|
|
417
|
+
value: string;
|
|
418
|
+
kind: string;
|
|
419
|
+
source: "agent" | "slide" | "page";
|
|
420
|
+
slideId?: string;
|
|
421
|
+
formId?: string;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export declare interface CollectedResult {
|
|
425
|
+
sessionId: string;
|
|
426
|
+
startedAt: string;
|
|
427
|
+
endedAt: string;
|
|
428
|
+
source: "agent" | "slide" | "page";
|
|
429
|
+
slideId?: string;
|
|
430
|
+
formId?: string;
|
|
431
|
+
results: Record<string, CollectedField>;
|
|
432
|
+
summary?: string;
|
|
378
433
|
}
|
|
379
434
|
|
|
380
435
|
export { ConnectionState }
|
|
@@ -472,8 +527,6 @@ export declare function extractPageContext(extras?: Record<string, unknown>, opt
|
|
|
472
527
|
|
|
473
528
|
export declare function extractRoutes(doc?: Document): ExtractedRoute[];
|
|
474
529
|
|
|
475
|
-
declare type FieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
476
|
-
|
|
477
530
|
export declare function getCachedPageContext(extras?: Record<string, unknown>, opts?: ExtractOptions): PageContext;
|
|
478
531
|
|
|
479
532
|
export declare function getCachedRoutes(): ExtractedRoute[];
|
|
@@ -500,30 +553,6 @@ export declare interface LiveLayerDebugPanelProps {
|
|
|
500
553
|
storageKey?: string;
|
|
501
554
|
}
|
|
502
555
|
|
|
503
|
-
export declare const LiveLayerField: ForwardRefExoticComponent<LiveLayerFieldProps & RefAttributes<FieldElement>>;
|
|
504
|
-
|
|
505
|
-
export declare type LiveLayerFieldProps = (CommonFieldProps & {
|
|
506
|
-
as?: "input";
|
|
507
|
-
} & Omit<InputHTMLAttributes<HTMLInputElement>, "name">) | (CommonFieldProps & {
|
|
508
|
-
as: "textarea";
|
|
509
|
-
} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "name">) | (CommonFieldProps & {
|
|
510
|
-
as: "select";
|
|
511
|
-
children: ReactNode;
|
|
512
|
-
} & Omit<SelectHTMLAttributes<HTMLSelectElement>, "name">);
|
|
513
|
-
|
|
514
|
-
export declare const LiveLayerForm: ForwardRefExoticComponent<LiveLayerFormProps & RefAttributes<HTMLFormElement>>;
|
|
515
|
-
|
|
516
|
-
export declare interface LiveLayerFormProps extends FormHTMLAttributes<HTMLFormElement> {
|
|
517
|
-
/** Stable identifier for the form. Becomes `data-ll-form`. */
|
|
518
|
-
id: string;
|
|
519
|
-
/**
|
|
520
|
-
* One-line description the agent sees. e.g. "create account",
|
|
521
|
-
* "request a demo". The agent uses this to decide WHEN to fill the
|
|
522
|
-
* form (matching user intent → form intent).
|
|
523
|
-
*/
|
|
524
|
-
intent?: string;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
556
|
export declare const LiveLayerRegion: ForwardRefExoticComponent<LiveLayerRegionProps & RefAttributes<HTMLElement>>;
|
|
528
557
|
|
|
529
558
|
export declare interface LiveLayerRegionProps {
|
|
@@ -669,9 +698,10 @@ export declare interface PageContext {
|
|
|
669
698
|
type: string;
|
|
670
699
|
}>;
|
|
671
700
|
/**
|
|
672
|
-
*
|
|
673
|
-
* uses these to call `fill_form
|
|
674
|
-
*
|
|
701
|
+
* Every <form> on the page is auto-discovered (0.12.0). The agent
|
|
702
|
+
* uses these entries to call `fill_form`, `submit_form`, or
|
|
703
|
+
* `collect_from_page`. Values NEVER included. Forms opted out via
|
|
704
|
+
* `data-ll-skip` or inside a `data-ll-private` subtree are absent.
|
|
675
705
|
*/
|
|
676
706
|
forms: Array<{
|
|
677
707
|
id: string;
|
|
@@ -803,6 +833,40 @@ export declare function useAudioLevel(): AudioLevelHandle;
|
|
|
803
833
|
|
|
804
834
|
export declare function useCameraState(): CameraStateHandle;
|
|
805
835
|
|
|
836
|
+
export declare function useCollect(options?: UseCollectOptions): UseCollectHandle;
|
|
837
|
+
|
|
838
|
+
export declare interface UseCollectHandle {
|
|
839
|
+
/** Field name → most-recent value. Resets when a new run starts. */
|
|
840
|
+
fields: Record<string, string>;
|
|
841
|
+
/** True between the first field update and the completion event. */
|
|
842
|
+
isCollecting: boolean;
|
|
843
|
+
/** Most recent completed result; null until at least one run finishes. */
|
|
844
|
+
lastResult: CollectedResult | null;
|
|
845
|
+
/** Clear the running snapshot manually (e.g. on slide change). */
|
|
846
|
+
reset: () => void;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export declare interface UseCollectOptions {
|
|
850
|
+
/**
|
|
851
|
+
* Fires for every field as it's recorded. Use for live progress UI.
|
|
852
|
+
* Omit if you only care about the final payload — that's what
|
|
853
|
+
* `<AvatarWidget onCollect />` is for.
|
|
854
|
+
*/
|
|
855
|
+
onFieldUpdate?: (update: CollectedField) => void;
|
|
856
|
+
/**
|
|
857
|
+
* Fires once when a collection run finishes. Same payload the
|
|
858
|
+
* `<AvatarWidget onCollect />` prop receives — they're equivalent.
|
|
859
|
+
*/
|
|
860
|
+
onComplete?: (result: CollectedResult) => void;
|
|
861
|
+
/**
|
|
862
|
+
* Restrict to one source. `"page"` ignores slide / agent flows;
|
|
863
|
+
* `"slide"` ignores page-form / agent-level flows; `"agent"`
|
|
864
|
+
* ignores everything except dashboard-declared field lists. Default
|
|
865
|
+
* `"all"` listens to every source.
|
|
866
|
+
*/
|
|
867
|
+
source?: "all" | "agent" | "slide" | "page";
|
|
868
|
+
}
|
|
869
|
+
|
|
806
870
|
export declare function useDisplayMode({ value, defaultValue, onChange, }?: Options): [DisplayMode_2, (next: DisplayMode_2) => void];
|
|
807
871
|
|
|
808
872
|
export declare function useDisplayModePersistence({ value, defaultValue, onChange, persistKey, disablePersistence, }?: Options_2): [DisplayMode_2, (next: DisplayMode_2) => void];
|