@ateam-ai-mobile/ui 0.1.10 → 0.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai-mobile/ui",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Prebuilt ADAS chat experience for React Native — full-screen chat, chips, widget views. Built on @ateam-ai-mobile/core + @ateam-ai-mobile/plugin-runtime.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,8 +9,8 @@
9
9
  "build": "tsup"
10
10
  },
11
11
  "dependencies": {
12
- "@ateam-ai-mobile/core": "0.1.10",
13
- "@ateam-ai-mobile/plugin-runtime": "0.1.10",
12
+ "@ateam-ai-mobile/core": "0.1.11",
13
+ "@ateam-ai-mobile/plugin-runtime": "0.1.11",
14
14
  "zustand": "^5.0.0"
15
15
  },
16
16
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -80,10 +80,12 @@ export {
80
80
  setUIActionNavigator,
81
81
  setPluginWebViewExtraHosts,
82
82
  setOAuthProvider,
83
+ setCallAudioProvider,
83
84
  } from './providers';
84
85
  export type {
85
86
  ClipboardProvider,
86
87
  UIActionRoute,
87
88
  OAuthProvider,
88
89
  OAuthGoogleConfig,
90
+ CallAudioProvider,
89
91
  } from './providers';
package/src/providers.ts CHANGED
@@ -48,6 +48,28 @@ export function getOAuthProvider(): OAuthProvider | null {
48
48
  return _oauth;
49
49
  }
50
50
 
51
+ // ── Call audio mode ────────────────────────────────────────────────────────
52
+ // A voice call needs the device in its CALL audio mode, or the phone's mic
53
+ // hears the loudspeaker and the AI interrupts itself (iOS: playAndRecord +
54
+ // voiceChat). Native, so the host wires it (via @ateam-ai-mobile/expo);
55
+ // voiceSession calls beginCall when a call starts and endCall when it ends.
56
+
57
+ export interface CallAudioProvider {
58
+ beginCall(): void;
59
+ endCall(): void;
60
+ }
61
+
62
+ let _callAudio: CallAudioProvider | null = null;
63
+
64
+ /** Register the device's call audio mode (e.g. via @ateam-ai-mobile/expo). */
65
+ export function setCallAudioProvider(callAudio: CallAudioProvider | null): void {
66
+ _callAudio = callAudio;
67
+ }
68
+
69
+ export function getCallAudioProvider(): CallAudioProvider | null {
70
+ return _callAudio;
71
+ }
72
+
51
73
  // ── Clipboard ──────────────────────────────────────────────────────────────
52
74
 
53
75
  export interface ClipboardProvider {
@@ -24,6 +24,7 @@
24
24
  * cannot reach the WebView beyond that one injected call.
25
25
  */
26
26
  import { getVoiceApiUrl, getToken, getTenant, getUser, refreshToken } from '@ateam-ai-mobile/core';
27
+ import { getCallAudioProvider } from '../providers';
27
28
  import {
28
29
  callDeviceTool,
29
30
  ensureDeviceToolsLoaded,
@@ -284,6 +285,11 @@ interface InternalState {
284
285
  persona?: VoicePersona;
285
286
  }
286
287
 
288
+ /** A call that holds the mic/speaker: connecting or connected. */
289
+ function isLiveCall(state: VoiceState): boolean {
290
+ return state === 'connecting' || state === 'connected';
291
+ }
292
+
287
293
  const IDLE: InternalState = {
288
294
  callState: 'idle',
289
295
  sessionId: null,
@@ -833,10 +839,26 @@ class VoiceSessionManager implements VoiceSessionProvider {
833
839
  // ── internals ────────────────────────────────────────────────
834
840
 
835
841
  private setState(partial: Partial<InternalState>): void {
842
+ const wasLive = isLiveCall(this.state.callState);
836
843
  this.state = { ...this.state, ...partial };
844
+ const isLive = isLiveCall(this.state.callState);
845
+ // The device's CALL audio mode follows the call: on as it starts connecting
846
+ // (before the WebView opens the mic), off as soon as it is no longer live —
847
+ // whichever path ends it (stop(), the page hanging up, an error).
848
+ if (wasLive !== isLive) this.setCallAudio(isLive);
837
849
  this.emit({ type: 'state' });
838
850
  }
839
851
 
852
+ private setCallAudio(on: boolean): void {
853
+ const audio = getCallAudioProvider();
854
+ if (!audio) return;
855
+ try {
856
+ if (on) audio.beginCall(); else audio.endCall();
857
+ } catch (e: any) {
858
+ console.warn('[voiceSession] call audio mode failed:', e?.message || String(e));
859
+ }
860
+ }
861
+
840
862
  private appendTranscript(entry: TranscriptEntry): void {
841
863
  this.state = { ...this.state, transcript: [...this.state.transcript, entry] };
842
864
  this.emit({ type: 'transcript' });