@graineai/inapp-react-native 0.40.1 → 0.40.2
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/dist/client.d.ts +3 -0
- package/dist/client.js +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react-native/voice-bar.d.ts +9 -1
- package/dist/react-native/voice-bar.js +30 -2
- package/dist/react-native/voice-launcher.js +34 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -86,6 +86,9 @@ export declare class GraineInAppClient {
|
|
|
86
86
|
private voiceHost;
|
|
87
87
|
get hasVoiceHost(): boolean;
|
|
88
88
|
setVoiceHost(on: boolean): void;
|
|
89
|
+
private nativeCall;
|
|
90
|
+
get nativeCallLive(): boolean;
|
|
91
|
+
setNativeCallLive(live: boolean): void;
|
|
89
92
|
private currentSurface;
|
|
90
93
|
get surface(): AgentSurface;
|
|
91
94
|
setSurface(next: AgentSurface): void;
|
package/dist/client.js
CHANGED
|
@@ -95,6 +95,7 @@ export class GraineInAppClient {
|
|
|
95
95
|
this.recentEvents = [];
|
|
96
96
|
this.lastWidget = null;
|
|
97
97
|
this.voiceHost = false;
|
|
98
|
+
this.nativeCall = false;
|
|
98
99
|
this.currentSurface = "voice";
|
|
99
100
|
if (!opts?.publishableKey)
|
|
100
101
|
throw new Error("[Graine] publishableKey is required.");
|
|
@@ -140,6 +141,16 @@ export class GraineInAppClient {
|
|
|
140
141
|
this.voiceHost = on;
|
|
141
142
|
this.emit("voice_host", { present: on });
|
|
142
143
|
}
|
|
144
|
+
get nativeCallLive() {
|
|
145
|
+
return this.nativeCall;
|
|
146
|
+
}
|
|
147
|
+
setNativeCallLive(live) {
|
|
148
|
+
const next = !!live;
|
|
149
|
+
if (this.nativeCall === next)
|
|
150
|
+
return;
|
|
151
|
+
this.nativeCall = next;
|
|
152
|
+
this.emit("native_call", { live: next });
|
|
153
|
+
}
|
|
143
154
|
get surface() {
|
|
144
155
|
return this.currentSurface;
|
|
145
156
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { UNNAMED_TAP, resolveWidgetImages } from "./protocol.js";
|
|
|
30
30
|
export { resolveNativeAppearance, type NativeAppearance, type NativeAppearanceFallback, resolveWidgetPalette, type WidgetPalette, type WidgetMode, } from "./react-native/appearance.js";
|
|
31
31
|
export { RtcVoiceSessionController, RtcVoiceError, type RtcVoiceState, type RtcVoiceErrorCode, type RtcVoiceSession, type RtcVoiceOptions, } from "./react-native/voice-rtc.js";
|
|
32
32
|
export { GraineAgentBar, GraineLauncher, type GraineAgentBarProps, type GraineLauncherProps, type GraineBarTheme, } from "./react-native/ui.js";
|
|
33
|
-
export { GraineVoiceBar, type GraineVoiceBarProps } from "./react-native/voice-bar.js";
|
|
33
|
+
export { GraineVoiceBar, GraineBarAction, type GraineVoiceBarProps, type GraineBarActionProps } from "./react-native/voice-bar.js";
|
|
34
34
|
export { GraineLauncherButton, type GraineLauncherButtonProps } from "./react-native/fab.js";
|
|
35
35
|
export { faceFor, facesForDesign, fontUrl, FONT_ASSET_VERSION, type FontLoader, type FontFace } from "./react-native/fonts.js";
|
|
36
36
|
export { GraineVoiceLauncher, CAPTION_HISTORY, type GraineVoiceLauncherProps, type GraineVoiceApi, } from "./react-native/voice-launcher.js";
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ export { UNNAMED_TAP, resolveWidgetImages } from "./protocol.js";
|
|
|
21
21
|
export { resolveNativeAppearance, resolveWidgetPalette, } from "./react-native/appearance.js";
|
|
22
22
|
export { RtcVoiceSessionController, RtcVoiceError, } from "./react-native/voice-rtc.js";
|
|
23
23
|
export { GraineAgentBar, GraineLauncher, } from "./react-native/ui.js";
|
|
24
|
-
export { GraineVoiceBar } from "./react-native/voice-bar.js";
|
|
24
|
+
export { GraineVoiceBar, GraineBarAction } from "./react-native/voice-bar.js";
|
|
25
25
|
export { GraineLauncherButton } from "./react-native/fab.js";
|
|
26
26
|
export { faceFor, facesForDesign, fontUrl, FONT_ASSET_VERSION } from "./react-native/fonts.js";
|
|
27
27
|
export { GraineVoiceLauncher, CAPTION_HISTORY, } from "./react-native/voice-launcher.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { type SdkIconName } from "../theme.js";
|
|
2
3
|
import type { GraineVoiceApi } from "./voice-launcher.js";
|
|
3
4
|
export interface GraineVoiceBarProps {
|
|
4
5
|
api: GraineVoiceApi;
|
|
@@ -12,5 +13,12 @@ export interface GraineVoiceBarProps {
|
|
|
12
13
|
bottomOffset: number;
|
|
13
14
|
}) => void;
|
|
14
15
|
name?: string;
|
|
16
|
+
accessory?: React.ReactNode;
|
|
15
17
|
}
|
|
16
|
-
export declare function GraineVoiceBar({ api, onClose, bottomInset, draggable, placement, onPlacementChange, name: nameProp, }: GraineVoiceBarProps): React.JSX.Element;
|
|
18
|
+
export declare function GraineVoiceBar({ api, onClose, bottomInset, draggable, placement, onPlacementChange, name: nameProp, accessory, }: GraineVoiceBarProps): React.JSX.Element;
|
|
19
|
+
export interface GraineBarActionProps {
|
|
20
|
+
label: string;
|
|
21
|
+
icon?: Extract<SdkIconName, "call" | "chat">;
|
|
22
|
+
onPress: () => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function GraineBarAction({ label, icon, onPress }: GraineBarActionProps): React.JSX.Element;
|
|
@@ -55,7 +55,7 @@ function statusOf(api, isChat, chatConnected, connectingText = DEFAULT_LOADER_TE
|
|
|
55
55
|
return { text: "Listening", waiting: null, meter: true };
|
|
56
56
|
return say("Tap to talk");
|
|
57
57
|
}
|
|
58
|
-
export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, placement, onPlacementChange, name: nameProp, }) {
|
|
58
|
+
export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, placement, onPlacementChange, name: nameProp, accessory, }) {
|
|
59
59
|
const ctx = useContext(Ctx);
|
|
60
60
|
const { messages, send, connected: chatConnected, appearance, surface, canSwitch, setSurface, launcherInset } = useGraineAgent();
|
|
61
61
|
const look = useDesign();
|
|
@@ -92,7 +92,7 @@ export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, pl
|
|
|
92
92
|
return (_jsxs(View, { pointerEvents: "box-none", ...drag.panHandlers, onLayout: drag.onLayout, style: {
|
|
93
93
|
position: "absolute", zIndex: 900, gap: 8, left: 12, right: 12,
|
|
94
94
|
bottom: baseBottom + drag.offset + keyboardLift,
|
|
95
|
-
}, children: [_jsx(WidgetGuard, { children: _jsx(GraineWidget, {}) }), isChat ? (_jsx(ChatWindow, { look: look, kit: kit, origin: origin, heading: heading, logoUrl: dash.logoUrl ?? undefined, logoPlate: dash.logoPlate, messages: messages, send: send, online: chatConnected, lifted: keyboardLift > 0, onCollapse: () => (canSwitch ? setSurface("voice") : endCall()) })) : caption ? (_jsx(CaptionCard, { look: look, role: caption.role, text: caption.text, live: !!caption.live })) : null, _jsx(Raised, { t: t, level: "floating", radius: t.shape.radius.xl, children: _jsxs(View, { style: {
|
|
95
|
+
}, children: [_jsx(WidgetGuard, { children: _jsx(GraineWidget, {}) }), isChat ? (_jsx(ChatWindow, { look: look, kit: kit, origin: origin, heading: heading, logoUrl: dash.logoUrl ?? undefined, logoPlate: dash.logoPlate, messages: messages, send: send, online: chatConnected, lifted: keyboardLift > 0, onCollapse: () => (canSwitch ? setSurface("voice") : endCall()) })) : caption ? (_jsx(CaptionCard, { look: look, role: caption.role, text: caption.text, live: !!caption.live })) : null, draws(accessory) ? _jsx(View, { pointerEvents: "box-none", children: accessory }) : null, _jsx(Raised, { t: t, level: "floating", radius: t.shape.radius.xl, children: _jsxs(View, { style: {
|
|
96
96
|
height: BAR_HEIGHT, paddingHorizontal: 9, flexDirection: "row", alignItems: "center", gap: 10,
|
|
97
97
|
backgroundColor: surfaceFill(c.surface, t), borderWidth: t.shape.borderWidth, borderColor: c.border,
|
|
98
98
|
borderRadius: t.shape.radius.xl, ...softShadow(t, "floating"),
|
|
@@ -107,6 +107,34 @@ export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, pl
|
|
|
107
107
|
api.start();
|
|
108
108
|
}, children: _jsx(Icon, { name: api.muted ? "micOff" : "mic", t: t, color: api.muted ? "#FFFFFF" : c.onAccent, kit: kit }) })), _jsx(RoundButton, { label: api.connected ? "End call" : "Close", fill: api.connected ? c.danger : c.accentSoft, onPress: endCall, children: _jsx(Icon, { name: api.connected ? "end" : "close", t: t, color: api.connected ? "#FFFFFF" : c.textMuted, kit: kit }) })] }) })] }));
|
|
109
109
|
}
|
|
110
|
+
export function GraineBarAction({ label, icon = "call", onPress }) {
|
|
111
|
+
const ctx = useContext(Ctx);
|
|
112
|
+
const look = useDesign();
|
|
113
|
+
const t = look.tokens;
|
|
114
|
+
const c = t.colors;
|
|
115
|
+
const kit = ctx?.svg ?? null;
|
|
116
|
+
const unit = t.spacing.unit;
|
|
117
|
+
const outline = t.elevation.hardOffset ? { borderWidth: Math.max(1, t.shape.borderWidth), borderColor: c.text } : {};
|
|
118
|
+
return (_jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: label, hitSlop: 8, onPress: onPress, style: ({ pressed }) => [
|
|
119
|
+
{
|
|
120
|
+
alignSelf: "flex-end",
|
|
121
|
+
flexDirection: "row",
|
|
122
|
+
alignItems: "center",
|
|
123
|
+
gap: unit * 1.5,
|
|
124
|
+
paddingHorizontal: unit * 3.5,
|
|
125
|
+
paddingVertical: unit * 2,
|
|
126
|
+
marginBottom: unit * 2,
|
|
127
|
+
borderRadius: t.shape.radius.xl,
|
|
128
|
+
backgroundColor: c.accent,
|
|
129
|
+
opacity: pressed ? 0.85 : 1,
|
|
130
|
+
},
|
|
131
|
+
outline,
|
|
132
|
+
softShadow(t, "floating"),
|
|
133
|
+
], children: [_jsx(Icon, { name: icon, t: t, color: c.onAccent, kit: kit, size: 16 }), _jsx(Text, { style: { color: c.onAccent, fontSize: t.font.scale.body, ...look.face(t.font.body, t.font.weights.strong) }, children: label })] }));
|
|
134
|
+
}
|
|
135
|
+
function draws(node) {
|
|
136
|
+
return node !== null && node !== undefined && typeof node !== "boolean";
|
|
137
|
+
}
|
|
110
138
|
function softShadow(t, level) {
|
|
111
139
|
return t.elevation.hardOffset ? {} : shadowStyle(t.elevation[level]);
|
|
112
140
|
}
|
|
@@ -5,6 +5,7 @@ import { Ctx, useGraineAgent } from "./index.js";
|
|
|
5
5
|
import { requestMicrophonePermission } from "./permissions.js";
|
|
6
6
|
export const CAPTION_HISTORY = 12;
|
|
7
7
|
export const SLOW_CONNECT_MS = 4000;
|
|
8
|
+
const CALL_IN_PROGRESS = "A call is already in progress.";
|
|
8
9
|
export function GraineVoiceLauncher({ webView: WebView, autoStart = false, active: activeProp = true, transport, onTransport, onCaption, onCallState, onMicDenied, onError, timing, onEnded, children, }) {
|
|
9
10
|
const { client, appearance, surface, surfaces } = useGraineAgent();
|
|
10
11
|
const endOnBackground = useContext(Ctx)?.endOnBackground ?? true;
|
|
@@ -214,7 +215,18 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
214
215
|
pushContext();
|
|
215
216
|
return client.on("screen", () => pushContext());
|
|
216
217
|
}, [client, ready, pushContext]);
|
|
218
|
+
const refusedForNativeCall = useCallback(() => {
|
|
219
|
+
if (!client.nativeCallLive)
|
|
220
|
+
return false;
|
|
221
|
+
pendingStartRef.current = false;
|
|
222
|
+
startedRef.current = false;
|
|
223
|
+
setConnecting(false);
|
|
224
|
+
onErrorRef.current?.(CALL_IN_PROGRESS);
|
|
225
|
+
return true;
|
|
226
|
+
}, [client]);
|
|
217
227
|
const beginCall = useCallback(async () => {
|
|
228
|
+
if (refusedForNativeCall())
|
|
229
|
+
return;
|
|
218
230
|
endedNotifiedRef.current = false;
|
|
219
231
|
if (client.isConnected) {
|
|
220
232
|
console.warn("[graine] The provider has its own connection open while GraineVoiceLauncher " +
|
|
@@ -228,8 +240,24 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
228
240
|
client.emit("mic_denied", { reason: "NotAllowedError" });
|
|
229
241
|
return;
|
|
230
242
|
}
|
|
243
|
+
if (refusedForNativeCall())
|
|
244
|
+
return;
|
|
231
245
|
post({ type: "graine:start-call" });
|
|
232
|
-
}, [client, ensureMic, onMicDenied, post]);
|
|
246
|
+
}, [client, ensureMic, onMicDenied, post, refusedForNativeCall]);
|
|
247
|
+
const connectingRef = useRef(false);
|
|
248
|
+
connectingRef.current = connecting;
|
|
249
|
+
useEffect(() => client.on("native_call", (e) => {
|
|
250
|
+
if (!e?.live)
|
|
251
|
+
return;
|
|
252
|
+
if (pendingStartRef.current) {
|
|
253
|
+
pendingStartRef.current = false;
|
|
254
|
+
setConnecting(false);
|
|
255
|
+
}
|
|
256
|
+
if (connectedRef.current || connectingRef.current) {
|
|
257
|
+
post({ type: "graine:end-call" });
|
|
258
|
+
armEndWatchdog();
|
|
259
|
+
}
|
|
260
|
+
}), [client, post, armEndWatchdog]);
|
|
233
261
|
useEffect(() => {
|
|
234
262
|
if (!connected)
|
|
235
263
|
return;
|
|
@@ -425,6 +453,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
425
453
|
setCaptionsOn,
|
|
426
454
|
clearCaptions: useCallback(() => setCaptions([]), []),
|
|
427
455
|
start: useCallback(() => {
|
|
456
|
+
if (refusedForNativeCall())
|
|
457
|
+
return;
|
|
428
458
|
setConnecting(true);
|
|
429
459
|
setMicDenied(false);
|
|
430
460
|
setFault(null);
|
|
@@ -438,7 +468,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
438
468
|
return;
|
|
439
469
|
}
|
|
440
470
|
void beginCall();
|
|
441
|
-
}, [beginCall, ready]),
|
|
471
|
+
}, [beginCall, ready, refusedForNativeCall]),
|
|
442
472
|
end: useCallback(() => { post({ type: "graine:end-call" }); armEndWatchdog(); }, [post, armEndWatchdog]),
|
|
443
473
|
connectivity,
|
|
444
474
|
retry: restartLoad,
|
|
@@ -474,6 +504,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
474
504
|
if (autoStart) {
|
|
475
505
|
if (startedRef.current)
|
|
476
506
|
return;
|
|
507
|
+
if (refusedForNativeCall())
|
|
508
|
+
return;
|
|
477
509
|
setConnecting(true);
|
|
478
510
|
setCaptions([]);
|
|
479
511
|
if (ready) {
|
package/package.json
CHANGED