@ariaflowagents/analytics-sdk 0.9.1
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/README.md +313 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +10 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +11 -0
- package/package.json +43 -0
- package/src/index.ts +284 -0
- package/src/react.ts +282 -0
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# @ariaflowagents/analytics-sdk
|
|
2
|
+
|
|
3
|
+
Type-safe SDK for sending analytics events to AriaFlow Analytics Platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ariaflowagents/analytics-sdk
|
|
9
|
+
# or
|
|
10
|
+
bun add @ariaflowagents/analytics-sdk
|
|
11
|
+
# or
|
|
12
|
+
yarn add @ariaflowagents/analytics-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Initialize the Client
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createAnalyticsClient } from '@ariaflowagents/analytics-sdk';
|
|
21
|
+
|
|
22
|
+
const analytics = createAnalyticsClient({
|
|
23
|
+
apiKey: 'your-api-key',
|
|
24
|
+
workspaceId: 'your-workspace-id',
|
|
25
|
+
endpoint: 'https://analytics.ariaflow.dev/api/v1', // optional
|
|
26
|
+
flushInterval: 5000, // optional, default: 5000ms
|
|
27
|
+
maxBatchSize: 20, // optional, default: 20
|
|
28
|
+
enableDebug: true, // optional, default: false
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Track Events
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Track a single event
|
|
36
|
+
await analytics.track({
|
|
37
|
+
sessionId: 'session-123',
|
|
38
|
+
agentId: 'hospital-agent',
|
|
39
|
+
workspaceId: 'workspace-456',
|
|
40
|
+
type: 'conversation.started',
|
|
41
|
+
data: { channel: 'web' }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Track batch events
|
|
45
|
+
await analytics.trackBatch([
|
|
46
|
+
{ sessionId: 'session-123', agentId: 'hospital-agent', workspaceId: 'workspace-456', type: 'node.entered', data: { nodeName: 'triage' } },
|
|
47
|
+
{ sessionId: 'session-123', agentId: 'hospital-agent', workspaceId: 'workspace-456', type: 'tool.called', data: { toolName: 'create_booking' } },
|
|
48
|
+
]);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Track Voice Calls
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Start a voice call
|
|
55
|
+
await analytics.trackVoiceCall({
|
|
56
|
+
sessionId: 'call-123',
|
|
57
|
+
workspaceId: 'workspace-456',
|
|
58
|
+
agentId: 'voice-agent',
|
|
59
|
+
userName: 'John Doe',
|
|
60
|
+
startedAt: new Date(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Update call metrics during the call
|
|
64
|
+
await analytics.updateVoiceCall('call-123', {
|
|
65
|
+
interruptions: 2,
|
|
66
|
+
userTurns: 5,
|
|
67
|
+
agentTurns: 4,
|
|
68
|
+
currentNode: 'booking_flow',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// End the call
|
|
72
|
+
await analytics.updateVoiceCall('call-123', {
|
|
73
|
+
endedAt: new Date(),
|
|
74
|
+
durationSeconds: 180,
|
|
75
|
+
outcome: 'booking_completed',
|
|
76
|
+
ttfMs: 850,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Set Context
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Set global context (applied to all events)
|
|
84
|
+
analytics.setContext({
|
|
85
|
+
userId: 'user-789',
|
|
86
|
+
conversationId: 'conv-abc',
|
|
87
|
+
agentId: 'hospital-agent',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Identify user
|
|
91
|
+
analytics.identify('user-789', {
|
|
92
|
+
name: 'John Doe',
|
|
93
|
+
email: 'john@example.com',
|
|
94
|
+
plan: 'pro',
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Flush Events
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Manually flush pending events
|
|
102
|
+
await analytics.flush();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## React Integration
|
|
106
|
+
|
|
107
|
+
### AnalyticsProvider
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { AnalyticsProvider } from '@ariaflowagents/analytics-sdk/react';
|
|
111
|
+
|
|
112
|
+
function App() {
|
|
113
|
+
return (
|
|
114
|
+
<AnalyticsProvider config={{
|
|
115
|
+
apiKey: process.env.ANALYTICS_API_KEY!,
|
|
116
|
+
workspaceId: 'workspace-456',
|
|
117
|
+
}}>
|
|
118
|
+
<YourApp />
|
|
119
|
+
</AnalyticsProvider>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### useAnalytics Hook
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { useAnalytics } from '@ariaflowagents/analytics-sdk/react';
|
|
128
|
+
|
|
129
|
+
function MyComponent() {
|
|
130
|
+
const { track, setContext, identify } = useAnalytics();
|
|
131
|
+
|
|
132
|
+
const handleClick = async () => {
|
|
133
|
+
await track({
|
|
134
|
+
type: 'custom',
|
|
135
|
+
sessionId: 'session-123',
|
|
136
|
+
agentId: 'hospital-agent',
|
|
137
|
+
workspaceId: 'workspace-456',
|
|
138
|
+
data: { button: 'submit_form' }
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return <button onClick={handleClick}>Submit</button>;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### usePageView Hook
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { usePageView } from '@ariaflowagents/analytics-sdk/react';
|
|
150
|
+
|
|
151
|
+
function DashboardPage() {
|
|
152
|
+
usePageView('dashboard', { section: 'analytics' });
|
|
153
|
+
|
|
154
|
+
return <div>Dashboard</div>;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### useVoiceCallTracker Hook
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { useVoiceCallTracker } from '@ariaflowagents/analytics-sdk/react';
|
|
162
|
+
|
|
163
|
+
function VoiceCallComponent({ sessionId, workspaceId }) {
|
|
164
|
+
const { startCall, endCall, trackInterruption, trackUserSpeech, trackAgentSpeech } =
|
|
165
|
+
useVoiceCallTracker(sessionId, workspaceId);
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
startCall('voice-agent');
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
endCall('completed');
|
|
172
|
+
};
|
|
173
|
+
}, []);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div>
|
|
177
|
+
<button onClick={trackInterruption}>User Interrupted</button>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Event Types
|
|
184
|
+
|
|
185
|
+
| Event Type | Description |
|
|
186
|
+
|------------|-------------|
|
|
187
|
+
| `conversation.started` | Conversation began |
|
|
188
|
+
| `conversation.ended` | Conversation ended |
|
|
189
|
+
| `node.entered` | Flow node entered |
|
|
190
|
+
| `node.exited` | Flow node exited |
|
|
191
|
+
| `tool.called` | Tool execution started |
|
|
192
|
+
| `tool.completed` | Tool finished successfully |
|
|
193
|
+
| `tool.error` | Tool execution failed |
|
|
194
|
+
| `booking.completed` | Booking action completed |
|
|
195
|
+
| `handoff.initiated` | Agent handoff triggered |
|
|
196
|
+
| `emergency.detected` | Emergency condition detected |
|
|
197
|
+
| `call.started` | Voice call started |
|
|
198
|
+
| `call.ended` | Voice call ended |
|
|
199
|
+
| `user.spoke` | User spoke (voice) |
|
|
200
|
+
| `agent.spoke` | Agent spoke (voice) |
|
|
201
|
+
| `user.interrupted` | User interrupted agent |
|
|
202
|
+
| `silence.detected` | Silence detected |
|
|
203
|
+
| `latency.stt` | Speech-to-text latency |
|
|
204
|
+
| `latency.ttf` | Time to first response |
|
|
205
|
+
| `latency.e2e` | End-to-end latency |
|
|
206
|
+
| `latency.tts` | Text-to-speech latency |
|
|
207
|
+
| `error.occurred` | Error occurred |
|
|
208
|
+
| `custom` | Custom event |
|
|
209
|
+
|
|
210
|
+
## Integration with AriaFlow Runtime
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { Runtime, createTelemetryHooks } from '@ariaflowagents/core';
|
|
214
|
+
import { createAnalyticsClient } from '@ariaflowagents/analytics-sdk';
|
|
215
|
+
|
|
216
|
+
const analytics = createAnalyticsClient({
|
|
217
|
+
apiKey: process.env.ANALYTICS_API_KEY!,
|
|
218
|
+
workspaceId: 'workspace-456',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Create telemetry hooks that forward events to analytics
|
|
222
|
+
const telemetryHooks = {
|
|
223
|
+
onAgentStart: async (context, agentId) => {
|
|
224
|
+
await analytics.track({
|
|
225
|
+
sessionId: context.session.id,
|
|
226
|
+
agentId,
|
|
227
|
+
workspaceId: 'workspace-456',
|
|
228
|
+
type: 'conversation.started',
|
|
229
|
+
data: {}
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
onToolCall: async (context, call) => {
|
|
233
|
+
await analytics.track({
|
|
234
|
+
sessionId: context.session.id,
|
|
235
|
+
agentId: context.agentId,
|
|
236
|
+
workspaceId: 'workspace-456',
|
|
237
|
+
type: 'tool.called',
|
|
238
|
+
data: {
|
|
239
|
+
toolName: call.toolName,
|
|
240
|
+
toolCallId: call.toolCallId,
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
onToolResult: async (context, call) => {
|
|
245
|
+
await analytics.track({
|
|
246
|
+
sessionId: context.session.id,
|
|
247
|
+
agentId: context.agentId,
|
|
248
|
+
workspaceId: 'workspace-456',
|
|
249
|
+
type: 'tool.completed',
|
|
250
|
+
data: {
|
|
251
|
+
toolName: call.toolName,
|
|
252
|
+
success: call.success,
|
|
253
|
+
durationMs: call.durationMs,
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
onHandoff: async (context, from, to, reason) => {
|
|
258
|
+
await analytics.track({
|
|
259
|
+
sessionId: context.session.id,
|
|
260
|
+
agentId: from,
|
|
261
|
+
workspaceId: 'workspace-456',
|
|
262
|
+
type: 'handoff.initiated',
|
|
263
|
+
data: { from, to, reason }
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const runtime = new Runtime({
|
|
269
|
+
agents: [...],
|
|
270
|
+
hooks: telemetryHooks,
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## API Reference
|
|
275
|
+
|
|
276
|
+
### `createAnalyticsClient(config: AnalyticsConfig): AnalyticsClient`
|
|
277
|
+
|
|
278
|
+
Creates a new analytics client instance.
|
|
279
|
+
|
|
280
|
+
### `AnalyticsClient` Methods
|
|
281
|
+
|
|
282
|
+
| Method | Description |
|
|
283
|
+
|--------|-------------|
|
|
284
|
+
| `track(event)` | Track a single event |
|
|
285
|
+
| `trackBatch(events)` | Track multiple events |
|
|
286
|
+
| `trackVoiceCall(data)` | Create a voice call record |
|
|
287
|
+
| `updateVoiceCall(sessionId, data)` | Update voice call metrics |
|
|
288
|
+
| `flush()` | Flush pending events |
|
|
289
|
+
| `setContext(context)` | Set global context |
|
|
290
|
+
| `identify(userId, traits?)` | Identify user |
|
|
291
|
+
|
|
292
|
+
### `AnalyticsConfig`
|
|
293
|
+
|
|
294
|
+
| Option | Type | Required | Default |
|
|
295
|
+
|--------|------|----------|---------|
|
|
296
|
+
| `apiKey` | `string` | ✅ | - |
|
|
297
|
+
| `workspaceId` | `string` | ✅ | - |
|
|
298
|
+
| `endpoint` | `string` | ❌ | `https://analytics.ariaflow.dev/api/v1` |
|
|
299
|
+
| `flushInterval` | `number` | ❌ | `5000` (ms) |
|
|
300
|
+
| `maxBatchSize` | `number` | ❌ | `20` |
|
|
301
|
+
| `enableDebug` | `boolean` | ❌ | `false` |
|
|
302
|
+
|
|
303
|
+
## Best Practices
|
|
304
|
+
|
|
305
|
+
1. **Initialize Once**: Create a single client instance and reuse it
|
|
306
|
+
2. **Batch Events**: The SDK automatically batches events for performance
|
|
307
|
+
3. **Context First**: Set context and user identity early in the session
|
|
308
|
+
4. **Flush on Exit**: Call `flush()` before page unload or app shutdown
|
|
309
|
+
5. **Error Handling**: Wrap calls in try-catch for production use
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
class s{queue=[];flushTimer=null;options;constructor(t){this.options=t,this.startFlushTimer()}add(t){if(this.queue.push(t),this.queue.length>=this.options.maxBatchSize)this.flush()}async flush(){if(this.queue.length===0)return;let t=[...this.queue];if(this.queue=[],this.options.enableDebug)console.log(`[Analytics] Flushing ${t.length} events`);try{await this.options.onFlush(t)}catch(e){console.error("[Analytics] Flush failed:",e),this.queue.unshift(...t)}}startFlushTimer(){this.flushTimer=setInterval(()=>{this.flush()},this.options.flushInterval)}destroy(){if(this.flushTimer)clearInterval(this.flushTimer),this.flushTimer=null;this.flush()}}class i{config;batcher;context;userId;userTraits;constructor(t){this.config={apiKey:t.apiKey,endpoint:t.endpoint??"https://analytics.ariaflow.dev/api/v1",workspaceId:t.workspaceId,flushInterval:t.flushInterval??5000,maxBatchSize:t.maxBatchSize??20,enableDebug:t.enableDebug??!1},this.context={workspaceId:this.config.workspaceId},this.batcher=new s({maxBatchSize:this.config.maxBatchSize,flushInterval:this.config.flushInterval,onFlush:this.sendEvents.bind(this),enableDebug:this.config.enableDebug})}async track(t){let e=this.enrichEvent(t);this.batcher.add(e)}async trackBatch(t){for(let e of t)await this.track(e)}async trackVoiceCall(t){if(this.config.enableDebug)console.log("[Analytics] Tracking voice call:",t.sessionId);let e=await fetch(`${this.config.endpoint}/voice-call`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(t)});if(!e.ok){let n=await e.text();throw Error(`Failed to track voice call: ${n}`)}}async updateVoiceCall(t,e){if(this.config.enableDebug)console.log("[Analytics] Updating voice call:",t);let n=await fetch(`${this.config.endpoint}/voice-call/${t}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(e)});if(!n.ok){let a=await n.text();throw Error(`Failed to update voice call: ${a}`)}}async flush(){await this.batcher.flush()}setContext(t){this.context={...this.context,...t}}identify(t,e){this.userId=t,this.userTraits=e}enrichEvent(t){return{...t,workspaceId:t.workspaceId??this.context.workspaceId,sessionId:t.sessionId??this.context.sessionId??"",agentId:t.agentId??this.context.agentId??"",conversationId:t.conversationId??this.context.conversationId,timestamp:t.timestamp??new Date,data:{...t.data,userId:t.data.userId??this.userId,userTraits:this.userTraits}}}async sendEvents(t){let e=await fetch(`${this.config.endpoint}/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({events:t})});if(!e.ok){let n=await e.text();throw Error(`Failed to send events: ${n}`)}}destroy(){this.batcher.destroy()}}function r(t){return new i(t)}var c=i;export{c as default,r as createAnalyticsClient,s as Batcher,i as AriaFlowAnalytics};
|
|
3
|
+
|
|
4
|
+
//# debugId=C025AF11B008174864756E2164756E21
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export interface AnalyticsEvent {\n id?: string;\n timestamp?: Date;\n sessionId: string;\n conversationId?: string;\n agentId: string;\n workspaceId: string;\n type: AnalyticsEventType;\n data: Record<string, unknown>;\n}\n\nexport type AnalyticsEventType =\n | \"conversation.started\"\n | \"conversation.ended\"\n | \"node.entered\"\n | \"node.exited\"\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.error\"\n | \"booking.completed\"\n | \"handoff.initiated\"\n | \"emergency.detected\"\n | \"call.started\"\n | \"call.ended\"\n | \"user.spoke\"\n | \"agent.spoke\"\n | \"user.interrupted\"\n | \"silence.detected\"\n | \"latency.stt\"\n | \"latency.ttf\"\n | \"latency.e2e\"\n | \"latency.tts\"\n | \"error.occurred\"\n | \"custom\";\n\nexport interface VoiceCallData {\n sessionId: string;\n workspaceId: string;\n agentId?: string;\n userName?: string;\n userId?: string;\n startedAt: Date;\n endedAt?: Date;\n durationSeconds?: number;\n userTurns?: number;\n agentTurns?: number;\n interruptions?: number;\n silenceEvents?: number;\n errors?: number;\n totalUserSpeechMs?: number;\n totalAgentSpeechMs?: number;\n totalSilenceMs?: number;\n ttfMs?: number;\n avgSttMs?: number;\n avgTtsMs?: number;\n e2eMs?: number;\n outcome?: string;\n outcomeData?: Record<string, unknown>;\n currentNode?: string;\n agentName?: string;\n transcript?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AnalyticsConfig {\n apiKey: string;\n endpoint?: string;\n workspaceId: string;\n flushInterval?: number;\n maxBatchSize?: number;\n enableDebug?: boolean;\n}\n\nexport interface AnalyticsClient {\n track: (event: AnalyticsEvent) => Promise<void>;\n trackBatch: (events: AnalyticsEvent[]) => Promise<void>;\n trackVoiceCall: (data: VoiceCallData) => Promise<void>;\n updateVoiceCall: (sessionId: string, data: Partial<VoiceCallData>) => Promise<void>;\n flush: () => Promise<void>;\n setContext: (context: Partial<AnalyticsContext>) => void;\n identify: (userId: string, traits?: Record<string, unknown>) => void;\n}\n\nexport interface AnalyticsContext {\n workspaceId: string;\n agentId?: string;\n sessionId?: string;\n userId?: string;\n conversationId?: string;\n}\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n flushInterval: number;\n onFlush: (events: AnalyticsEvent[]) => Promise<void>;\n enableDebug?: boolean;\n}\n\nexport class Batcher {\n private queue: AnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private options: BatcherOptions;\n\n constructor(options: BatcherOptions) {\n this.options = options;\n this.startFlushTimer();\n }\n\n add(event: AnalyticsEvent): void {\n this.queue.push(event);\n\n if (this.queue.length >= this.options.maxBatchSize) {\n this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const events = [...this.queue];\n this.queue = [];\n\n if (this.options.enableDebug) {\n console.log(`[Analytics] Flushing ${events.length} events`);\n }\n\n try {\n await this.options.onFlush(events);\n } catch (error) {\n console.error(\"[Analytics] Flush failed:\", error);\n this.queue.unshift(...events);\n }\n }\n\n private startFlushTimer(): void {\n this.flushTimer = setInterval(() => {\n this.flush();\n }, this.options.flushInterval);\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n this.flush();\n }\n}\n\nexport class AriaFlowAnalytics implements AnalyticsClient {\n private config: Required<AnalyticsConfig>;\n private batcher: Batcher;\n private context: AnalyticsContext;\n private userId?: string;\n private userTraits?: Record<string, unknown>;\n\n constructor(config: AnalyticsConfig) {\n this.config = {\n apiKey: config.apiKey,\n endpoint: config.endpoint ?? \"https://analytics.ariaflow.dev/api/v1\",\n workspaceId: config.workspaceId,\n flushInterval: config.flushInterval ?? 5000,\n maxBatchSize: config.maxBatchSize ?? 20,\n enableDebug: config.enableDebug ?? false,\n };\n\n this.context = {\n workspaceId: this.config.workspaceId,\n };\n\n this.batcher = new Batcher({\n maxBatchSize: this.config.maxBatchSize,\n flushInterval: this.config.flushInterval,\n onFlush: this.sendEvents.bind(this),\n enableDebug: this.config.enableDebug,\n });\n }\n\n async track(event: AnalyticsEvent): Promise<void> {\n const enrichedEvent = this.enrichEvent(event);\n this.batcher.add(enrichedEvent);\n }\n\n async trackBatch(events: AnalyticsEvent[]): Promise<void> {\n for (const event of events) {\n await this.track(event);\n }\n }\n\n async trackVoiceCall(data: VoiceCallData): Promise<void> {\n if (this.config.enableDebug) {\n console.log(\"[Analytics] Tracking voice call:\", data.sessionId);\n }\n\n const response = await fetch(`${this.config.endpoint}/voice-call`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to track voice call: ${error}`);\n }\n }\n\n async updateVoiceCall(sessionId: string, data: Partial<VoiceCallData>): Promise<void> {\n if (this.config.enableDebug) {\n console.log(\"[Analytics] Updating voice call:\", sessionId);\n }\n\n const response = await fetch(`${this.config.endpoint}/voice-call/${sessionId}`, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to update voice call: ${error}`);\n }\n }\n\n async flush(): Promise<void> {\n await this.batcher.flush();\n }\n\n setContext(context: Partial<AnalyticsContext>): void {\n this.context = { ...this.context, ...context };\n }\n\n identify(userId: string, traits?: Record<string, unknown>): void {\n this.userId = userId;\n this.userTraits = traits;\n }\n\n private enrichEvent(event: AnalyticsEvent): AnalyticsEvent {\n return {\n ...event,\n workspaceId: event.workspaceId ?? this.context.workspaceId,\n sessionId: event.sessionId ?? this.context.sessionId ?? \"\",\n agentId: event.agentId ?? this.context.agentId ?? \"\",\n conversationId: event.conversationId ?? this.context.conversationId,\n timestamp: event.timestamp ?? new Date(),\n data: {\n ...event.data,\n userId: event.data.userId ?? this.userId,\n userTraits: this.userTraits,\n },\n };\n }\n\n private async sendEvents(events: AnalyticsEvent[]): Promise<void> {\n const response = await fetch(`${this.config.endpoint}/events`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({ events }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send events: ${error}`);\n }\n }\n\n destroy(): void {\n this.batcher.destroy();\n }\n}\n\nexport function createAnalyticsClient(config: AnalyticsConfig): AnalyticsClient {\n return new AriaFlowAnalytics(config);\n}\n\nexport default AriaFlowAnalytics;\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "C025AF11B008174864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
class m{queue=[];flushTimer=null;options;constructor(t){this.options=t,this.startFlushTimer()}add(t){if(this.queue.push(t),this.queue.length>=this.options.maxBatchSize)this.flush()}async flush(){if(this.queue.length===0)return;let t=[...this.queue];if(this.queue=[],this.options.enableDebug)console.log(`[Analytics] Flushing ${t.length} events`);try{await this.options.onFlush(t)}catch(n){console.error("[Analytics] Flush failed:",n),this.queue.unshift(...t)}}startFlushTimer(){this.flushTimer=setInterval(()=>{this.flush()},this.options.flushInterval)}destroy(){if(this.flushTimer)clearInterval(this.flushTimer),this.flushTimer=null;this.flush()}}class g{config;batcher;context;userId;userTraits;constructor(t){this.config={apiKey:t.apiKey,endpoint:t.endpoint??"https://analytics.ariaflow.dev/api/v1",workspaceId:t.workspaceId,flushInterval:t.flushInterval??5000,maxBatchSize:t.maxBatchSize??20,enableDebug:t.enableDebug??!1},this.context={workspaceId:this.config.workspaceId},this.batcher=new m({maxBatchSize:this.config.maxBatchSize,flushInterval:this.config.flushInterval,onFlush:this.sendEvents.bind(this),enableDebug:this.config.enableDebug})}async track(t){let n=this.enrichEvent(t);this.batcher.add(n)}async trackBatch(t){for(let n of t)await this.track(n)}async trackVoiceCall(t){if(this.config.enableDebug)console.log("[Analytics] Tracking voice call:",t.sessionId);let n=await fetch(`${this.config.endpoint}/voice-call`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(t)});if(!n.ok){let e=await n.text();throw Error(`Failed to track voice call: ${e}`)}}async updateVoiceCall(t,n){if(this.config.enableDebug)console.log("[Analytics] Updating voice call:",t);let e=await fetch(`${this.config.endpoint}/voice-call/${t}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(n)});if(!e.ok){let r=await e.text();throw Error(`Failed to update voice call: ${r}`)}}async flush(){await this.batcher.flush()}setContext(t){this.context={...this.context,...t}}identify(t,n){this.userId=t,this.userTraits=n}enrichEvent(t){return{...t,workspaceId:t.workspaceId??this.context.workspaceId,sessionId:t.sessionId??this.context.sessionId??"",agentId:t.agentId??this.context.agentId??"",conversationId:t.conversationId??this.context.conversationId,timestamp:t.timestamp??new Date,data:{...t.data,userId:t.data.userId??this.userId,userTraits:this.userTraits}}}async sendEvents(t){let n=await fetch(`${this.config.endpoint}/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({events:t})});if(!n.ok){let e=await n.text();throw Error(`Failed to send events: ${e}`)}}destroy(){this.batcher.destroy()}}function v(t){return new g(t)}var x=g;import{useEffect as f,useRef as C,useCallback as s}from"react";var l=null;function w(t){if(l)l.destroy();return l=v(t),l}function k(){return l}function A(t){let n=C(null);f(()=>{if(t&&!n.current)n.current=v(t);return()=>{if(n.current)n.current.destroy(),n.current=null}},[t]);let e=n.current??l,r=s(async(a)=>{if(!e){console.warn("[Analytics] Client not initialized");return}await e.track(a)},[e]),i=s(async(a)=>{if(!e){console.warn("[Analytics] Client not initialized");return}await e.trackBatch(a)},[e]),u=s(async(a)=>{if(!e){console.warn("[Analytics] Client not initialized");return}await e.trackVoiceCall(a)},[e]),d=s(async(a,o)=>{if(!e){console.warn("[Analytics] Client not initialized");return}await e.updateVoiceCall(a,o)},[e]),h=s(async()=>{if(!e){console.warn("[Analytics] Client not initialized");return}await e.flush()},[e]),p=s((a)=>{if(!e){console.warn("[Analytics] Client not initialized");return}e.setContext(a)},[e]),y=s((a,o)=>{if(!e){console.warn("[Analytics] Client not initialized");return}e.identify(a,o)},[e]);return{track:r,trackBatch:i,trackVoiceCall:u,updateVoiceCall:d,flush:h,setContext:p,identify:y}}function D({children:t,config:n}){return f(()=>{return w(n),()=>{let e=k();if(e)e.destroy()}},[n]),t}function E(){let{track:t}=A();return t}function S(t,n){let{track:e}=A();f(()=>{e({type:"custom",sessionId:"",agentId:"",workspaceId:"",data:{event:"page_view",page:t,...n}})},[t,n,e])}function V(t,n){let{trackVoiceCall:e,updateVoiceCall:r}=A(),i=C({sessionId:t,workspaceId:n,userTurns:0,agentTurns:0,interruptions:0,totalUserSpeechMs:0,totalAgentSpeechMs:0}),u=s(async(a)=>{let o={sessionId:t,workspaceId:n,agentId:a,startedAt:new Date};i.current={...i.current,...o},await e(o)},[t,n,e]),d=s(async(a,o)=>{let c={endedAt:new Date,outcome:a,...i.current,...o};if(c.startedAt&&c.endedAt)c.durationSeconds=Math.floor((c.endedAt.getTime()-c.startedAt.getTime())/1000);await r(t,c)},[t,r]),h=s(()=>{i.current.interruptions=(i.current.interruptions??0)+1,r(t,{interruptions:i.current.interruptions})},[t,r]),p=s((a)=>{i.current.userTurns=(i.current.userTurns??0)+1,i.current.totalUserSpeechMs=(i.current.totalUserSpeechMs??0)+a,r(t,{userTurns:i.current.userTurns,totalUserSpeechMs:i.current.totalUserSpeechMs})},[t,r]),y=s((a)=>{i.current.agentTurns=(i.current.agentTurns??0)+1,i.current.totalAgentSpeechMs=(i.current.totalAgentSpeechMs??0)+a,r(t,{agentTurns:i.current.agentTurns,totalAgentSpeechMs:i.current.totalAgentSpeechMs})},[t,r]);return{startCall:u,endCall:d,trackInterruption:h,trackUserSpeech:p,trackAgentSpeech:y}}export{V as useVoiceCallTracker,E as useTrackEvent,S as usePageView,A as useAnalytics,w as initAnalytics,k as getAnalyticsClient,D as AnalyticsProvider};
|
|
3
|
+
|
|
4
|
+
//# debugId=752A6A65667196E164756E2164756E21
|
|
5
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/react.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export interface AnalyticsEvent {\n id?: string;\n timestamp?: Date;\n sessionId: string;\n conversationId?: string;\n agentId: string;\n workspaceId: string;\n type: AnalyticsEventType;\n data: Record<string, unknown>;\n}\n\nexport type AnalyticsEventType =\n | \"conversation.started\"\n | \"conversation.ended\"\n | \"node.entered\"\n | \"node.exited\"\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.error\"\n | \"booking.completed\"\n | \"handoff.initiated\"\n | \"emergency.detected\"\n | \"call.started\"\n | \"call.ended\"\n | \"user.spoke\"\n | \"agent.spoke\"\n | \"user.interrupted\"\n | \"silence.detected\"\n | \"latency.stt\"\n | \"latency.ttf\"\n | \"latency.e2e\"\n | \"latency.tts\"\n | \"error.occurred\"\n | \"custom\";\n\nexport interface VoiceCallData {\n sessionId: string;\n workspaceId: string;\n agentId?: string;\n userName?: string;\n userId?: string;\n startedAt: Date;\n endedAt?: Date;\n durationSeconds?: number;\n userTurns?: number;\n agentTurns?: number;\n interruptions?: number;\n silenceEvents?: number;\n errors?: number;\n totalUserSpeechMs?: number;\n totalAgentSpeechMs?: number;\n totalSilenceMs?: number;\n ttfMs?: number;\n avgSttMs?: number;\n avgTtsMs?: number;\n e2eMs?: number;\n outcome?: string;\n outcomeData?: Record<string, unknown>;\n currentNode?: string;\n agentName?: string;\n transcript?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AnalyticsConfig {\n apiKey: string;\n endpoint?: string;\n workspaceId: string;\n flushInterval?: number;\n maxBatchSize?: number;\n enableDebug?: boolean;\n}\n\nexport interface AnalyticsClient {\n track: (event: AnalyticsEvent) => Promise<void>;\n trackBatch: (events: AnalyticsEvent[]) => Promise<void>;\n trackVoiceCall: (data: VoiceCallData) => Promise<void>;\n updateVoiceCall: (sessionId: string, data: Partial<VoiceCallData>) => Promise<void>;\n flush: () => Promise<void>;\n setContext: (context: Partial<AnalyticsContext>) => void;\n identify: (userId: string, traits?: Record<string, unknown>) => void;\n}\n\nexport interface AnalyticsContext {\n workspaceId: string;\n agentId?: string;\n sessionId?: string;\n userId?: string;\n conversationId?: string;\n}\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n flushInterval: number;\n onFlush: (events: AnalyticsEvent[]) => Promise<void>;\n enableDebug?: boolean;\n}\n\nexport class Batcher {\n private queue: AnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private options: BatcherOptions;\n\n constructor(options: BatcherOptions) {\n this.options = options;\n this.startFlushTimer();\n }\n\n add(event: AnalyticsEvent): void {\n this.queue.push(event);\n\n if (this.queue.length >= this.options.maxBatchSize) {\n this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const events = [...this.queue];\n this.queue = [];\n\n if (this.options.enableDebug) {\n console.log(`[Analytics] Flushing ${events.length} events`);\n }\n\n try {\n await this.options.onFlush(events);\n } catch (error) {\n console.error(\"[Analytics] Flush failed:\", error);\n this.queue.unshift(...events);\n }\n }\n\n private startFlushTimer(): void {\n this.flushTimer = setInterval(() => {\n this.flush();\n }, this.options.flushInterval);\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n this.flush();\n }\n}\n\nexport class AriaFlowAnalytics implements AnalyticsClient {\n private config: Required<AnalyticsConfig>;\n private batcher: Batcher;\n private context: AnalyticsContext;\n private userId?: string;\n private userTraits?: Record<string, unknown>;\n\n constructor(config: AnalyticsConfig) {\n this.config = {\n apiKey: config.apiKey,\n endpoint: config.endpoint ?? \"https://analytics.ariaflow.dev/api/v1\",\n workspaceId: config.workspaceId,\n flushInterval: config.flushInterval ?? 5000,\n maxBatchSize: config.maxBatchSize ?? 20,\n enableDebug: config.enableDebug ?? false,\n };\n\n this.context = {\n workspaceId: this.config.workspaceId,\n };\n\n this.batcher = new Batcher({\n maxBatchSize: this.config.maxBatchSize,\n flushInterval: this.config.flushInterval,\n onFlush: this.sendEvents.bind(this),\n enableDebug: this.config.enableDebug,\n });\n }\n\n async track(event: AnalyticsEvent): Promise<void> {\n const enrichedEvent = this.enrichEvent(event);\n this.batcher.add(enrichedEvent);\n }\n\n async trackBatch(events: AnalyticsEvent[]): Promise<void> {\n for (const event of events) {\n await this.track(event);\n }\n }\n\n async trackVoiceCall(data: VoiceCallData): Promise<void> {\n if (this.config.enableDebug) {\n console.log(\"[Analytics] Tracking voice call:\", data.sessionId);\n }\n\n const response = await fetch(`${this.config.endpoint}/voice-call`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to track voice call: ${error}`);\n }\n }\n\n async updateVoiceCall(sessionId: string, data: Partial<VoiceCallData>): Promise<void> {\n if (this.config.enableDebug) {\n console.log(\"[Analytics] Updating voice call:\", sessionId);\n }\n\n const response = await fetch(`${this.config.endpoint}/voice-call/${sessionId}`, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to update voice call: ${error}`);\n }\n }\n\n async flush(): Promise<void> {\n await this.batcher.flush();\n }\n\n setContext(context: Partial<AnalyticsContext>): void {\n this.context = { ...this.context, ...context };\n }\n\n identify(userId: string, traits?: Record<string, unknown>): void {\n this.userId = userId;\n this.userTraits = traits;\n }\n\n private enrichEvent(event: AnalyticsEvent): AnalyticsEvent {\n return {\n ...event,\n workspaceId: event.workspaceId ?? this.context.workspaceId,\n sessionId: event.sessionId ?? this.context.sessionId ?? \"\",\n agentId: event.agentId ?? this.context.agentId ?? \"\",\n conversationId: event.conversationId ?? this.context.conversationId,\n timestamp: event.timestamp ?? new Date(),\n data: {\n ...event.data,\n userId: event.data.userId ?? this.userId,\n userTraits: this.userTraits,\n },\n };\n }\n\n private async sendEvents(events: AnalyticsEvent[]): Promise<void> {\n const response = await fetch(`${this.config.endpoint}/events`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({ events }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send events: ${error}`);\n }\n }\n\n destroy(): void {\n this.batcher.destroy();\n }\n}\n\nexport function createAnalyticsClient(config: AnalyticsConfig): AnalyticsClient {\n return new AriaFlowAnalytics(config);\n}\n\nexport default AriaFlowAnalytics;\n",
|
|
6
|
+
"import { useEffect, useRef, useCallback } from \"react\";\nimport {\n AriaFlowAnalytics,\n createAnalyticsClient,\n type AnalyticsConfig,\n type AnalyticsEvent,\n type AnalyticsContext,\n type VoiceCallData,\n} from \"./index.js\";\n\nexport interface UseAnalyticsOptions extends AnalyticsConfig {\n autoFlush?: boolean;\n}\n\nlet globalClient: AriaFlowAnalytics | null = null;\n\nexport function initAnalytics(config: AnalyticsConfig): AriaFlowAnalytics {\n if (globalClient) {\n globalClient.destroy();\n }\n globalClient = createAnalyticsClient(config) as AriaFlowAnalytics;\n return globalClient;\n}\n\nexport function getAnalyticsClient(): AriaFlowAnalytics | null {\n return globalClient;\n}\n\nexport function useAnalytics(config?: UseAnalyticsOptions): {\n track: (event: AnalyticsEvent) => Promise<void>;\n trackBatch: (events: AnalyticsEvent[]) => Promise<void>;\n trackVoiceCall: (data: VoiceCallData) => Promise<void>;\n updateVoiceCall: (sessionId: string, data: Partial<VoiceCallData>) => Promise<void>;\n flush: () => Promise<void>;\n setContext: (context: Partial<AnalyticsContext>) => void;\n identify: (userId: string, traits?: Record<string, unknown>) => void;\n} {\n const clientRef = useRef<AriaFlowAnalytics | null>(null);\n\n useEffect(() => {\n if (config && !clientRef.current) {\n clientRef.current = createAnalyticsClient(config) as AriaFlowAnalytics;\n }\n\n return () => {\n if (clientRef.current) {\n clientRef.current.destroy();\n clientRef.current = null;\n }\n };\n }, [config]);\n\n const client = clientRef.current ?? globalClient;\n\n const track = useCallback(\n async (event: AnalyticsEvent) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n await client.track(event);\n },\n [client]\n );\n\n const trackBatch = useCallback(\n async (events: AnalyticsEvent[]) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n await client.trackBatch(events);\n },\n [client]\n );\n\n const trackVoiceCall = useCallback(\n async (data: VoiceCallData) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n await client.trackVoiceCall(data);\n },\n [client]\n );\n\n const updateVoiceCall = useCallback(\n async (sessionId: string, data: Partial<VoiceCallData>) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n await client.updateVoiceCall(sessionId, data);\n },\n [client]\n );\n\n const flush = useCallback(async () => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n await client.flush();\n }, [client]);\n\n const setContext = useCallback(\n (context: Partial<AnalyticsContext>) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n client.setContext(context);\n },\n [client]\n );\n\n const identify = useCallback(\n (userId: string, traits?: Record<string, unknown>) => {\n if (!client) {\n console.warn(\"[Analytics] Client not initialized\");\n return;\n }\n client.identify(userId, traits);\n },\n [client]\n );\n\n return {\n track,\n trackBatch,\n trackVoiceCall,\n updateVoiceCall,\n flush,\n setContext,\n identify,\n };\n}\n\nexport function AnalyticsProvider({\n children,\n config,\n}: {\n children: React.ReactNode;\n config: AnalyticsConfig;\n}): React.ReactElement {\n useEffect(() => {\n initAnalytics(config);\n return () => {\n const client = getAnalyticsClient();\n if (client) {\n client.destroy();\n }\n };\n }, [config]);\n\n return children as React.ReactElement;\n}\n\nexport function useTrackEvent(): (event: AnalyticsEvent) => Promise<void> {\n const { track } = useAnalytics();\n return track;\n}\n\nexport function usePageView(pageName: string, properties?: Record<string, unknown>): void {\n const { track } = useAnalytics();\n\n useEffect(() => {\n track({\n type: \"custom\",\n sessionId: \"\",\n agentId: \"\",\n workspaceId: \"\",\n data: {\n event: \"page_view\",\n page: pageName,\n ...properties,\n },\n });\n }, [pageName, properties, track]);\n}\n\nexport function useVoiceCallTracker(sessionId: string, workspaceId: string): {\n startCall: (agentId?: string) => Promise<void>;\n endCall: (outcome?: string, data?: Partial<VoiceCallData>) => Promise<void>;\n trackInterruption: () => void;\n trackUserSpeech: (durationMs: number) => void;\n trackAgentSpeech: (durationMs: number) => void;\n} {\n const { trackVoiceCall, updateVoiceCall } = useAnalytics();\n const callDataRef = useRef<Partial<VoiceCallData>>({\n sessionId,\n workspaceId,\n userTurns: 0,\n agentTurns: 0,\n interruptions: 0,\n totalUserSpeechMs: 0,\n totalAgentSpeechMs: 0,\n });\n\n const startCall = useCallback(\n async (agentId?: string) => {\n const data: VoiceCallData = {\n sessionId,\n workspaceId,\n agentId,\n startedAt: new Date(),\n };\n\n callDataRef.current = {\n ...callDataRef.current,\n ...data,\n };\n\n await trackVoiceCall(data);\n },\n [sessionId, workspaceId, trackVoiceCall]\n );\n\n const endCall = useCallback(\n async (outcome?: string, additionalData?: Partial<VoiceCallData>) => {\n const data: Partial<VoiceCallData> = {\n endedAt: new Date(),\n outcome,\n ...callDataRef.current,\n ...additionalData,\n };\n\n if (data.startedAt && data.endedAt) {\n data.durationSeconds = Math.floor(\n (data.endedAt.getTime() - data.startedAt.getTime()) / 1000\n );\n }\n\n await updateVoiceCall(sessionId, data);\n },\n [sessionId, updateVoiceCall]\n );\n\n const trackInterruption = useCallback(() => {\n callDataRef.current.interruptions = (callDataRef.current.interruptions ?? 0) + 1;\n updateVoiceCall(sessionId, {\n interruptions: callDataRef.current.interruptions,\n });\n }, [sessionId, updateVoiceCall]);\n\n const trackUserSpeech = useCallback(\n (durationMs: number) => {\n callDataRef.current.userTurns = (callDataRef.current.userTurns ?? 0) + 1;\n callDataRef.current.totalUserSpeechMs =\n (callDataRef.current.totalUserSpeechMs ?? 0) + durationMs;\n\n updateVoiceCall(sessionId, {\n userTurns: callDataRef.current.userTurns,\n totalUserSpeechMs: callDataRef.current.totalUserSpeechMs,\n });\n },\n [sessionId, updateVoiceCall]\n );\n\n const trackAgentSpeech = useCallback(\n (durationMs: number) => {\n callDataRef.current.agentTurns = (callDataRef.current.agentTurns ?? 0) + 1;\n callDataRef.current.totalAgentSpeechMs =\n (callDataRef.current.totalAgentSpeechMs ?? 0) + durationMs;\n\n updateVoiceCall(sessionId, {\n agentTurns: callDataRef.current.agentTurns,\n totalAgentSpeechMs: callDataRef.current.totalAgentSpeechMs,\n });\n },\n [sessionId, updateVoiceCall]\n );\n\n return {\n startCall,\n endCall,\n trackInterruption,\n trackUserSpeech,\n trackAgentSpeech,\n };\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "752A6A65667196E164756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ariaflowagents/analytics-sdk",
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Type-safe SDK for sending analytics events to AriaFlow Analytics Platform",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./react": "./src/react.ts"
|
|
9
|
+
},
|
|
10
|
+
"main": "./src/index.ts",
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "~/.bun/bin/bun build ./src/index.ts ./src/react.ts --outdir ./dist --target bun --minify --sourcemap --external react",
|
|
14
|
+
"dev": "~/.bun/bin/bun build ./src/index.ts ./src/react.ts --outdir ./dist --target bun --watch --external react"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ariaflow",
|
|
22
|
+
"analytics",
|
|
23
|
+
"sdk",
|
|
24
|
+
"telemetry"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"zod": "^3.24.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/bun": "latest",
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"react": "^19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"react": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
export interface AnalyticsEvent {
|
|
2
|
+
id?: string;
|
|
3
|
+
timestamp?: Date;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
conversationId?: string;
|
|
6
|
+
agentId: string;
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
type: AnalyticsEventType;
|
|
9
|
+
data: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type AnalyticsEventType =
|
|
13
|
+
| "conversation.started"
|
|
14
|
+
| "conversation.ended"
|
|
15
|
+
| "node.entered"
|
|
16
|
+
| "node.exited"
|
|
17
|
+
| "tool.called"
|
|
18
|
+
| "tool.completed"
|
|
19
|
+
| "tool.error"
|
|
20
|
+
| "booking.completed"
|
|
21
|
+
| "handoff.initiated"
|
|
22
|
+
| "emergency.detected"
|
|
23
|
+
| "call.started"
|
|
24
|
+
| "call.ended"
|
|
25
|
+
| "user.spoke"
|
|
26
|
+
| "agent.spoke"
|
|
27
|
+
| "user.interrupted"
|
|
28
|
+
| "silence.detected"
|
|
29
|
+
| "latency.stt"
|
|
30
|
+
| "latency.ttf"
|
|
31
|
+
| "latency.e2e"
|
|
32
|
+
| "latency.tts"
|
|
33
|
+
| "error.occurred"
|
|
34
|
+
| "custom";
|
|
35
|
+
|
|
36
|
+
export interface VoiceCallData {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
agentId?: string;
|
|
40
|
+
userName?: string;
|
|
41
|
+
userId?: string;
|
|
42
|
+
startedAt: Date;
|
|
43
|
+
endedAt?: Date;
|
|
44
|
+
durationSeconds?: number;
|
|
45
|
+
userTurns?: number;
|
|
46
|
+
agentTurns?: number;
|
|
47
|
+
interruptions?: number;
|
|
48
|
+
silenceEvents?: number;
|
|
49
|
+
errors?: number;
|
|
50
|
+
totalUserSpeechMs?: number;
|
|
51
|
+
totalAgentSpeechMs?: number;
|
|
52
|
+
totalSilenceMs?: number;
|
|
53
|
+
ttfMs?: number;
|
|
54
|
+
avgSttMs?: number;
|
|
55
|
+
avgTtsMs?: number;
|
|
56
|
+
e2eMs?: number;
|
|
57
|
+
outcome?: string;
|
|
58
|
+
outcomeData?: Record<string, unknown>;
|
|
59
|
+
currentNode?: string;
|
|
60
|
+
agentName?: string;
|
|
61
|
+
transcript?: string;
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface AnalyticsConfig {
|
|
66
|
+
apiKey: string;
|
|
67
|
+
endpoint?: string;
|
|
68
|
+
workspaceId: string;
|
|
69
|
+
flushInterval?: number;
|
|
70
|
+
maxBatchSize?: number;
|
|
71
|
+
enableDebug?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AnalyticsClient {
|
|
75
|
+
track: (event: AnalyticsEvent) => Promise<void>;
|
|
76
|
+
trackBatch: (events: AnalyticsEvent[]) => Promise<void>;
|
|
77
|
+
trackVoiceCall: (data: VoiceCallData) => Promise<void>;
|
|
78
|
+
updateVoiceCall: (sessionId: string, data: Partial<VoiceCallData>) => Promise<void>;
|
|
79
|
+
flush: () => Promise<void>;
|
|
80
|
+
setContext: (context: Partial<AnalyticsContext>) => void;
|
|
81
|
+
identify: (userId: string, traits?: Record<string, unknown>) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AnalyticsContext {
|
|
85
|
+
workspaceId: string;
|
|
86
|
+
agentId?: string;
|
|
87
|
+
sessionId?: string;
|
|
88
|
+
userId?: string;
|
|
89
|
+
conversationId?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface BatcherOptions {
|
|
93
|
+
maxBatchSize: number;
|
|
94
|
+
flushInterval: number;
|
|
95
|
+
onFlush: (events: AnalyticsEvent[]) => Promise<void>;
|
|
96
|
+
enableDebug?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class Batcher {
|
|
100
|
+
private queue: AnalyticsEvent[] = [];
|
|
101
|
+
private flushTimer: ReturnType<typeof setInterval> | null = null;
|
|
102
|
+
private options: BatcherOptions;
|
|
103
|
+
|
|
104
|
+
constructor(options: BatcherOptions) {
|
|
105
|
+
this.options = options;
|
|
106
|
+
this.startFlushTimer();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
add(event: AnalyticsEvent): void {
|
|
110
|
+
this.queue.push(event);
|
|
111
|
+
|
|
112
|
+
if (this.queue.length >= this.options.maxBatchSize) {
|
|
113
|
+
this.flush();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async flush(): Promise<void> {
|
|
118
|
+
if (this.queue.length === 0) return;
|
|
119
|
+
|
|
120
|
+
const events = [...this.queue];
|
|
121
|
+
this.queue = [];
|
|
122
|
+
|
|
123
|
+
if (this.options.enableDebug) {
|
|
124
|
+
console.log(`[Analytics] Flushing ${events.length} events`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await this.options.onFlush(events);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("[Analytics] Flush failed:", error);
|
|
131
|
+
this.queue.unshift(...events);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private startFlushTimer(): void {
|
|
136
|
+
this.flushTimer = setInterval(() => {
|
|
137
|
+
this.flush();
|
|
138
|
+
}, this.options.flushInterval);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
destroy(): void {
|
|
142
|
+
if (this.flushTimer) {
|
|
143
|
+
clearInterval(this.flushTimer);
|
|
144
|
+
this.flushTimer = null;
|
|
145
|
+
}
|
|
146
|
+
this.flush();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class AriaFlowAnalytics implements AnalyticsClient {
|
|
151
|
+
private config: Required<AnalyticsConfig>;
|
|
152
|
+
private batcher: Batcher;
|
|
153
|
+
private context: AnalyticsContext;
|
|
154
|
+
private userId?: string;
|
|
155
|
+
private userTraits?: Record<string, unknown>;
|
|
156
|
+
|
|
157
|
+
constructor(config: AnalyticsConfig) {
|
|
158
|
+
this.config = {
|
|
159
|
+
apiKey: config.apiKey,
|
|
160
|
+
endpoint: config.endpoint ?? "https://analytics.ariaflow.dev/api/v1",
|
|
161
|
+
workspaceId: config.workspaceId,
|
|
162
|
+
flushInterval: config.flushInterval ?? 5000,
|
|
163
|
+
maxBatchSize: config.maxBatchSize ?? 20,
|
|
164
|
+
enableDebug: config.enableDebug ?? false,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
this.context = {
|
|
168
|
+
workspaceId: this.config.workspaceId,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
this.batcher = new Batcher({
|
|
172
|
+
maxBatchSize: this.config.maxBatchSize,
|
|
173
|
+
flushInterval: this.config.flushInterval,
|
|
174
|
+
onFlush: this.sendEvents.bind(this),
|
|
175
|
+
enableDebug: this.config.enableDebug,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async track(event: AnalyticsEvent): Promise<void> {
|
|
180
|
+
const enrichedEvent = this.enrichEvent(event);
|
|
181
|
+
this.batcher.add(enrichedEvent);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async trackBatch(events: AnalyticsEvent[]): Promise<void> {
|
|
185
|
+
for (const event of events) {
|
|
186
|
+
await this.track(event);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async trackVoiceCall(data: VoiceCallData): Promise<void> {
|
|
191
|
+
if (this.config.enableDebug) {
|
|
192
|
+
console.log("[Analytics] Tracking voice call:", data.sessionId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const response = await fetch(`${this.config.endpoint}/voice-call`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: {
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
200
|
+
},
|
|
201
|
+
body: JSON.stringify(data),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
const error = await response.text();
|
|
206
|
+
throw new Error(`Failed to track voice call: ${error}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async updateVoiceCall(sessionId: string, data: Partial<VoiceCallData>): Promise<void> {
|
|
211
|
+
if (this.config.enableDebug) {
|
|
212
|
+
console.log("[Analytics] Updating voice call:", sessionId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const response = await fetch(`${this.config.endpoint}/voice-call/${sessionId}`, {
|
|
216
|
+
method: "PUT",
|
|
217
|
+
headers: {
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify(data),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
const error = await response.text();
|
|
226
|
+
throw new Error(`Failed to update voice call: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async flush(): Promise<void> {
|
|
231
|
+
await this.batcher.flush();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setContext(context: Partial<AnalyticsContext>): void {
|
|
235
|
+
this.context = { ...this.context, ...context };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
identify(userId: string, traits?: Record<string, unknown>): void {
|
|
239
|
+
this.userId = userId;
|
|
240
|
+
this.userTraits = traits;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private enrichEvent(event: AnalyticsEvent): AnalyticsEvent {
|
|
244
|
+
return {
|
|
245
|
+
...event,
|
|
246
|
+
workspaceId: event.workspaceId ?? this.context.workspaceId,
|
|
247
|
+
sessionId: event.sessionId ?? this.context.sessionId ?? "",
|
|
248
|
+
agentId: event.agentId ?? this.context.agentId ?? "",
|
|
249
|
+
conversationId: event.conversationId ?? this.context.conversationId,
|
|
250
|
+
timestamp: event.timestamp ?? new Date(),
|
|
251
|
+
data: {
|
|
252
|
+
...event.data,
|
|
253
|
+
userId: event.data.userId ?? this.userId,
|
|
254
|
+
userTraits: this.userTraits,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private async sendEvents(events: AnalyticsEvent[]): Promise<void> {
|
|
260
|
+
const response = await fetch(`${this.config.endpoint}/events`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({ events }),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
const error = await response.text();
|
|
271
|
+
throw new Error(`Failed to send events: ${error}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
destroy(): void {
|
|
276
|
+
this.batcher.destroy();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function createAnalyticsClient(config: AnalyticsConfig): AnalyticsClient {
|
|
281
|
+
return new AriaFlowAnalytics(config);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default AriaFlowAnalytics;
|
package/src/react.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
AriaFlowAnalytics,
|
|
4
|
+
createAnalyticsClient,
|
|
5
|
+
type AnalyticsConfig,
|
|
6
|
+
type AnalyticsEvent,
|
|
7
|
+
type AnalyticsContext,
|
|
8
|
+
type VoiceCallData,
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
|
|
11
|
+
export interface UseAnalyticsOptions extends AnalyticsConfig {
|
|
12
|
+
autoFlush?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let globalClient: AriaFlowAnalytics | null = null;
|
|
16
|
+
|
|
17
|
+
export function initAnalytics(config: AnalyticsConfig): AriaFlowAnalytics {
|
|
18
|
+
if (globalClient) {
|
|
19
|
+
globalClient.destroy();
|
|
20
|
+
}
|
|
21
|
+
globalClient = createAnalyticsClient(config) as AriaFlowAnalytics;
|
|
22
|
+
return globalClient;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getAnalyticsClient(): AriaFlowAnalytics | null {
|
|
26
|
+
return globalClient;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useAnalytics(config?: UseAnalyticsOptions): {
|
|
30
|
+
track: (event: AnalyticsEvent) => Promise<void>;
|
|
31
|
+
trackBatch: (events: AnalyticsEvent[]) => Promise<void>;
|
|
32
|
+
trackVoiceCall: (data: VoiceCallData) => Promise<void>;
|
|
33
|
+
updateVoiceCall: (sessionId: string, data: Partial<VoiceCallData>) => Promise<void>;
|
|
34
|
+
flush: () => Promise<void>;
|
|
35
|
+
setContext: (context: Partial<AnalyticsContext>) => void;
|
|
36
|
+
identify: (userId: string, traits?: Record<string, unknown>) => void;
|
|
37
|
+
} {
|
|
38
|
+
const clientRef = useRef<AriaFlowAnalytics | null>(null);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (config && !clientRef.current) {
|
|
42
|
+
clientRef.current = createAnalyticsClient(config) as AriaFlowAnalytics;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
if (clientRef.current) {
|
|
47
|
+
clientRef.current.destroy();
|
|
48
|
+
clientRef.current = null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}, [config]);
|
|
52
|
+
|
|
53
|
+
const client = clientRef.current ?? globalClient;
|
|
54
|
+
|
|
55
|
+
const track = useCallback(
|
|
56
|
+
async (event: AnalyticsEvent) => {
|
|
57
|
+
if (!client) {
|
|
58
|
+
console.warn("[Analytics] Client not initialized");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await client.track(event);
|
|
62
|
+
},
|
|
63
|
+
[client]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const trackBatch = useCallback(
|
|
67
|
+
async (events: AnalyticsEvent[]) => {
|
|
68
|
+
if (!client) {
|
|
69
|
+
console.warn("[Analytics] Client not initialized");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await client.trackBatch(events);
|
|
73
|
+
},
|
|
74
|
+
[client]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const trackVoiceCall = useCallback(
|
|
78
|
+
async (data: VoiceCallData) => {
|
|
79
|
+
if (!client) {
|
|
80
|
+
console.warn("[Analytics] Client not initialized");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await client.trackVoiceCall(data);
|
|
84
|
+
},
|
|
85
|
+
[client]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const updateVoiceCall = useCallback(
|
|
89
|
+
async (sessionId: string, data: Partial<VoiceCallData>) => {
|
|
90
|
+
if (!client) {
|
|
91
|
+
console.warn("[Analytics] Client not initialized");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await client.updateVoiceCall(sessionId, data);
|
|
95
|
+
},
|
|
96
|
+
[client]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const flush = useCallback(async () => {
|
|
100
|
+
if (!client) {
|
|
101
|
+
console.warn("[Analytics] Client not initialized");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await client.flush();
|
|
105
|
+
}, [client]);
|
|
106
|
+
|
|
107
|
+
const setContext = useCallback(
|
|
108
|
+
(context: Partial<AnalyticsContext>) => {
|
|
109
|
+
if (!client) {
|
|
110
|
+
console.warn("[Analytics] Client not initialized");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
client.setContext(context);
|
|
114
|
+
},
|
|
115
|
+
[client]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const identify = useCallback(
|
|
119
|
+
(userId: string, traits?: Record<string, unknown>) => {
|
|
120
|
+
if (!client) {
|
|
121
|
+
console.warn("[Analytics] Client not initialized");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
client.identify(userId, traits);
|
|
125
|
+
},
|
|
126
|
+
[client]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
track,
|
|
131
|
+
trackBatch,
|
|
132
|
+
trackVoiceCall,
|
|
133
|
+
updateVoiceCall,
|
|
134
|
+
flush,
|
|
135
|
+
setContext,
|
|
136
|
+
identify,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function AnalyticsProvider({
|
|
141
|
+
children,
|
|
142
|
+
config,
|
|
143
|
+
}: {
|
|
144
|
+
children: React.ReactNode;
|
|
145
|
+
config: AnalyticsConfig;
|
|
146
|
+
}): React.ReactElement {
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
initAnalytics(config);
|
|
149
|
+
return () => {
|
|
150
|
+
const client = getAnalyticsClient();
|
|
151
|
+
if (client) {
|
|
152
|
+
client.destroy();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}, [config]);
|
|
156
|
+
|
|
157
|
+
return children as React.ReactElement;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function useTrackEvent(): (event: AnalyticsEvent) => Promise<void> {
|
|
161
|
+
const { track } = useAnalytics();
|
|
162
|
+
return track;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function usePageView(pageName: string, properties?: Record<string, unknown>): void {
|
|
166
|
+
const { track } = useAnalytics();
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
track({
|
|
170
|
+
type: "custom",
|
|
171
|
+
sessionId: "",
|
|
172
|
+
agentId: "",
|
|
173
|
+
workspaceId: "",
|
|
174
|
+
data: {
|
|
175
|
+
event: "page_view",
|
|
176
|
+
page: pageName,
|
|
177
|
+
...properties,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}, [pageName, properties, track]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function useVoiceCallTracker(sessionId: string, workspaceId: string): {
|
|
184
|
+
startCall: (agentId?: string) => Promise<void>;
|
|
185
|
+
endCall: (outcome?: string, data?: Partial<VoiceCallData>) => Promise<void>;
|
|
186
|
+
trackInterruption: () => void;
|
|
187
|
+
trackUserSpeech: (durationMs: number) => void;
|
|
188
|
+
trackAgentSpeech: (durationMs: number) => void;
|
|
189
|
+
} {
|
|
190
|
+
const { trackVoiceCall, updateVoiceCall } = useAnalytics();
|
|
191
|
+
const callDataRef = useRef<Partial<VoiceCallData>>({
|
|
192
|
+
sessionId,
|
|
193
|
+
workspaceId,
|
|
194
|
+
userTurns: 0,
|
|
195
|
+
agentTurns: 0,
|
|
196
|
+
interruptions: 0,
|
|
197
|
+
totalUserSpeechMs: 0,
|
|
198
|
+
totalAgentSpeechMs: 0,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const startCall = useCallback(
|
|
202
|
+
async (agentId?: string) => {
|
|
203
|
+
const data: VoiceCallData = {
|
|
204
|
+
sessionId,
|
|
205
|
+
workspaceId,
|
|
206
|
+
agentId,
|
|
207
|
+
startedAt: new Date(),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
callDataRef.current = {
|
|
211
|
+
...callDataRef.current,
|
|
212
|
+
...data,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
await trackVoiceCall(data);
|
|
216
|
+
},
|
|
217
|
+
[sessionId, workspaceId, trackVoiceCall]
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const endCall = useCallback(
|
|
221
|
+
async (outcome?: string, additionalData?: Partial<VoiceCallData>) => {
|
|
222
|
+
const data: Partial<VoiceCallData> = {
|
|
223
|
+
endedAt: new Date(),
|
|
224
|
+
outcome,
|
|
225
|
+
...callDataRef.current,
|
|
226
|
+
...additionalData,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (data.startedAt && data.endedAt) {
|
|
230
|
+
data.durationSeconds = Math.floor(
|
|
231
|
+
(data.endedAt.getTime() - data.startedAt.getTime()) / 1000
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await updateVoiceCall(sessionId, data);
|
|
236
|
+
},
|
|
237
|
+
[sessionId, updateVoiceCall]
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const trackInterruption = useCallback(() => {
|
|
241
|
+
callDataRef.current.interruptions = (callDataRef.current.interruptions ?? 0) + 1;
|
|
242
|
+
updateVoiceCall(sessionId, {
|
|
243
|
+
interruptions: callDataRef.current.interruptions,
|
|
244
|
+
});
|
|
245
|
+
}, [sessionId, updateVoiceCall]);
|
|
246
|
+
|
|
247
|
+
const trackUserSpeech = useCallback(
|
|
248
|
+
(durationMs: number) => {
|
|
249
|
+
callDataRef.current.userTurns = (callDataRef.current.userTurns ?? 0) + 1;
|
|
250
|
+
callDataRef.current.totalUserSpeechMs =
|
|
251
|
+
(callDataRef.current.totalUserSpeechMs ?? 0) + durationMs;
|
|
252
|
+
|
|
253
|
+
updateVoiceCall(sessionId, {
|
|
254
|
+
userTurns: callDataRef.current.userTurns,
|
|
255
|
+
totalUserSpeechMs: callDataRef.current.totalUserSpeechMs,
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
[sessionId, updateVoiceCall]
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const trackAgentSpeech = useCallback(
|
|
262
|
+
(durationMs: number) => {
|
|
263
|
+
callDataRef.current.agentTurns = (callDataRef.current.agentTurns ?? 0) + 1;
|
|
264
|
+
callDataRef.current.totalAgentSpeechMs =
|
|
265
|
+
(callDataRef.current.totalAgentSpeechMs ?? 0) + durationMs;
|
|
266
|
+
|
|
267
|
+
updateVoiceCall(sessionId, {
|
|
268
|
+
agentTurns: callDataRef.current.agentTurns,
|
|
269
|
+
totalAgentSpeechMs: callDataRef.current.totalAgentSpeechMs,
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
[sessionId, updateVoiceCall]
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
startCall,
|
|
277
|
+
endCall,
|
|
278
|
+
trackInterruption,
|
|
279
|
+
trackUserSpeech,
|
|
280
|
+
trackAgentSpeech,
|
|
281
|
+
};
|
|
282
|
+
}
|