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), without duplicating anything the platform already tracks server-side.
What not to report (read this first)
The backend (the session server and the brain) already reports its own server-side KAVA events for every session a KalturaAvatarSession connects to. These are the 80000-range "Immersive Agents" events: callStarted, callEnded, messageResponse (message delivery), and messageFeedbackSent (feedback).
KavaAnalytics has no code path that can send any of these (see the module docblock in src/experience/analytics.js). That is deliberate, not a gap to fill. Do not build client-side reporting for:
| Already server-tracked (80000-range) | Don't re-report client-side 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 GenUI widget rendering on screen isn't itself one of those signals — the widget's arrival rides the same message-delivery event the server already counted. What's safe to report (because it has no server-side equivalent) is the client-only choice the learner makes on that widget. That could be 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 with no server-side equivalent:
'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 — 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 |