@live-assistant/react-native 0.3.0 → 0.3.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 +6 -2
- package/dist/live-assistant.d.ts +4 -0
- package/dist/live-assistant.d.ts.map +1 -1
- package/dist/live-assistant.js +24 -8
- package/dist/live-assistant.js.map +1 -1
- package/package.json +6 -6
- package/src/live-assistant.tsx +24 -8
package/README.md
CHANGED
|
@@ -15,8 +15,10 @@ same way in every app — so they are not yours to write. The one value it canno
|
|
|
15
15
|
invent is the route on **your** server that mints a short-lived token, because
|
|
16
16
|
your API key must never ship in an app bundle.
|
|
17
17
|
|
|
18
|
-
This page is the whole of it:
|
|
19
|
-
|
|
18
|
+
This page is the whole of it: five steps, then a table for every value you can
|
|
19
|
+
pass. **Building for the web only?** Steps 1 and 2 are the native setup — there,
|
|
20
|
+
the install is the whole of it and nothing needs rebuilding; skip to
|
|
21
|
+
[step 3](#3-mint-tokens-on-your-server). If you would rather read code than prose,
|
|
20
22
|
[`examples/expo-app`](https://github.com/Recipely-Team/live-assistant/tree/main/examples/expo-app)
|
|
21
23
|
is a working app in one file, and CI builds it on every change.
|
|
22
24
|
|
|
@@ -39,6 +41,8 @@ On the web nothing native is needed, but `getUserMedia` only exists in a
|
|
|
39
41
|
|
|
40
42
|
## 2. Configure the microphone — one line
|
|
41
43
|
|
|
44
|
+
*Native only. A web build needs nothing from this step.*
|
|
45
|
+
|
|
42
46
|
```json
|
|
43
47
|
{
|
|
44
48
|
"expo": {
|
package/dist/live-assistant.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export type LiveAssistantProps = LiveAssistantEndpointProps | LiveAssistantConne
|
|
|
49
49
|
* - **The controller is built once**, in a `useState` initialiser. It owns a
|
|
50
50
|
* socket and two devices; rebuilding it on a render would start a session per
|
|
51
51
|
* render. The session is stopped when this unmounts.
|
|
52
|
+
* - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
|
|
53
|
+
* through a ref, so passing them inline — which is how anyone passes them —
|
|
54
|
+
* cannot re-run the lifecycle effect. It once could, and its cleanup hung up
|
|
55
|
+
* on a live session whenever a parent re-rendered.
|
|
52
56
|
* - **`headers` and `language` are read at connection time, not at mount.** An
|
|
53
57
|
* app whose `Authorization` header is refreshed mid-session would otherwise
|
|
54
58
|
* reconnect with the token it had when the component first rendered — which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-assistant.d.ts","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG1H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,mEAAmE;AACnE,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,YAAY,CAAC;IACzD,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9E,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;IACpF,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED,wCAAwC;AACxC,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,mFAAmF;AACnF,UAAU,4BAA6B,SAAQ,wBAAwB;IACrE,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,MAAM,MAAM,kBAAkB,GAAG,0BAA0B,GAAG,4BAA4B,CAAC;AAK3F
|
|
1
|
+
{"version":3,"file":"live-assistant.d.ts","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG1H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,mEAAmE;AACnE,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,YAAY,CAAC;IACzD,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9E,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;IACpF,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED,wCAAwC;AACxC,UAAU,0BAA2B,SAAQ,wBAAwB;IACnE,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,mFAAmF;AACnF,UAAU,4BAA6B,SAAQ,wBAAwB;IACrE,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC;CAChC;AAED,MAAM,MAAM,kBAAkB,GAAG,0BAA0B,GAAG,4BAA4B,CAAC;AAK3F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,aAAa,EACb,QAA2B,EAC3B,OAAO,EACP,KAAK,EACL,IAAI,EACJ,MAAM,EACN,OAAO,EACP,SAAS,EACT,GAAG,MAAM,EACV,EAAE,kBAAkB,+BA2CpB"}
|
package/dist/live-assistant.js
CHANGED
|
@@ -27,6 +27,10 @@ const JSON_TYPE = 'application/json';
|
|
|
27
27
|
* - **The controller is built once**, in a `useState` initialiser. It owns a
|
|
28
28
|
* socket and two devices; rebuilding it on a render would start a session per
|
|
29
29
|
* render. The session is stopped when this unmounts.
|
|
30
|
+
* - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
|
|
31
|
+
* through a ref, so passing them inline — which is how anyone passes them —
|
|
32
|
+
* cannot re-run the lifecycle effect. It once could, and its cleanup hung up
|
|
33
|
+
* on a live session whenever a parent re-rendered.
|
|
30
34
|
* - **`headers` and `language` are read at connection time, not at mount.** An
|
|
31
35
|
* app whose `Authorization` header is refreshed mid-session would otherwise
|
|
32
36
|
* reconnect with the token it had when the component first rendered — which
|
|
@@ -42,8 +46,8 @@ const JSON_TYPE = 'application/json';
|
|
|
42
46
|
*/
|
|
43
47
|
function LiveAssistant({ tokenEndpoint, getConnection, language = DEFAULT_LANGUAGE, headers, tools, page, timing, onReady, onFailure, ...widget }) {
|
|
44
48
|
// Read at connection time rather than captured at mount: see the doc block.
|
|
45
|
-
const latest = (0, react_1.useRef)({ tokenEndpoint, getConnection, language, headers });
|
|
46
|
-
latest.current = { tokenEndpoint, getConnection, language, headers };
|
|
49
|
+
const latest = (0, react_1.useRef)({ tokenEndpoint, getConnection, language, headers, onReady, onFailure });
|
|
50
|
+
latest.current = { tokenEndpoint, getConnection, language, headers, onReady, onFailure };
|
|
47
51
|
const [controller] = (0, react_1.useState)(() => new core_1.AssistantController({
|
|
48
52
|
session: new gemini_1.GeminiLiveSession(),
|
|
49
53
|
microphone: new audio_1.Microphone(),
|
|
@@ -65,20 +69,32 @@ function LiveAssistant({ tokenEndpoint, getConnection, language = DEFAULT_LANGUA
|
|
|
65
69
|
return (await response.json());
|
|
66
70
|
},
|
|
67
71
|
}));
|
|
72
|
+
// Only the controller is a dependency. `onReady` used to be one too, and an
|
|
73
|
+
// inline arrow — the ordinary way to pass it — is a new function on every
|
|
74
|
+
// render, so the effect re-ran and its cleanup hung up on a live session
|
|
75
|
+
// whenever anything above this re-rendered.
|
|
68
76
|
(0, react_1.useEffect)(() => {
|
|
69
|
-
onReady?.(controller);
|
|
77
|
+
latest.current.onReady?.(controller);
|
|
70
78
|
return () => void controller.stop();
|
|
71
|
-
}, [controller
|
|
72
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.AssistantProvider, { controller: controller, children: [(0, jsx_runtime_1.jsx)(FailureReporter, { onFailure: onFailure }), (0, jsx_runtime_1.jsx)(widget_1.AssistantWidget, { ...widget })] }));
|
|
79
|
+
}, [controller]);
|
|
80
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.AssistantProvider, { controller: controller, children: [(0, jsx_runtime_1.jsx)(FailureReporter, { onFailure: latest.current.onFailure }), (0, jsx_runtime_1.jsx)(widget_1.AssistantWidget, { ...widget })] }));
|
|
73
81
|
}
|
|
74
82
|
const selectError = (state) => state.error;
|
|
75
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Reports failures to the app without re-rendering the widget for them.
|
|
85
|
+
*
|
|
86
|
+
* The handler is held in a ref for the same reason the lifecycle effect takes
|
|
87
|
+
* only the controller: an inline arrow changes identity every render, and a
|
|
88
|
+
* handler in the dependencies would report the same failure again each time.
|
|
89
|
+
*/
|
|
76
90
|
function FailureReporter({ onFailure }) {
|
|
77
91
|
const error = (0, react_2.useAssistantState)(selectError);
|
|
92
|
+
const report = (0, react_1.useRef)(onFailure);
|
|
93
|
+
report.current = onFailure;
|
|
78
94
|
(0, react_1.useEffect)(() => {
|
|
79
95
|
if (error !== null)
|
|
80
|
-
|
|
81
|
-
}, [error
|
|
96
|
+
report.current?.(error);
|
|
97
|
+
}, [error]);
|
|
82
98
|
return null;
|
|
83
99
|
}
|
|
84
100
|
//# sourceMappingURL=live-assistant.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-assistant.js","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"live-assistant.js","sourceRoot":"","sources":["../src/live-assistant.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-assistant/react-native",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "One install for a voice assistant in a React Native or Expo app: the session, a Gemini Live connection, microphone and playback, React bindings and a ready-made widget, all re-exported from one place.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Recep Tayyip Ekşi",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@live-assistant/audio": "0.3.
|
|
45
|
-
"@live-assistant/core": "0.3.
|
|
46
|
-
"@live-assistant/gemini": "0.3.
|
|
47
|
-
"@live-assistant/react": "0.3.
|
|
48
|
-
"@live-assistant/widget": "0.3.
|
|
44
|
+
"@live-assistant/audio": "0.3.1",
|
|
45
|
+
"@live-assistant/core": "0.3.1",
|
|
46
|
+
"@live-assistant/gemini": "0.3.1",
|
|
47
|
+
"@live-assistant/react": "0.3.1",
|
|
48
|
+
"@live-assistant/widget": "0.3.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": ">=18",
|
package/src/live-assistant.tsx
CHANGED
|
@@ -60,6 +60,10 @@ const JSON_TYPE = 'application/json';
|
|
|
60
60
|
* - **The controller is built once**, in a `useState` initialiser. It owns a
|
|
61
61
|
* socket and two devices; rebuilding it on a render would start a session per
|
|
62
62
|
* render. The session is stopped when this unmounts.
|
|
63
|
+
* - **Handlers are held, not depended on.** `onReady` and `onFailure` are read
|
|
64
|
+
* through a ref, so passing them inline — which is how anyone passes them —
|
|
65
|
+
* cannot re-run the lifecycle effect. It once could, and its cleanup hung up
|
|
66
|
+
* on a live session whenever a parent re-rendered.
|
|
63
67
|
* - **`headers` and `language` are read at connection time, not at mount.** An
|
|
64
68
|
* app whose `Authorization` header is refreshed mid-session would otherwise
|
|
65
69
|
* reconnect with the token it had when the component first rendered — which
|
|
@@ -86,8 +90,8 @@ export function LiveAssistant({
|
|
|
86
90
|
...widget
|
|
87
91
|
}: LiveAssistantProps) {
|
|
88
92
|
// Read at connection time rather than captured at mount: see the doc block.
|
|
89
|
-
const latest = useRef({ tokenEndpoint, getConnection, language, headers });
|
|
90
|
-
latest.current = { tokenEndpoint, getConnection, language, headers };
|
|
93
|
+
const latest = useRef({ tokenEndpoint, getConnection, language, headers, onReady, onFailure });
|
|
94
|
+
latest.current = { tokenEndpoint, getConnection, language, headers, onReady, onFailure };
|
|
91
95
|
|
|
92
96
|
const [controller] = useState(
|
|
93
97
|
() =>
|
|
@@ -112,14 +116,18 @@ export function LiveAssistant({
|
|
|
112
116
|
}),
|
|
113
117
|
);
|
|
114
118
|
|
|
119
|
+
// Only the controller is a dependency. `onReady` used to be one too, and an
|
|
120
|
+
// inline arrow — the ordinary way to pass it — is a new function on every
|
|
121
|
+
// render, so the effect re-ran and its cleanup hung up on a live session
|
|
122
|
+
// whenever anything above this re-rendered.
|
|
115
123
|
useEffect(() => {
|
|
116
|
-
onReady?.(controller);
|
|
124
|
+
latest.current.onReady?.(controller);
|
|
117
125
|
return () => void controller.stop();
|
|
118
|
-
}, [controller
|
|
126
|
+
}, [controller]);
|
|
119
127
|
|
|
120
128
|
return (
|
|
121
129
|
<AssistantProvider controller={controller}>
|
|
122
|
-
<FailureReporter onFailure={onFailure} />
|
|
130
|
+
<FailureReporter onFailure={latest.current.onFailure} />
|
|
123
131
|
<AssistantWidget {...widget} />
|
|
124
132
|
</AssistantProvider>
|
|
125
133
|
);
|
|
@@ -127,11 +135,19 @@ export function LiveAssistant({
|
|
|
127
135
|
|
|
128
136
|
const selectError = (state: { readonly error: AssistantFailure | null }): AssistantFailure | null => state.error;
|
|
129
137
|
|
|
130
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Reports failures to the app without re-rendering the widget for them.
|
|
140
|
+
*
|
|
141
|
+
* The handler is held in a ref for the same reason the lifecycle effect takes
|
|
142
|
+
* only the controller: an inline arrow changes identity every render, and a
|
|
143
|
+
* handler in the dependencies would report the same failure again each time.
|
|
144
|
+
*/
|
|
131
145
|
function FailureReporter({ onFailure }: { readonly onFailure?: (failure: AssistantFailure) => void }) {
|
|
132
146
|
const error = useAssistantState(selectError);
|
|
147
|
+
const report = useRef(onFailure);
|
|
148
|
+
report.current = onFailure;
|
|
133
149
|
useEffect(() => {
|
|
134
|
-
if (error !== null)
|
|
135
|
-
}, [error
|
|
150
|
+
if (error !== null) report.current?.(error);
|
|
151
|
+
}, [error]);
|
|
136
152
|
return null;
|
|
137
153
|
}
|