@facepe/widget-mobile-app 1.3.1 → 1.4.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.
- package/README.md +19 -0
- package/dist/FacePeMobileAvatar.js +49 -19
- package/dist/types.d.ts +20 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -338,6 +338,25 @@ when the component unmounts.
|
|
|
338
338
|
| `durationSec` | Length in seconds |
|
|
339
339
|
| `endReason` | `ended` (the user or your code ended it), `disconnected` (the stream dropped), `unmounted` (the component went away) |
|
|
340
340
|
| `transcript` | Every line in order: `{ role: "user" \| "assistant", text, at }` |
|
|
341
|
+
| `form` | For a form avatar: `{ title, complete, fields: [{ label, question, required, value }] }`; `null` otherwise |
|
|
342
|
+
|
|
343
|
+
For an avatar that **fills a form**, the record also carries the form as the
|
|
344
|
+
conversation filled it: each question with the customer's answer (`value` is
|
|
345
|
+
`null` for a question that was not answered). FacePe reads the answers from the
|
|
346
|
+
conversation when it ends, so the record arrives a few seconds after the call.
|
|
347
|
+
For other avatars `form` is `null`.
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
"form": {
|
|
351
|
+
"title": "Customer Intake Form",
|
|
352
|
+
"complete": true,
|
|
353
|
+
"fields": [
|
|
354
|
+
{ "label": "Name", "question": "Could you please tell me your full name?", "required": true, "value": "Tony" },
|
|
355
|
+
{ "label": "Phone", "question": "What is the best phone number to reach you?", "required": true, "value": "9659390715" },
|
|
356
|
+
{ "label": "Email", "question": "May I have your email address?", "required": false, "value": "tony@gmail.com" }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
```
|
|
341
360
|
|
|
342
361
|
---
|
|
343
362
|
|
|
@@ -58,6 +58,14 @@ function ensureLiveKitGlobals(LiveKitRN) {
|
|
|
58
58
|
console.warn('[FacePeMobileAvatar] registerGlobals warning:', err);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Speech recognition writes a spoken phone number in parts ("9659-3907-1526").
|
|
63
|
+
* Long numbers are shown as plain digits; short ones ("2-3") are left as said.
|
|
64
|
+
*/
|
|
65
|
+
const joinNumberParts = (text) => text.replace(/\d+(?:[ \t]*[-\u2010\u2011\u2013][ \t]*\d+)+/g, (m) => {
|
|
66
|
+
const digits = m.replace(/\D/g, '');
|
|
67
|
+
return digits.length >= 7 ? digits : m;
|
|
68
|
+
});
|
|
61
69
|
export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', style, videoStyle, objectFit = 'cover', mirror = false, autoStart = true, enableMic = true, onTranscript, onSpeakStart, onSpeakEnd, onUserSpeakStart, onUserSpeakEnd, onOrderUpdate, onCheckout, onStatusChange, onError, onConversationEnd, posterUrl, }, ref) => {
|
|
62
70
|
const { token, loading: tokenLoading, error: tokenError, apiBaseUrl, refreshToken } = useFacePeMobileContext();
|
|
63
71
|
// The avatar's portrait, shown until its video arrives. Looked up from the
|
|
@@ -109,7 +117,8 @@ export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', styl
|
|
|
109
117
|
const onTranscriptRef = useRef(onTranscript);
|
|
110
118
|
onTranscriptRef.current = onTranscript;
|
|
111
119
|
// A transcript line: recorded for the conversation record, and passed on.
|
|
112
|
-
const emitLine = (role,
|
|
120
|
+
const emitLine = (role, said, id) => {
|
|
121
|
+
const text = joinNumberParts(said);
|
|
113
122
|
const conv = conversationRef.current;
|
|
114
123
|
if (conv && text) {
|
|
115
124
|
const same = id ? conv.lines.find((l) => l.id === id) : undefined;
|
|
@@ -120,30 +129,51 @@ export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', styl
|
|
|
120
129
|
}
|
|
121
130
|
onTranscriptRef.current?.({ payload: { role, text, isFinal: true } });
|
|
122
131
|
};
|
|
123
|
-
// Hands the whole conversation to the app, once.
|
|
132
|
+
// Hands the whole conversation to the app, once. For a form avatar it first
|
|
133
|
+
// asks FacePe for the form as the conversation filled it (a few seconds).
|
|
124
134
|
const finishConversation = (endReason) => {
|
|
125
135
|
const conv = conversationRef.current;
|
|
126
136
|
conversationRef.current = null;
|
|
127
137
|
if (!conv)
|
|
128
138
|
return;
|
|
129
139
|
const endedAt = new Date();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
const transcript = conv.lines.map(({ role, text, at }) => ({ role, text, at }));
|
|
141
|
+
void (async () => {
|
|
142
|
+
let form = null;
|
|
143
|
+
const token = tokenRef.current;
|
|
144
|
+
if (conv.conversationId && transcript.length > 0 && token) {
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`${apiBaseUrl.replace(/\/+$/, '')}/avatar-sessions/${encodeURIComponent(conv.conversationId)}/form`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
149
|
+
body: JSON.stringify({ transcript: transcript.map(({ role, text }) => ({ role, text })) }),
|
|
150
|
+
});
|
|
151
|
+
if (res.ok)
|
|
152
|
+
form = (await res.json())?.data?.form ?? null;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* offline: the record goes without the form */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
onConversationEndRef.current?.({
|
|
160
|
+
conversationId: conv.conversationId,
|
|
161
|
+
avatar: conv.avatar,
|
|
162
|
+
language: conv.language,
|
|
163
|
+
platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'web',
|
|
164
|
+
sdkVersion: SDK_VERSION,
|
|
165
|
+
startedAt: conv.startedAt.toISOString(),
|
|
166
|
+
endedAt: endedAt.toISOString(),
|
|
167
|
+
durationSec: Math.max(0, Math.round((endedAt.getTime() - conv.startedAt.getTime()) / 1000)),
|
|
168
|
+
endReason,
|
|
169
|
+
transcript,
|
|
170
|
+
form,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
console.warn('[FacePeMobileAvatar] onConversationEnd error:', err);
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
147
177
|
};
|
|
148
178
|
const mountedRef = useRef(true);
|
|
149
179
|
const isConnectingRef = useRef(false);
|
package/dist/types.d.ts
CHANGED
|
@@ -22,6 +22,20 @@ export interface FacePeConversationLine {
|
|
|
22
22
|
* (`onConversationEnd`). It stays in your app: the SDK sends it nowhere — you
|
|
23
23
|
* decide whether to show, keep or send it.
|
|
24
24
|
*/
|
|
25
|
+
/** One question of a form avatar, with the customer's answer (null: not answered). */
|
|
26
|
+
export interface FacePeFormAnswer {
|
|
27
|
+
label: string;
|
|
28
|
+
question: string;
|
|
29
|
+
required: boolean;
|
|
30
|
+
value: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** A form avatar's form, filled from the conversation. */
|
|
33
|
+
export interface FacePeFilledForm {
|
|
34
|
+
title: string;
|
|
35
|
+
/** Every required question has an answer. */
|
|
36
|
+
complete: boolean;
|
|
37
|
+
fields: FacePeFormAnswer[];
|
|
38
|
+
}
|
|
25
39
|
export interface FacePeConversation {
|
|
26
40
|
/** The conversation's id (its FacePe usage record), or null if none was opened. */
|
|
27
41
|
conversationId: string | null;
|
|
@@ -41,6 +55,12 @@ export interface FacePeConversation {
|
|
|
41
55
|
endReason: 'ended' | 'disconnected' | 'unmounted';
|
|
42
56
|
/** Every line, in order (a line that grew while being spoken appears once, complete). */
|
|
43
57
|
transcript: FacePeConversationLine[];
|
|
58
|
+
/**
|
|
59
|
+
* For an avatar that fills a form: its questions with the customer's answers,
|
|
60
|
+
* read from the conversation. Null for other avatars, or when the answers
|
|
61
|
+
* could not be read.
|
|
62
|
+
*/
|
|
63
|
+
form: FacePeFilledForm | null;
|
|
44
64
|
}
|
|
45
65
|
export interface OrderItem {
|
|
46
66
|
name: string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export declare const SDK_VERSION = "1.
|
|
2
|
+
export declare const SDK_VERSION = "1.4.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export const SDK_VERSION = '1.
|
|
2
|
+
export const SDK_VERSION = '1.4.0';
|
package/package.json
CHANGED