Widget-interaction analytics (avoiding double-counting)
A recipe for reporting GenUI widget interactions (which widget the learner acted on, what they picked) to KAVA via KavaAnalytics.buttonClicked() (./experience/analytics).
What to report
KavaAnalytics sends two client-side Application Events only: pageLoad and buttonClicked (see the module docblock in src/experience/analytics.js). It has no code path for call, message-delivery or feedback events, so don't build client-side reporting for these:
| Signal | Don't report it as... |
|---|---|
| A message was delivered to the user | A buttonClicked/pageLoad for "message shown" |
The user thumbs-up/down'd a reply (mgmt.feedback.add) |
A buttonClicked for "feedback given" |
| A call/session started or ended | A buttonClicked/pageLoad for "session start/end" |
A widget rendering on screen is a message arrival, not a separate signal. Report the client-only choice the learner makes on that widget: which chip they clicked, which link they opened, or which answer they picked. That choice is the only thing this recipe reports.
The recipe: two widget interaction types, two distinguishable events
Wire each widget's onAction intent (see GenUI · Authoring and Consuming Widgets § onAction, WIDGET_KINDS, and the hand-rolled escape hatch above) straight into one buttonClicked() call. Construct one KavaAnalytics per page/session (same pattern as README's KAVA analytics section) and call it from the same onAction handler you already pass to ExperienceRenderer:
import { ExperienceRenderer } from '@kaltura/intelligent-agents/experience/genui';
import { KavaAnalytics } from '@kaltura/intelligent-agents/experience/analytics';
// AGENTIC_PARTNER_ID: your partner id (see the README's KAVA analytics section linked above)
// session: an active KalturaAvatarSession from your app
const analytics = new KavaAnalytics({
partnerId: AGENTIC_PARTNER_ID,
sessionId: session.threadId, // ties the event to this conversation without re-reporting the conversation itself
hostingKalturaApplication: 25, // HOSTING_APPLICATIONS.agents
});
new ExperienceRenderer({
session,
mount: document.getElementById('widgets'),
onAction(action, payload) {
if (action === 'followup') {
// Interaction type 1: a followups-tool suggested-question chip was clicked.
analytics.buttonClicked({
buttonType: 'Select',
buttonName: 'genui-followup-chip',
buttonValue: payload.question, // which chip, makes this event distinguishable per question
buttonInfo: 'GenUI followups widget, suggested-question chip clicked',
});
session.speak(payload.question);
} else if (action === 'open') {
// Interaction type 2: a show-link-tool (or sources/content-gallery) link card was opened.
analytics.buttonClicked({
buttonType: 'Open',
buttonName: 'genui-show-link-card',
buttonValue: payload.url,
buttonInfo: 'GenUI show-link widget, link card opened',
});
}
},
}).start();
Two rules keep the two events distinguishable and non-duplicated:
- A different
buttonNameper widget/interaction type (genui-followup-chipvs.genui-show-link-card), this is what a KAVA dashboard groups and filters on. Don't reuse one generic name across widget kinds. buttonValuecarries the specific choice (the exact question text, the exact URL) rather than a boolean or the widget kind, the kind already lives inbuttonName. Two clicks on two different chips inside the SAMEfollowupswidget still produce two distinct, non-duplicate rows, because each carries a differentbuttonValue.
Apply the same two-line pattern to any other onAction intent:
'play'({entryId, url, embedUrl}): a video-gallery clip was opened.'submit'({values}): auser-properties-formwas submitted. Report only that it happened and which fields were filled, not the raw values if they're personal data. See Structured Data Forms for where that data durably belongs instead.
Why the two events stay distinct
followups-tool and show-link-tool (the second requires enabling the show_link capability, which is off by default, see GenUI · Authoring and Consuming Widgets) can arrive in the same turn. Run through the two buttonClicked() calls above, the resulting payloads share the same partnerId/sessionId (same conversation), but differ in buttonName/buttonType/buttonValue. That makes them two distinguishable, non-duplicated events tied to one conversation, not two copies of the same one. To test this without landing rows on your live KAVA data, pass an injected transport to KavaAnalytics.
Related docs
| Doc | Covers |
|---|---|
| GenUI · Authoring and Consuming Widgets | ExperienceRenderer's onAction contract this recipe wires into |
| GenUI · Per-Runtime Widget Detail | Per-runtime model keys, constraints, and descriptor shapes |
| GenUI Reference | Back to the index |