@graineai/inapp-react-native 0.33.3 → 0.33.4
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/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/protocol.d.ts +54 -0
- package/dist/protocol.js +83 -0
- package/dist/react-native/index.d.ts +2 -0
- package/dist/react-native/index.js +2 -1
- package/dist/react-native/voice-launcher.d.ts +13 -1
- package/dist/react-native/voice-launcher.js +104 -4
- package/dist/react-native/widget.js +104 -37
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type { ActionHandler, AppActionRequest, AppActionResult, AppEvent, ScreenContext, ScreenField, WidgetSurface, } from "./protocol.js";
|
|
2
2
|
export { widgetSurface } from "./protocol.js";
|
|
3
|
+
export { WIDGET_PRESETS, WIDGET_IMAGE_SIZES, WIDGET_DENSITIES, WIDGET_BUTTONS, WIDGET_SURFACE_CHOICES, DEFAULT_WIDGET_PRESET, WIDGET_PLATFORMS, PLATFORM_GRAMMAR, normaliseWidgetStyle, resolveWidgetLook, resolvePlatform, } from "./protocol.js";
|
|
4
|
+
export type { WidgetStyle, WidgetLook, WidgetPreset, WidgetImageSize, WidgetDensity, WidgetButton, WidgetPlatform, PlatformGrammar } from "./protocol.js";
|
|
3
5
|
export { GraineWidget } from "./react-native/widget.js";
|
|
4
6
|
export type { GraineWidgetProps, GraineWidgetTheme } from "./react-native/widget.js";
|
|
5
7
|
export { GraineInAppClient, type GraineInAppOptions, type SessionConfig, type ActionDiagnosis, } from "./client.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { widgetSurface } from "./protocol.js";
|
|
2
|
+
export { WIDGET_PRESETS, WIDGET_IMAGE_SIZES, WIDGET_DENSITIES, WIDGET_BUTTONS, WIDGET_SURFACE_CHOICES, DEFAULT_WIDGET_PRESET, WIDGET_PLATFORMS, PLATFORM_GRAMMAR, normaliseWidgetStyle, resolveWidgetLook, resolvePlatform, } from "./protocol.js";
|
|
2
3
|
export { GraineWidget } from "./react-native/widget.js";
|
|
3
4
|
export { GraineInAppClient, } from "./client.js";
|
|
4
5
|
export { FrictionDetector, resolveFriction, rulesFor, DEFAULT_FRICTION, } from "./friction.js";
|
package/dist/protocol.d.ts
CHANGED
|
@@ -64,3 +64,57 @@ export declare const UNNAMED_TAP = "tap:screen";
|
|
|
64
64
|
export declare function resolveWidgetImages(data: Record<string, unknown>, baseUrl: string): Record<string, unknown>;
|
|
65
65
|
export type WidgetSurface = "screen" | "sheet" | "card";
|
|
66
66
|
export declare function widgetSurface(kind: string | null | undefined): WidgetSurface;
|
|
67
|
+
export type WidgetPreset = "gallery" | "cards" | "list" | "spotlight" | "compact";
|
|
68
|
+
export type WidgetImageSize = "small" | "medium" | "large";
|
|
69
|
+
export type WidgetDensity = "comfortable" | "compact";
|
|
70
|
+
export type WidgetButton = "filled" | "outline" | "row";
|
|
71
|
+
export type WidgetPlatform = "auto" | "ios" | "material" | "oneui";
|
|
72
|
+
export interface WidgetStyle {
|
|
73
|
+
preset?: WidgetPreset;
|
|
74
|
+
imageSize?: WidgetImageSize;
|
|
75
|
+
density?: WidgetDensity;
|
|
76
|
+
button?: WidgetButton;
|
|
77
|
+
facts?: boolean;
|
|
78
|
+
columns?: 1 | 2;
|
|
79
|
+
surface?: "auto" | WidgetSurface;
|
|
80
|
+
radius?: number;
|
|
81
|
+
accent?: string;
|
|
82
|
+
platform?: WidgetPlatform;
|
|
83
|
+
}
|
|
84
|
+
export declare const WIDGET_PRESETS: ReadonlyArray<WidgetPreset>;
|
|
85
|
+
export declare const WIDGET_IMAGE_SIZES: ReadonlyArray<WidgetImageSize>;
|
|
86
|
+
export declare const WIDGET_DENSITIES: ReadonlyArray<WidgetDensity>;
|
|
87
|
+
export declare const WIDGET_BUTTONS: ReadonlyArray<WidgetButton>;
|
|
88
|
+
export declare const WIDGET_SURFACE_CHOICES: ReadonlyArray<"auto" | WidgetSurface>;
|
|
89
|
+
export declare const WIDGET_PLATFORMS: ReadonlyArray<WidgetPlatform>;
|
|
90
|
+
export declare const DEFAULT_WIDGET_PRESET: WidgetPreset;
|
|
91
|
+
export declare function normaliseWidgetStyle(raw: unknown): WidgetStyle;
|
|
92
|
+
export interface WidgetLook {
|
|
93
|
+
preset: WidgetPreset;
|
|
94
|
+
imageSize: WidgetImageSize;
|
|
95
|
+
density: WidgetDensity;
|
|
96
|
+
button: WidgetButton;
|
|
97
|
+
facts: boolean;
|
|
98
|
+
columns: 1 | 2;
|
|
99
|
+
surface: WidgetSurface;
|
|
100
|
+
radius?: number;
|
|
101
|
+
accent?: string;
|
|
102
|
+
platform: WidgetPlatform;
|
|
103
|
+
}
|
|
104
|
+
export declare function resolveWidgetLook(style: WidgetStyle | null | undefined, kind: string | null | undefined): WidgetLook;
|
|
105
|
+
export interface PlatformGrammar {
|
|
106
|
+
radius: number;
|
|
107
|
+
chipRadius: number;
|
|
108
|
+
buttonRadius: number;
|
|
109
|
+
pillButtons: boolean;
|
|
110
|
+
separatorInset: number;
|
|
111
|
+
grabber: {
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
};
|
|
115
|
+
titleWeight: "600" | "700" | "500";
|
|
116
|
+
padBoost: number;
|
|
117
|
+
chipOutlined: boolean;
|
|
118
|
+
}
|
|
119
|
+
export declare const PLATFORM_GRAMMAR: Record<Exclude<WidgetPlatform, "auto">, PlatformGrammar>;
|
|
120
|
+
export declare function resolvePlatform(platform: WidgetPlatform | undefined, os: string | undefined, manufacturer?: string | null): Exclude<WidgetPlatform, "auto">;
|
package/dist/protocol.js
CHANGED
|
@@ -41,3 +41,86 @@ const SURFACE = {
|
|
|
41
41
|
export function widgetSurface(kind) {
|
|
42
42
|
return SURFACE[String(kind ?? "").trim().toLowerCase()] ?? "card";
|
|
43
43
|
}
|
|
44
|
+
export const WIDGET_PRESETS = ["gallery", "cards", "list", "spotlight", "compact"];
|
|
45
|
+
export const WIDGET_IMAGE_SIZES = ["small", "medium", "large"];
|
|
46
|
+
export const WIDGET_DENSITIES = ["comfortable", "compact"];
|
|
47
|
+
export const WIDGET_BUTTONS = ["filled", "outline", "row"];
|
|
48
|
+
export const WIDGET_SURFACE_CHOICES = ["auto", "screen", "sheet", "card"];
|
|
49
|
+
export const WIDGET_PLATFORMS = ["auto", "ios", "material", "oneui"];
|
|
50
|
+
export const DEFAULT_WIDGET_PRESET = "cards";
|
|
51
|
+
export function normaliseWidgetStyle(raw) {
|
|
52
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
53
|
+
return {};
|
|
54
|
+
const r = raw;
|
|
55
|
+
const out = {};
|
|
56
|
+
const pick = (v, allowed) => {
|
|
57
|
+
const s = String(v ?? "").trim().toLowerCase();
|
|
58
|
+
return allowed.includes(s) ? s : undefined;
|
|
59
|
+
};
|
|
60
|
+
const preset = pick(r.preset, WIDGET_PRESETS);
|
|
61
|
+
if (preset)
|
|
62
|
+
out.preset = preset;
|
|
63
|
+
const imageSize = pick(r.imageSize, WIDGET_IMAGE_SIZES);
|
|
64
|
+
if (imageSize)
|
|
65
|
+
out.imageSize = imageSize;
|
|
66
|
+
const density = pick(r.density, WIDGET_DENSITIES);
|
|
67
|
+
if (density)
|
|
68
|
+
out.density = density;
|
|
69
|
+
const button = pick(r.button, WIDGET_BUTTONS);
|
|
70
|
+
if (button)
|
|
71
|
+
out.button = button;
|
|
72
|
+
if (typeof r.facts === "boolean")
|
|
73
|
+
out.facts = r.facts;
|
|
74
|
+
if (r.columns === 2 || r.columns === "2")
|
|
75
|
+
out.columns = 2;
|
|
76
|
+
else if (r.columns === 1 || r.columns === "1")
|
|
77
|
+
out.columns = 1;
|
|
78
|
+
const surface = pick(r.surface, WIDGET_SURFACE_CHOICES);
|
|
79
|
+
if (surface)
|
|
80
|
+
out.surface = surface;
|
|
81
|
+
if (typeof r.radius === "number" && Number.isFinite(r.radius))
|
|
82
|
+
out.radius = Math.max(0, Math.min(24, Math.round(r.radius)));
|
|
83
|
+
if (typeof r.accent === "string" && /^#[0-9a-f]{6}$/i.test(r.accent.trim()))
|
|
84
|
+
out.accent = r.accent.trim().toUpperCase();
|
|
85
|
+
const platform = pick(r.platform, WIDGET_PLATFORMS);
|
|
86
|
+
if (platform)
|
|
87
|
+
out.platform = platform;
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
const PRESET_DEFAULTS = {
|
|
91
|
+
gallery: { imageSize: "large", button: "filled" },
|
|
92
|
+
cards: { imageSize: "medium", button: "filled" },
|
|
93
|
+
list: { imageSize: "small", button: "row" },
|
|
94
|
+
spotlight: { imageSize: "large", button: "filled" },
|
|
95
|
+
compact: { imageSize: "small", button: "outline" },
|
|
96
|
+
};
|
|
97
|
+
export function resolveWidgetLook(style, kind) {
|
|
98
|
+
const s = normaliseWidgetStyle(style);
|
|
99
|
+
const preset = s.preset ?? DEFAULT_WIDGET_PRESET;
|
|
100
|
+
const d = PRESET_DEFAULTS[preset];
|
|
101
|
+
const surface = s.surface && s.surface !== "auto" ? s.surface : widgetSurface(kind);
|
|
102
|
+
return {
|
|
103
|
+
preset,
|
|
104
|
+
imageSize: s.imageSize ?? d.imageSize,
|
|
105
|
+
density: s.density ?? "comfortable",
|
|
106
|
+
button: s.button ?? d.button,
|
|
107
|
+
facts: s.facts ?? true,
|
|
108
|
+
columns: (preset === "gallery" || preset === "cards") && s.columns === 2 ? 2 : 1,
|
|
109
|
+
surface,
|
|
110
|
+
...(s.radius !== undefined ? { radius: s.radius } : {}),
|
|
111
|
+
...(s.accent ? { accent: s.accent } : {}),
|
|
112
|
+
platform: s.platform ?? "auto",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export const PLATFORM_GRAMMAR = {
|
|
116
|
+
ios: { radius: 12, chipRadius: 8, buttonRadius: 12, pillButtons: false, separatorInset: 16, grabber: { width: 36, height: 5 }, titleWeight: "600", padBoost: 0, chipOutlined: false },
|
|
117
|
+
material: { radius: 16, chipRadius: 8, buttonRadius: 20, pillButtons: true, separatorInset: 0, grabber: { width: 32, height: 4 }, titleWeight: "500", padBoost: 0, chipOutlined: true },
|
|
118
|
+
oneui: { radius: 26, chipRadius: 999, buttonRadius: 999, pillButtons: true, separatorInset: 0, grabber: { width: 40, height: 4 }, titleWeight: "700", padBoost: 2, chipOutlined: false },
|
|
119
|
+
};
|
|
120
|
+
export function resolvePlatform(platform, os, manufacturer) {
|
|
121
|
+
if (platform && platform !== "auto")
|
|
122
|
+
return platform;
|
|
123
|
+
if (os === "android")
|
|
124
|
+
return /samsung/i.test(String(manufacturer ?? "")) ? "oneui" : "material";
|
|
125
|
+
return "ios";
|
|
126
|
+
}
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { GraineInAppClient, type GraineInAppOptions } from "../client.js";
|
|
3
3
|
import { type AudioAdapter, type VoiceSessionOptions } from "../voice.js";
|
|
4
4
|
import type { ActionHandler, ScreenContext, ScreenField } from "../protocol.js";
|
|
5
|
+
import { type WidgetStyle } from "../protocol.js";
|
|
5
6
|
export { GraineFab, GraineTouchCapture } from "./fab.js";
|
|
6
7
|
export type { GraineFabProps, GraineTouchCaptureProps } from "./fab.js";
|
|
7
8
|
export { widgetSurface } from "../protocol.js";
|
|
@@ -73,6 +74,7 @@ export interface GraineWidget {
|
|
|
73
74
|
kind: string;
|
|
74
75
|
data: Record<string, unknown>;
|
|
75
76
|
fallbackText: string;
|
|
77
|
+
style?: WidgetStyle;
|
|
76
78
|
}
|
|
77
79
|
export interface Caption {
|
|
78
80
|
role: "agent" | "customer";
|
|
@@ -3,7 +3,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
|
|
3
3
|
import { AppState } from "react-native";
|
|
4
4
|
import { GraineInAppClient } from "../client.js";
|
|
5
5
|
import { VoiceSession } from "../voice.js";
|
|
6
|
-
import { resolveWidgetImages } from "../protocol.js";
|
|
6
|
+
import { resolveWidgetImages, normaliseWidgetStyle } from "../protocol.js";
|
|
7
7
|
import { GraineFab, GraineTouchCapture } from "./fab.js";
|
|
8
8
|
export { GraineFab, GraineTouchCapture } from "./fab.js";
|
|
9
9
|
export { widgetSurface } from "../protocol.js";
|
|
@@ -396,6 +396,7 @@ export function useGraineWidget() {
|
|
|
396
396
|
kind: String(w.kind || ""),
|
|
397
397
|
data: resolveWidgetImages((w.data || {}), client.opts.baseUrl),
|
|
398
398
|
fallbackText: String(w.fallback_text || ""),
|
|
399
|
+
style: normaliseWidgetStyle(w.style),
|
|
399
400
|
}
|
|
400
401
|
: null, [client]);
|
|
401
402
|
const [widget, setWidget] = useState(() => adopt(client.pendingWidget));
|
|
@@ -23,9 +23,21 @@ export interface GraineVoiceLauncherProps {
|
|
|
23
23
|
onEnded?: () => void;
|
|
24
24
|
onMicDenied?: (reason: string) => void;
|
|
25
25
|
onError?: (message: string) => void;
|
|
26
|
+
timing?: {
|
|
27
|
+
readyTimeoutMs?: number;
|
|
28
|
+
retryDelaysMs?: number[];
|
|
29
|
+
endWatchdogMs?: number;
|
|
30
|
+
};
|
|
26
31
|
children?: (api: GraineVoiceApi) => React.ReactNode;
|
|
27
32
|
}
|
|
33
|
+
export interface GraineConnectivity {
|
|
34
|
+
state: "loading" | "ready" | "retrying" | "failed";
|
|
35
|
+
attempt: number;
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
28
38
|
export interface GraineVoiceApi {
|
|
39
|
+
connectivity: GraineConnectivity;
|
|
40
|
+
retry: () => void;
|
|
29
41
|
connected: boolean;
|
|
30
42
|
connecting: boolean;
|
|
31
43
|
muted: boolean;
|
|
@@ -40,4 +52,4 @@ export interface GraineVoiceApi {
|
|
|
40
52
|
clearCaptions: () => void;
|
|
41
53
|
}
|
|
42
54
|
export declare const CAPTION_HISTORY = 12;
|
|
43
|
-
export declare function GraineVoiceLauncher({ webView: WebView, autoStart, active, transport, onTransport, onCaption, onCallState, onMicDenied, onError, onEnded, children, }: GraineVoiceLauncherProps): React.JSX.Element;
|
|
55
|
+
export declare function GraineVoiceLauncher({ webView: WebView, autoStart, active, transport, onTransport, onCaption, onCallState, onMicDenied, onError, timing, onEnded, children, }: GraineVoiceLauncherProps): React.JSX.Element;
|
|
@@ -4,7 +4,7 @@ import { AppState, View } from "react-native";
|
|
|
4
4
|
import { useGraineAgent } from "./index.js";
|
|
5
5
|
import { requestMicrophonePermission } from "./permissions.js";
|
|
6
6
|
export const CAPTION_HISTORY = 12;
|
|
7
|
-
export function GraineVoiceLauncher({ webView: WebView, autoStart = false, active = true, transport, onTransport, onCaption, onCallState, onMicDenied, onError, onEnded, children, }) {
|
|
7
|
+
export function GraineVoiceLauncher({ webView: WebView, autoStart = false, active = true, transport, onTransport, onCaption, onCallState, onMicDenied, onError, timing, onEnded, children, }) {
|
|
8
8
|
const { client, appearance } = useGraineAgent();
|
|
9
9
|
const ref = useRef(null);
|
|
10
10
|
const startedRef = useRef(false);
|
|
@@ -38,6 +38,88 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
38
38
|
const [voiceAgentId, setVoiceAgentId] = useState(client.config ? client.config.webcallAgentId || client.config.agentId : null);
|
|
39
39
|
const onErrorRef = useRef(onError);
|
|
40
40
|
onErrorRef.current = onError;
|
|
41
|
+
const READY_TIMEOUT_MS = timing?.readyTimeoutMs ?? 15000;
|
|
42
|
+
const RETRY_DELAYS_MS = timing?.retryDelaysMs ?? [1000, 2000, 4000, 8000, 15000, 15000];
|
|
43
|
+
const END_WATCHDOG_MS = timing?.endWatchdogMs ?? 4000;
|
|
44
|
+
const [connectivity, setConnectivity] = useState({ state: "loading", attempt: 0 });
|
|
45
|
+
const attemptRef = useRef(0);
|
|
46
|
+
const retryTimerRef = useRef(null);
|
|
47
|
+
const readyTimerRef = useRef(null);
|
|
48
|
+
const endWatchdogRef = useRef(null);
|
|
49
|
+
const connectedRef = useRef(false);
|
|
50
|
+
useEffect(() => { connectedRef.current = connected; }, [connected]);
|
|
51
|
+
const endedNotifiedRef = useRef(true);
|
|
52
|
+
const onEndedRef = useRef(onEnded);
|
|
53
|
+
onEndedRef.current = onEnded;
|
|
54
|
+
const clearTimer = (r) => {
|
|
55
|
+
if (r.current) {
|
|
56
|
+
clearTimeout(r.current);
|
|
57
|
+
r.current = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const scheduleRetry = useCallback((reason) => {
|
|
61
|
+
clearTimer(readyTimerRef);
|
|
62
|
+
if (connectedRef.current)
|
|
63
|
+
return;
|
|
64
|
+
const attempt = attemptRef.current;
|
|
65
|
+
if (attempt >= RETRY_DELAYS_MS.length) {
|
|
66
|
+
setConnectivity({ state: "failed", attempt, reason });
|
|
67
|
+
setConnecting(false);
|
|
68
|
+
pendingStartRef.current = false;
|
|
69
|
+
onErrorRef.current?.("The assistant could not load. Check your connection and try again.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const base = RETRY_DELAYS_MS[attempt];
|
|
73
|
+
const delay = Math.round(base * (0.75 + Math.random() * 0.5));
|
|
74
|
+
attemptRef.current = attempt + 1;
|
|
75
|
+
setConnectivity({ state: "retrying", attempt: attempt + 1, reason });
|
|
76
|
+
setReady(false);
|
|
77
|
+
clearTimer(retryTimerRef);
|
|
78
|
+
retryTimerRef.current = setTimeout(() => {
|
|
79
|
+
retryTimerRef.current = null;
|
|
80
|
+
try {
|
|
81
|
+
ref.current?.reload();
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
}, delay);
|
|
85
|
+
}, []);
|
|
86
|
+
const armReadyTimer = useCallback(() => {
|
|
87
|
+
clearTimer(readyTimerRef);
|
|
88
|
+
readyTimerRef.current = setTimeout(() => {
|
|
89
|
+
readyTimerRef.current = null;
|
|
90
|
+
scheduleRetry("ready-timeout");
|
|
91
|
+
}, READY_TIMEOUT_MS);
|
|
92
|
+
}, [scheduleRetry]);
|
|
93
|
+
const restartLoad = useCallback(() => {
|
|
94
|
+
clearTimer(retryTimerRef);
|
|
95
|
+
attemptRef.current = 0;
|
|
96
|
+
setConnectivity({ state: "loading", attempt: 0 });
|
|
97
|
+
setReady(false);
|
|
98
|
+
try {
|
|
99
|
+
ref.current?.reload();
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
armReadyTimer();
|
|
103
|
+
}, [armReadyTimer]);
|
|
104
|
+
const forceEnd = useCallback((why) => {
|
|
105
|
+
clearTimer(endWatchdogRef);
|
|
106
|
+
setConnected(false);
|
|
107
|
+
setConnecting(false);
|
|
108
|
+
connectedRef.current = false;
|
|
109
|
+
if (!endedNotifiedRef.current) {
|
|
110
|
+
endedNotifiedRef.current = true;
|
|
111
|
+
console.warn(`[graine] the page did not confirm the call ended (${why}); ending it locally`);
|
|
112
|
+
onEndedRef.current?.();
|
|
113
|
+
}
|
|
114
|
+
restartLoad();
|
|
115
|
+
}, [restartLoad]);
|
|
116
|
+
const armEndWatchdog = useCallback(() => {
|
|
117
|
+
clearTimer(endWatchdogRef);
|
|
118
|
+
if (!connectedRef.current)
|
|
119
|
+
return;
|
|
120
|
+
endWatchdogRef.current = setTimeout(() => forceEnd("watchdog"), END_WATCHDOG_MS);
|
|
121
|
+
}, [forceEnd]);
|
|
122
|
+
useEffect(() => () => { clearTimer(retryTimerRef); clearTimer(readyTimerRef); clearTimer(endWatchdogRef); }, []);
|
|
41
123
|
useEffect(() => {
|
|
42
124
|
if (voiceAgentId)
|
|
43
125
|
return;
|
|
@@ -75,6 +157,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
75
157
|
return client.on("screen", () => pushContext());
|
|
76
158
|
}, [client, ready, pushContext]);
|
|
77
159
|
const beginCall = useCallback(async () => {
|
|
160
|
+
endedNotifiedRef.current = false;
|
|
78
161
|
if (client.isConnected) {
|
|
79
162
|
console.warn("[graine] The provider has its own connection open while GraineVoiceLauncher " +
|
|
80
163
|
"is starting a call. Pass autoConnect={false} to <GraineProvider> — otherwise " +
|
|
@@ -132,6 +215,10 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
132
215
|
}
|
|
133
216
|
return;
|
|
134
217
|
case "graine:ready":
|
|
218
|
+
clearTimer(readyTimerRef);
|
|
219
|
+
clearTimer(retryTimerRef);
|
|
220
|
+
attemptRef.current = 0;
|
|
221
|
+
setConnectivity({ state: "ready", attempt: 0 });
|
|
135
222
|
setReady(true);
|
|
136
223
|
pushContext();
|
|
137
224
|
if ((autoStart || pendingStartRef.current) && !startedRef.current && activeRef.current) {
|
|
@@ -144,6 +231,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
144
231
|
}
|
|
145
232
|
return;
|
|
146
233
|
case "graine:call":
|
|
234
|
+
if (msg.connected)
|
|
235
|
+
endedNotifiedRef.current = false;
|
|
147
236
|
setConnected(!!msg.connected);
|
|
148
237
|
setConnecting(!!msg.connecting);
|
|
149
238
|
if (!msg.connected && !msg.connecting)
|
|
@@ -164,9 +253,14 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
164
253
|
return;
|
|
165
254
|
}
|
|
166
255
|
case "graine:ended":
|
|
256
|
+
clearTimer(endWatchdogRef);
|
|
167
257
|
setConnected(false);
|
|
168
258
|
setConnecting(false);
|
|
169
|
-
|
|
259
|
+
connectedRef.current = false;
|
|
260
|
+
if (!endedNotifiedRef.current) {
|
|
261
|
+
endedNotifiedRef.current = true;
|
|
262
|
+
onEnded?.();
|
|
263
|
+
}
|
|
170
264
|
return;
|
|
171
265
|
case "graine:mic-denied":
|
|
172
266
|
client.emit("mic_denied", { reason: String(msg.reason || "NotAllowedError") });
|
|
@@ -211,7 +305,9 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
211
305
|
}
|
|
212
306
|
void beginCall();
|
|
213
307
|
}, [beginCall, ready]),
|
|
214
|
-
end: useCallback(() => post({ type: "graine:end-call" }), [post]),
|
|
308
|
+
end: useCallback(() => { post({ type: "graine:end-call" }); armEndWatchdog(); }, [post, armEndWatchdog]),
|
|
309
|
+
connectivity,
|
|
310
|
+
retry: restartLoad,
|
|
215
311
|
setMuted: useCallback((m) => {
|
|
216
312
|
setMutedState(m);
|
|
217
313
|
post({ type: "graine:set-muted", muted: m });
|
|
@@ -223,6 +319,9 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
223
319
|
const first = activeSeenRef.current === null;
|
|
224
320
|
activeSeenRef.current = active;
|
|
225
321
|
if (active) {
|
|
322
|
+
if (attemptRef.current >= RETRY_DELAYS_MS.length) {
|
|
323
|
+
restartLoad();
|
|
324
|
+
}
|
|
226
325
|
if (autoStart) {
|
|
227
326
|
if (startedRef.current)
|
|
228
327
|
return;
|
|
@@ -243,6 +342,7 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
243
342
|
if (first)
|
|
244
343
|
return;
|
|
245
344
|
post({ type: "graine:end-call" });
|
|
345
|
+
armEndWatchdog();
|
|
246
346
|
startedRef.current = false;
|
|
247
347
|
pendingStartRef.current = false;
|
|
248
348
|
setConnected(false);
|
|
@@ -286,5 +386,5 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
286
386
|
(mountedIdleRef.current ? "&idle=1" : "") +
|
|
287
387
|
`&pk=${encodeURIComponent(client.opts.publishableKey)}` +
|
|
288
388
|
(appearance?.accent ? `&accent=${encodeURIComponent(String(appearance.accent).replace("#", ""))}` : "");
|
|
289
|
-
return (_jsxs(_Fragment, { children: [_jsx(View, { style: { position: "absolute", width: 1, height: 1, opacity: 0, bottom: 0, left: 0 }, pointerEvents: "none", children: _jsx(WebView, { ref: ref, source: { uri: src }, originWhitelist: ["*"], javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, mediaCapturePermissionGrantType: "grant", onMessage: onMessage, style: { width: 1, height: 1, backgroundColor: "transparent" } }) }), children?.(api)] }));
|
|
389
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: { position: "absolute", width: 1, height: 1, opacity: 0, bottom: 0, left: 0 }, pointerEvents: "none", children: _jsx(WebView, { ref: ref, source: { uri: src }, originWhitelist: ["*"], javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, mediaCapturePermissionGrantType: "grant", onMessage: onMessage, onLoadStart: armReadyTimer, onError: (e) => scheduleRetry(`load-error:${e?.nativeEvent?.code ?? ""}`), onHttpError: (e) => scheduleRetry(`http-${e?.nativeEvent?.statusCode ?? ""}`), onRenderProcessGone: () => scheduleRetry("render-process-gone"), style: { width: 1, height: 1, backgroundColor: "transparent" } }) }), children?.(api)] }));
|
|
290
390
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useContext } from "react";
|
|
3
|
-
import { Image, Modal, Pressable, ScrollView, Text, View, useColorScheme } from "react-native";
|
|
2
|
+
import { useContext, useState } from "react";
|
|
3
|
+
import { Image, Modal, Platform, Pressable, ScrollView, Text, View, useColorScheme } from "react-native";
|
|
4
4
|
import { Ctx, useGraineWidget } from "./index.js";
|
|
5
5
|
import { resolveNativeAppearance, resolveWidgetPalette } from "./appearance.js";
|
|
6
|
-
import {
|
|
6
|
+
import { PLATFORM_GRAMMAR, resolvePlatform, resolveWidgetLook } from "../protocol.js";
|
|
7
7
|
function tint(hex, alpha) {
|
|
8
8
|
const m = /^#?([0-9a-f]{6})$/i.exec(String(hex).trim());
|
|
9
9
|
if (!m)
|
|
@@ -11,35 +11,74 @@ function tint(hex, alpha) {
|
|
|
11
11
|
const n = parseInt(m[1], 16);
|
|
12
12
|
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
const HERO_HEIGHT = { small: 100, medium: 140, large: 180 };
|
|
15
|
+
const THUMB = { small: 56, medium: 72, large: 96 };
|
|
16
|
+
const LIST_THUMB = { small: 36, medium: 44, large: 56 };
|
|
17
|
+
function WidgetRow({ row, variant, look, grammar, theme, accent, radius, onPick, }) {
|
|
15
18
|
const r = Math.max(10, radius - 6);
|
|
16
|
-
|
|
19
|
+
const tight = look.density === "compact";
|
|
20
|
+
const pad = (variant === "list" ? (tight ? 6 : 9) : (tight ? 7 : 10)) + grammar.padBoost;
|
|
21
|
+
const gap = tight ? 6 : 10;
|
|
22
|
+
const rowIsButton = look.button === "row";
|
|
23
|
+
const outline = look.button === "outline";
|
|
24
|
+
const label = row.unavailable ? "Unavailable" : row.selectLabel;
|
|
25
|
+
const a11y = `${row.selectLabel}: ${row.title}`;
|
|
26
|
+
const badge = row.subtitle ? (_jsx(View, { style: { flexDirection: "row" }, children: _jsx(Text, { style: {
|
|
27
|
+
color: accent, fontSize: 10.5, fontWeight: "800", letterSpacing: 0.4,
|
|
28
|
+
textTransform: "uppercase", backgroundColor: tint(accent, 0.15),
|
|
29
|
+
borderRadius: 999, paddingHorizontal: 8, paddingVertical: 3, overflow: "hidden",
|
|
30
|
+
}, children: row.subtitle }) })) : null;
|
|
31
|
+
const titleLine = (big) => (_jsxs(View, { style: { flexDirection: "row", alignItems: "baseline", gap: 8 }, children: [_jsx(Text, { numberOfLines: 2, style: { color: theme.text, fontSize: big ? 16.5 : (variant === "list" ? 13.5 : 14.5), fontWeight: grammar.titleWeight, flex: 1 }, children: row.title }), row.price ? (_jsx(Text, { style: {
|
|
32
|
+
color: theme.text, fontSize: big ? 17 : (variant === "list" ? 13.5 : 14.5), fontWeight: "800",
|
|
33
|
+
fontVariant: ["tabular-nums"],
|
|
34
|
+
}, children: row.price })) : null] }));
|
|
35
|
+
const facts = look.facts && row.facts.length ? (_jsx(View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 6, marginTop: 2 }, children: (variant === "list" ? row.facts.slice(0, 2) : row.facts).map((f, i) => (_jsx(Text, { style: {
|
|
36
|
+
color: theme.muted, fontSize: variant === "list" ? 10.5 : 11.5, fontWeight: "600",
|
|
37
|
+
backgroundColor: grammar.chipOutlined ? "transparent" : tint(theme.border, 0.35),
|
|
38
|
+
borderWidth: grammar.chipOutlined ? 1 : 0, borderColor: theme.border,
|
|
39
|
+
borderRadius: grammar.chipRadius, paddingHorizontal: variant === "list" ? 6 : 8, paddingVertical: variant === "list" ? 2 : 4, overflow: "hidden",
|
|
40
|
+
}, children: f }, `${f}-${i}`))) })) : null;
|
|
41
|
+
const button = (wide) => (_jsx(Pressable, { disabled: row.unavailable, onPress: onPick, accessibilityRole: "button", accessibilityLabel: a11y, hitSlop: wide ? undefined : 8, style: {
|
|
42
|
+
minHeight: 44, justifyContent: "center", alignItems: "center",
|
|
43
|
+
borderRadius: wide ? grammar.buttonRadius : (grammar.pillButtons ? 999 : grammar.buttonRadius), paddingHorizontal: wide ? 12 : 14,
|
|
44
|
+
marginTop: wide ? 8 : 0,
|
|
45
|
+
backgroundColor: row.unavailable || outline ? "transparent" : accent,
|
|
46
|
+
borderWidth: row.unavailable || outline ? 1.5 : 0,
|
|
47
|
+
borderColor: row.unavailable ? theme.border : accent,
|
|
48
|
+
}, children: _jsx(Text, { style: {
|
|
49
|
+
color: row.unavailable ? theme.muted : (outline ? accent : theme.onAccent),
|
|
50
|
+
fontSize: wide ? 14 : 12.5, fontWeight: "700",
|
|
51
|
+
}, children: label }) }));
|
|
52
|
+
const image = (style) => row.image ? (_jsx(Image, { source: { uri: row.image }, accessibilityIgnoresInvertColors: true, resizeMode: "cover", style: { borderRadius: Math.max(8, radius - 8), backgroundColor: theme.border, ...style } })) : null;
|
|
53
|
+
const wrap = (children, style) => rowIsButton ? (_jsx(Pressable, { disabled: row.unavailable, onPress: onPick, accessibilityRole: "button", accessibilityLabel: a11y, style: style, children: children })) : (_jsx(View, { style: style, children: children }));
|
|
54
|
+
if (variant === "hero") {
|
|
55
|
+
return wrap(_jsxs(_Fragment, { children: [image({ width: "100%", height: HERO_HEIGHT[look.imageSize] }), _jsxs(View, { style: { gap: 3 }, children: [badge, titleLine(true), facts, rowIsButton ? null : button(true)] })] }), {
|
|
56
|
+
borderRadius: r, borderWidth: 1, borderColor: theme.border, padding: pad,
|
|
57
|
+
opacity: row.unavailable ? 0.55 : 1, gap, flexDirection: "column",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (variant === "list") {
|
|
61
|
+
const t = LIST_THUMB[look.imageSize];
|
|
62
|
+
return wrap(_jsxs(_Fragment, { children: [image({ width: t, height: t }), _jsxs(View, { style: { flex: 1, minWidth: 0, gap: 2 }, children: [titleLine(false), facts] }), rowIsButton ? (_jsx(Text, { style: { color: theme.muted, fontSize: 22, fontWeight: "600", paddingLeft: 4 }, children: "›" })) : button(false)] }), {
|
|
63
|
+
flexDirection: "row", alignItems: "center", gap,
|
|
64
|
+
paddingVertical: pad, paddingHorizontal: 2, minHeight: 44,
|
|
65
|
+
borderBottomWidth: 1, borderBottomColor: theme.border, marginLeft: grammar.separatorInset,
|
|
66
|
+
opacity: row.unavailable ? 0.55 : 1,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (variant === "compact") {
|
|
70
|
+
return wrap(_jsxs(_Fragment, { children: [_jsxs(View, { style: { flex: 1, minWidth: 0, gap: 2 }, children: [badge, titleLine(false), facts] }), rowIsButton ? null : button(false)] }), {
|
|
17
71
|
borderRadius: r, borderWidth: 1, borderColor: theme.border,
|
|
18
|
-
|
|
19
|
-
flexDirection:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fontVariant: ["tabular-nums"],
|
|
29
|
-
}, children: row.price })) : null] }), row.facts.length ? (_jsx(View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 6, marginTop: 2 }, children: row.facts.map((f, i) => (_jsx(Text, { style: {
|
|
30
|
-
color: theme.muted, fontSize: 11.5, fontWeight: "600",
|
|
31
|
-
backgroundColor: tint(theme.border, 0.35),
|
|
32
|
-
borderRadius: 8, paddingHorizontal: 8, paddingVertical: 4, overflow: "hidden",
|
|
33
|
-
}, children: f }, `${f}-${i}`))) })) : null, hero ? (_jsx(Pressable, { disabled: row.unavailable, onPress: onPick, accessibilityRole: "button", accessibilityLabel: `${row.selectLabel}: ${row.title}`, style: {
|
|
34
|
-
marginTop: 8, height: 44, borderRadius: 11, alignItems: "center", justifyContent: "center",
|
|
35
|
-
backgroundColor: row.unavailable ? "transparent" : accent,
|
|
36
|
-
borderWidth: row.unavailable ? 1 : 0, borderColor: theme.border,
|
|
37
|
-
}, children: _jsx(Text, { style: { color: row.unavailable ? theme.muted : theme.onAccent, fontSize: 14, fontWeight: "700" }, children: row.unavailable ? "Unavailable" : row.selectLabel }) })) : null] }), hero ? null : (_jsx(Pressable, { disabled: row.unavailable, onPress: onPick, accessibilityRole: "button", accessibilityLabel: `${row.selectLabel}: ${row.title}`, hitSlop: 8, style: {
|
|
38
|
-
minHeight: 44, justifyContent: "center",
|
|
39
|
-
backgroundColor: row.unavailable ? "transparent" : accent,
|
|
40
|
-
borderWidth: row.unavailable ? 1 : 0, borderColor: theme.border,
|
|
41
|
-
borderRadius: 999, paddingHorizontal: 14,
|
|
42
|
-
}, children: _jsx(Text, { style: { color: row.unavailable ? theme.muted : theme.onAccent, fontSize: 12.5, fontWeight: "700" }, children: row.unavailable ? "Unavailable" : row.selectLabel }) }))] }));
|
|
72
|
+
paddingVertical: pad, paddingHorizontal: pad + 2,
|
|
73
|
+
flexDirection: "row", alignItems: "center", gap,
|
|
74
|
+
opacity: row.unavailable ? 0.55 : 1, minHeight: 44,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const t = THUMB[look.imageSize];
|
|
78
|
+
return wrap(_jsxs(_Fragment, { children: [image({ width: t, height: t }), _jsxs(View, { style: { flex: 1, minWidth: 0, gap: 3 }, children: [badge, titleLine(false), facts] }), rowIsButton ? null : button(false)] }), {
|
|
79
|
+
borderRadius: r, borderWidth: 1, borderColor: theme.border, padding: pad,
|
|
80
|
+
opacity: row.unavailable ? 0.55 : 1, flexDirection: "row", gap, alignItems: "center",
|
|
81
|
+
});
|
|
43
82
|
}
|
|
44
83
|
function AnsweredLine({ answered, theme, accent, radius, onClear }) {
|
|
45
84
|
const d = (answered.data || {});
|
|
@@ -60,9 +99,13 @@ export function GraineWidget(props) {
|
|
|
60
99
|
const { widget, answered, submit, dismiss } = useGraineWidget();
|
|
61
100
|
const appearance = useContext(Ctx)?.appearance ?? null;
|
|
62
101
|
const scheme = useColorScheme();
|
|
102
|
+
const [pagerWidth, setPagerWidth] = useState(0);
|
|
103
|
+
const [page, setPage] = useState(0);
|
|
104
|
+
const look = resolveWidgetLook(widget?.style, widget?.kind);
|
|
105
|
+
const grammar = PLATFORM_GRAMMAR[resolvePlatform(look.platform, Platform.OS, Platform.constants?.Manufacturer ?? Platform.constants?.Brand)];
|
|
63
106
|
const dash = resolveNativeAppearance(appearance, { accent: "#318CE7", heading: "" });
|
|
64
|
-
const accent = props.accent ?? dash.accent;
|
|
65
|
-
const radius = props.radius ?? dash.radius;
|
|
107
|
+
const accent = props.accent ?? look.accent ?? dash.accent;
|
|
108
|
+
const radius = props.radius ?? look.radius ?? (look.platform !== "auto" ? grammar.radius : dash.radius);
|
|
66
109
|
const theme = {
|
|
67
110
|
...resolveWidgetPalette(appearance, { scheme, accent }),
|
|
68
111
|
...(props.theme ?? {}),
|
|
@@ -132,7 +175,7 @@ export function GraineWidget(props) {
|
|
|
132
175
|
const isChips = (widget.kind === "choices" || isQuickActions) && options.length > 0;
|
|
133
176
|
const isCalendar = widget.kind === "calendar_slots" && Array.isArray(data?.days) && data.days.length > 0;
|
|
134
177
|
const drawable = isProducts || isChips || isCalendar;
|
|
135
|
-
const surface = drawable ?
|
|
178
|
+
const surface = drawable ? look.surface : "card";
|
|
136
179
|
const pick = (it) => submit(normalised.toSubmit(it), normalised.toSay(it));
|
|
137
180
|
const body = (_jsx(_Fragment, { children: isCalendar ? (_jsxs(View, { style: { gap: 12 }, children: [data.note ? (_jsx(Text, { style: { color: theme.muted, fontSize: 12.5, lineHeight: 18 }, children: String(data.note) })) : null, data.days.slice(0, 4).map((d, di) => {
|
|
138
181
|
const dayLabel = String(d?.label ?? d?.id ?? `Day ${di + 1}`);
|
|
@@ -144,7 +187,31 @@ export function GraineWidget(props) {
|
|
|
144
187
|
borderWidth: 1, borderColor: theme.border, borderRadius: 11,
|
|
145
188
|
paddingHorizontal: 14,
|
|
146
189
|
}, children: _jsx(Text, { style: { color: theme.text, fontSize: 13.5, fontWeight: "600" }, children: slot }) }, `${slot}-${si}`))) })] }, `${dayLabel}-${di}`));
|
|
147
|
-
})] })) : isProducts ? (
|
|
190
|
+
})] })) : isProducts ? ((() => {
|
|
191
|
+
const draw = (row, i, variant) => (_jsx(WidgetRow, { row: row, variant: variant, look: look, grammar: grammar, theme: theme, accent: accent, radius: radius, onPick: () => pick({ ...normalised.items[i], id: row.id, title: row.title, price: row.price }) }, `${row.id}-${i}`));
|
|
192
|
+
const gap = look.density === "compact" ? 6 : 10;
|
|
193
|
+
if (look.preset === "spotlight") {
|
|
194
|
+
const cardW = pagerWidth ? Math.round(pagerWidth * 0.86) : 300;
|
|
195
|
+
const step = cardW + gap;
|
|
196
|
+
return (_jsxs(View, { onLayout: (e) => setPagerWidth(e?.nativeEvent?.layout?.width ?? 0), children: [_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, snapToInterval: step, decelerationRate: "fast", onMomentumScrollEnd: (e) => setPage(Math.round((e?.nativeEvent?.contentOffset?.x ?? 0) / step)), contentContainerStyle: { gap, paddingRight: 24 }, children: rows.map((row, i) => (_jsx(View, { style: { width: cardW }, children: draw(row, i, "hero") }, `${row.id}-${i}`))) }), rows.length > 1 ? (_jsx(View, { style: { flexDirection: "row", justifyContent: "center", gap: 5, paddingTop: 10 }, accessibilityLabel: `${Math.min(page + 1, rows.length)} of ${rows.length}`, children: rows.map((row, i) => (_jsx(View, { style: {
|
|
197
|
+
height: 5, borderRadius: 999, width: i === page ? 14 : 5,
|
|
198
|
+
backgroundColor: i === page ? accent : theme.border,
|
|
199
|
+
} }, `dot-${row.id}-${i}`))) })) : null] }));
|
|
200
|
+
}
|
|
201
|
+
if (look.columns === 2) {
|
|
202
|
+
return (_jsx(View, { style: { flexDirection: "row", flexWrap: "wrap", gap, justifyContent: "space-between" }, children: rows.map((row, i) => (_jsx(View, { style: { width: "48%" }, children: draw(row, i, "hero") }, `${row.id}-${i}`))) }));
|
|
203
|
+
}
|
|
204
|
+
if (look.preset === "gallery") {
|
|
205
|
+
return _jsx(View, { style: { gap }, children: rows.map((row, i) => draw(row, i, "hero")) });
|
|
206
|
+
}
|
|
207
|
+
if (look.preset === "list") {
|
|
208
|
+
return _jsx(View, { children: rows.map((row, i) => draw(row, i, "list")) });
|
|
209
|
+
}
|
|
210
|
+
if (look.preset === "compact") {
|
|
211
|
+
return _jsx(View, { style: { gap }, children: rows.map((row, i) => draw(row, i, "compact")) });
|
|
212
|
+
}
|
|
213
|
+
return _jsx(View, { style: { gap }, children: rows.map((row, i) => draw(row, i, "row")) });
|
|
214
|
+
})()) : isChips ? (_jsx(View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 8 }, children: options.map((o, i) => {
|
|
148
215
|
const label = String(o?.label ?? o?.title ?? o ?? "");
|
|
149
216
|
const id = String(o?.id ?? label);
|
|
150
217
|
return (_jsx(Pressable, { onPress: () => submit(isQuickActions ? { id, label } : { chosen: id, label }, `I'll go with ${label}.`), accessibilityRole: "button", accessibilityLabel: label, style: {
|
|
@@ -155,7 +222,7 @@ export function GraineWidget(props) {
|
|
|
155
222
|
const title = data?.title ? String(data.title) : "";
|
|
156
223
|
const prompt = data?.prompt ? String(data.prompt) : "";
|
|
157
224
|
if (surface === "screen") {
|
|
158
|
-
return (_jsxs(_Fragment, { children: [chosen, _jsx(Modal, { visible: true, animationType: "slide", onRequestClose: dismiss, transparent:
|
|
225
|
+
return (_jsxs(_Fragment, { children: [chosen, _jsx(Modal, { visible: true, animationType: "slide", onRequestClose: dismiss, transparent: true, presentationStyle: "overFullScreen", statusBarTranslucent: true, children: _jsxs(View, { style: { flex: 1, backgroundColor: theme.surface }, children: [_jsxs(View, { style: {
|
|
159
226
|
paddingTop: 52, paddingHorizontal: 18, paddingBottom: 12,
|
|
160
227
|
flexDirection: "row", alignItems: "center", gap: 12,
|
|
161
228
|
}, children: [_jsx(Pressable, { onPress: dismiss, accessibilityRole: "button", accessibilityLabel: "Close and keep talking", hitSlop: 10, style: {
|
|
@@ -167,11 +234,11 @@ export function GraineWidget(props) {
|
|
|
167
234
|
}, children: [_jsx(Text, { style: { color: theme.muted, fontSize: 10.5, fontWeight: "800", letterSpacing: 0.4, textTransform: "uppercase" }, children: "Still listening" }), _jsx(Text, { numberOfLines: 1, style: { color: theme.text, fontSize: 13.5, marginTop: 2 }, children: prompt || "Tap one, or just say it." })] })] }) })] }));
|
|
168
235
|
}
|
|
169
236
|
if (surface === "sheet") {
|
|
170
|
-
return (_jsxs(_Fragment, { children: [chosen, _jsx(Modal, { visible: true, animationType: "slide", transparent: true, onRequestClose: dismiss, children: _jsxs(View, { style: { flex: 1, backgroundColor: "rgba(0,0,0,0.45)", justifyContent: "flex-end" }, children: [_jsx(Pressable, { onPress: dismiss, accessibilityRole: "button", accessibilityLabel: "Close", style: { flex: 1 } }), _jsxs(View, { style: {
|
|
237
|
+
return (_jsxs(_Fragment, { children: [chosen, _jsx(Modal, { visible: true, animationType: "slide", transparent: true, presentationStyle: "overFullScreen", statusBarTranslucent: true, onRequestClose: dismiss, children: _jsxs(View, { style: { flex: 1, backgroundColor: "rgba(0,0,0,0.45)", justifyContent: "flex-end" }, children: [_jsx(Pressable, { onPress: dismiss, accessibilityRole: "button", accessibilityLabel: "Close", style: { flex: 1 } }), _jsxs(View, { style: {
|
|
171
238
|
backgroundColor: theme.surface,
|
|
172
239
|
borderTopLeftRadius: Math.max(20, radius), borderTopRightRadius: Math.max(20, radius),
|
|
173
240
|
paddingHorizontal: 18, paddingTop: 10, paddingBottom: 28, gap: 14,
|
|
174
|
-
}, children: [_jsx(View, { style: { alignItems: "center" }, children: _jsx(View, { style: { width:
|
|
241
|
+
}, children: [_jsx(View, { style: { alignItems: "center" }, children: _jsx(View, { style: { width: grammar.grabber.width, height: grammar.grabber.height, borderRadius: 999, backgroundColor: theme.border } }) }), title ? (_jsx(Text, { style: { color: theme.text, fontSize: 18, fontWeight: "800" }, children: title })) : null, prompt ? (_jsx(Text, { style: { color: theme.muted, fontSize: 13, lineHeight: 18, marginTop: -8 }, children: prompt })) : null, _jsx(ScrollView, { style: { maxHeight: 420 }, contentContainerStyle: { paddingBottom: 4 }, keyboardShouldPersistTaps: "handled", children: body }), _jsx(Pressable, { onPress: dismiss, accessibilityRole: "button", accessibilityLabel: "None of these", style: {
|
|
175
242
|
alignSelf: "flex-start", minHeight: 44, justifyContent: "center",
|
|
176
243
|
paddingHorizontal: 16, borderRadius: 999, backgroundColor: tint(theme.border, 0.4),
|
|
177
244
|
}, children: _jsx(Text, { style: { color: theme.text, fontSize: 13.5, fontWeight: "700" }, children: "None of these" }) })] })] }) })] }));
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graineai/inapp-react-native",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.4",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Graine in-app agent for React Native
|
|
5
|
+
"description": "Graine in-app agent for React Native \u2014 an agent that sees the screen your customer is on and can act on it.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
8
8
|
"main": "dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
35
35
|
"test": "npm run build && node --test test/*.test.mjs && npm run test:render",
|
|
36
36
|
"prepublishOnly": "npm test && node scripts/verify-package-clean.mjs",
|
|
37
|
-
"test:render": "node --import ./test/harness/register.mjs --test test/render
|
|
37
|
+
"test:render": "node --import ./test/harness/register.mjs --test test/render/*.test.mjs"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@jambonz/client-sdk-react-native": "^0.1.4",
|