@graineai/inapp-react-native 0.20.1 → 0.21.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/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { VoiceSession, liveAudioStreamAdapter, base64ToPcm16, CAPTURE_SAMPLE_RAT
4
4
  export { EchoGuard, decodeAgentAudio, decodeMuLaw, decodePcm16, base64ToBytes, type AudioFormat, type DecodedAudio, } from "./audio.js";
5
5
  export { addMaskRule, maskDeep, maskString } from "./mask.js";
6
6
  export { GraineProvider, useGraineAgent, useGraineScreen, useGraineAction, useGraineVoice, useGraineIdentify, useGraineEvents, useGraineTrack, useGraineTap, useGraineHighlight, useGraineWidget, type GraineEvent, type GraineProviderProps, type Turn, type Caption, } from "./react-native/index.js";
7
+ export { resolveNativeAppearance, type NativeAppearance, type NativeAppearanceFallback, } from "./react-native/appearance.js";
7
8
  export { RtcVoiceSessionController, RtcVoiceError, type RtcVoiceState, type RtcVoiceErrorCode, type RtcVoiceSession, type RtcVoiceOptions, } from "./react-native/voice-rtc.js";
8
9
  export { GraineAgentBar, GraineLauncher, type GraineAgentBarProps, type GraineLauncherProps, type GraineBarTheme, } from "./react-native/ui.js";
9
10
  export { GraineVoiceLauncher, type GraineVoiceLauncherProps, type GraineVoiceApi, } from "./react-native/voice-launcher.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { VoiceSession, liveAudioStreamAdapter, base64ToPcm16, CAPTURE_SAMPLE_RAT
3
3
  export { EchoGuard, decodeAgentAudio, decodeMuLaw, decodePcm16, base64ToBytes, } from "./audio.js";
4
4
  export { addMaskRule, maskDeep, maskString } from "./mask.js";
5
5
  export { GraineProvider, useGraineAgent, useGraineScreen, useGraineAction, useGraineVoice, useGraineIdentify, useGraineEvents, useGraineTrack, useGraineTap, useGraineHighlight, useGraineWidget, } from "./react-native/index.js";
6
+ export { resolveNativeAppearance, } from "./react-native/appearance.js";
6
7
  export { RtcVoiceSessionController, RtcVoiceError, } from "./react-native/voice-rtc.js";
7
8
  export { GraineAgentBar, GraineLauncher, } from "./react-native/ui.js";
8
9
  export { GraineVoiceLauncher, } from "./react-native/voice-launcher.js";
@@ -0,0 +1,14 @@
1
+ export interface NativeAppearance {
2
+ accent: string;
3
+ heading: string;
4
+ logoUrl: string | null;
5
+ radius: number;
6
+ }
7
+ export interface NativeAppearanceFallback {
8
+ accent: string;
9
+ heading: string;
10
+ radius?: number;
11
+ maxRadius?: number;
12
+ origin?: string;
13
+ }
14
+ export declare function resolveNativeAppearance(appearance: Record<string, any> | null | undefined, fallback: NativeAppearanceFallback): NativeAppearance;
@@ -0,0 +1,29 @@
1
+ const HEX = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
2
+ const RADIUS_CEILING = 48;
3
+ const DEFAULT_ORIGIN = "https://www.graine.ai";
4
+ function clamp(value, max) {
5
+ if (value < 0)
6
+ return 0;
7
+ return value > max ? max : value;
8
+ }
9
+ function numeric(value) {
10
+ if (typeof value === "number")
11
+ return Number.isFinite(value) ? value : null;
12
+ if (typeof value === "string" && value.trim() !== "") {
13
+ const n = Number(value);
14
+ return Number.isFinite(n) ? n : null;
15
+ }
16
+ return null;
17
+ }
18
+ export function resolveNativeAppearance(appearance, fallback) {
19
+ const a = appearance || {};
20
+ const max = clamp(numeric(fallback.maxRadius) ?? RADIUS_CEILING, RADIUS_CEILING);
21
+ const rawAccent = String(a.accent ?? "").trim();
22
+ const accent = HEX.test(rawAccent) ? "#" + rawAccent.replace(/^#/, "") : fallback.accent;
23
+ const heading = String(a.title ?? "").trim() || fallback.heading;
24
+ const logoKey = String(a.logo ?? "").trim();
25
+ const origin = String(fallback.origin || DEFAULT_ORIGIN).replace(/\/+$/, "");
26
+ const logoUrl = logoKey ? `${origin}/api/embed/logo?k=${encodeURIComponent(logoKey)}` : null;
27
+ const radius = clamp(numeric(a.radius) ?? numeric(fallback.radius) ?? 18, max);
28
+ return { accent, heading, logoUrl, radius };
29
+ }
@@ -148,6 +148,15 @@ export function GraineProvider({ children, autoConnect = true, onProactive, endO
148
148
  ? { ...prev, text: prev.text + text }
149
149
  : { role: "agent", text, live: true })),
150
150
  ];
151
+ (async () => {
152
+ try {
153
+ const cfg = client.config ?? (await client.session());
154
+ if (cfg?.appearance)
155
+ setAppearance(cfg.appearance);
156
+ }
157
+ catch {
158
+ }
159
+ })();
151
160
  offs.push(client.registerAction("highlight_element", async (args) => {
152
161
  const name = String(args?.name || args?.element || args?.target || "");
153
162
  const known = client.getHighlightables();
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { ActivityIndicator, Image, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, useColorScheme, View, } from "react-native";
4
4
  import { useGraineAgent } from "./index.js";
5
+ import { resolveNativeAppearance } from "./appearance.js";
5
6
  const DARK = {
6
7
  surface: "#17161A",
7
8
  border: "rgba(255,255,255,0.09)",
@@ -22,9 +23,13 @@ const LIGHT = {
22
23
  };
23
24
  export function GraineAgentBar({ accent: accentProp, name: nameProp, avatarUrl: avatarProp, bottomInset = 96, hidden = false, captionsOn = false, scheme = "auto", theme: themeProp, }) {
24
25
  const { connected, connecting, error, messages, send, muted, setMuted, caption, agentSpeaking, launcherVisible, launcherInset, appearance, } = useGraineAgent();
25
- const accent = accentProp ?? appearance?.accent ?? "#318CE7";
26
- const name = nameProp ?? appearance?.title ?? "Assistant";
27
- const avatarUrl = avatarProp ?? appearance?.logo ?? undefined;
26
+ const dashboard = resolveNativeAppearance(appearance, {
27
+ accent: "#318CE7",
28
+ heading: "Assistant",
29
+ });
30
+ const accent = accentProp ?? dashboard.accent;
31
+ const name = nameProp ?? dashboard.heading;
32
+ const avatarUrl = avatarProp ?? dashboard.logoUrl ?? undefined;
28
33
  const deviceScheme = useColorScheme();
29
34
  const styles = useMemo(() => {
30
35
  const base = (scheme === "auto" ? deviceScheme === "light" : scheme === "light") ? LIGHT : DARK;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graineai/inapp-react-native",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
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",
@@ -32,7 +32,8 @@
32
32
  "scripts": {
33
33
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
34
34
  "typecheck": "tsc -p tsconfig.build.json --noEmit",
35
- "prepublishOnly": "npm run build && node scripts/verify-package-clean.mjs"
35
+ "test": "npm run build && node --test test/*.test.mjs",
36
+ "prepublishOnly": "npm test && node scripts/verify-package-clean.mjs"
36
37
  },
37
38
  "peerDependencies": {
38
39
  "react": ">=17",