@vanira/sdk-react-native 0.0.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.
Files changed (148) hide show
  1. package/README.md +239 -0
  2. package/package.json +53 -0
  3. package/src/__tests__/WebRTCClient.integration.test.ts +396 -0
  4. package/src/__tests__/adapters.test.ts +475 -0
  5. package/src/__tests__/httpResponse.test.ts +25 -0
  6. package/src/__tests__/mocks/react-native-incall-manager.ts +8 -0
  7. package/src/__tests__/mocks/react-native-permissions.ts +15 -0
  8. package/src/__tests__/mocks/react-native-webrtc.ts +6 -0
  9. package/src/__tests__/mocks/react-native.ts +28 -0
  10. package/src/__tests__/preset.test.ts +239 -0
  11. package/src/__tests__/resolveRuntimeConfig.test.ts +90 -0
  12. package/src/__tests__/storage.test.ts +211 -0
  13. package/src/__tests__/webrtcSignaling.test.ts +42 -0
  14. package/src/adapters/PeerConnectionAdapter.ts +101 -0
  15. package/src/adapters/browser/BrowserAudioAdapter.ts +43 -0
  16. package/src/adapters/browser/BrowserDataChannelAdapter.ts +69 -0
  17. package/src/adapters/browser/BrowserMediaAdapter.ts +15 -0
  18. package/src/adapters/browser/BrowserPeerAdapter.ts +14 -0
  19. package/src/adapters/browser/index.ts +4 -0
  20. package/src/adapters/interfaces.ts +84 -0
  21. package/src/adapters/react-native/RNAudioAdapter.ts +42 -0
  22. package/src/adapters/react-native/RNDataChannelAdapter.ts +79 -0
  23. package/src/adapters/react-native/RNMediaAdapter.ts +46 -0
  24. package/src/adapters/react-native/RNPeerAdapter.ts +28 -0
  25. package/src/adapters/react-native/callAudioRouting.ts +115 -0
  26. package/src/adapters/react-native/decodeUtf8.ts +72 -0
  27. package/src/adapters/react-native/index.ts +4 -0
  28. package/src/adapters/react-native/rnUploadFile.ts +76 -0
  29. package/src/adapters/storage/BrowserDualStorageAdapter.ts +71 -0
  30. package/src/adapters/storage/MemoryStorageAdapter.ts +50 -0
  31. package/src/adapters/storage/StorageAdapter.ts +21 -0
  32. package/src/adapters/storage/createSyncStorageAdapter.ts +40 -0
  33. package/src/adapters/storage/index.ts +7 -0
  34. package/src/api/services/ChatService.ts +304 -0
  35. package/src/api/services/ConfigService.ts +33 -0
  36. package/src/assets/icons.js +35 -0
  37. package/src/cdn.ts +68 -0
  38. package/src/core/CallSessionStore.ts +137 -0
  39. package/src/core/DraggableController.ts +83 -0
  40. package/src/core/SessionManager.ts +322 -0
  41. package/src/core/VaniraAI.ts +464 -0
  42. package/src/core/WebRTCClient.ts +1012 -0
  43. package/src/core/httpResponse.ts +22 -0
  44. package/src/core/iceServers.ts +18 -0
  45. package/src/core/toolCallNormalize.ts +80 -0
  46. package/src/core/voice-client.js +236 -0
  47. package/src/core/webrtcSignaling.ts +72 -0
  48. package/src/index.js +34 -0
  49. package/src/index.ts +6 -0
  50. package/src/platforms/browser.ts +67 -0
  51. package/src/platforms/react-native.ts +105 -0
  52. package/src/presets/BookingCalendarModal.tsx +457 -0
  53. package/src/presets/CameraModal.tsx +576 -0
  54. package/src/presets/DynamicFormModal.tsx +378 -0
  55. package/src/presets/NativePresetRenderer.tsx +350 -0
  56. package/src/presets/NavigateHandler.tsx +75 -0
  57. package/src/presets/PresetHost.tsx +155 -0
  58. package/src/presets/PresetShellModal.tsx +97 -0
  59. package/src/presets/UploadModal.tsx +321 -0
  60. package/src/presets/calendar/calendarUtils.ts +386 -0
  61. package/src/presets/call/CallSpeakerToggle.tsx +59 -0
  62. package/src/presets/call/callAudioRouting.ts +2 -0
  63. package/src/presets/call/useCallSpeaker.ts +31 -0
  64. package/src/presets/camera/cameraPermissions.ts +18 -0
  65. package/src/presets/camera/cameraStream.ts +19 -0
  66. package/src/presets/camera/cameraUtils.ts +21 -0
  67. package/src/presets/camera/useLivenessFlow.ts +95 -0
  68. package/src/presets/chalkboard/ChalkboardOverlay.tsx +156 -0
  69. package/src/presets/chalkboard/EraseTextHandler.tsx +95 -0
  70. package/src/presets/chalkboard/TypeTextHandler.tsx +107 -0
  71. package/src/presets/chalkboard/boardAbort.ts +36 -0
  72. package/src/presets/chalkboard/boardQueue.ts +620 -0
  73. package/src/presets/chalkboard/chalkboardSession.ts +75 -0
  74. package/src/presets/chalkboard/drawUtils.ts +123 -0
  75. package/src/presets/chalkboard/textUtils.ts +109 -0
  76. package/src/presets/clipRegion/ClipRegionModal.tsx +261 -0
  77. package/src/presets/clipRegion/clipRegionBridge.ts +19 -0
  78. package/src/presets/form/formValidation.ts +104 -0
  79. package/src/presets/form/parseFormFields.ts +171 -0
  80. package/src/presets/host/HostElementPresetHandler.tsx +155 -0
  81. package/src/presets/host/hostPresetBridge.ts +71 -0
  82. package/src/presets/index.ts +63 -0
  83. package/src/presets/liveScreen/CloseLiveScreenHandler.tsx +36 -0
  84. package/src/presets/liveScreen/LiveScreenCaptureHost.tsx +312 -0
  85. package/src/presets/liveScreen/LiveScreenHandler.tsx +25 -0
  86. package/src/presets/liveScreen/LiveScreenPipOverlay.tsx +6 -0
  87. package/src/presets/liveScreen/liveScreenSession.ts +73 -0
  88. package/src/presets/liveVision/CloseLiveVisionHandler.tsx +29 -0
  89. package/src/presets/liveVision/LiveVisionCameraHost.tsx +317 -0
  90. package/src/presets/liveVision/LiveVisionHandler.tsx +26 -0
  91. package/src/presets/liveVision/LiveVisionPipOverlay.tsx +7 -0
  92. package/src/presets/liveVision/liveVisionFrameLoop.ts +38 -0
  93. package/src/presets/liveVision/liveVisionSession.ts +75 -0
  94. package/src/presets/liveVision/liveVisionUpload.ts +62 -0
  95. package/src/presets/navigation/internalRouteRegistry.ts +25 -0
  96. package/src/presets/navigation/navigationBridge.ts +76 -0
  97. package/src/presets/navigation/navigationTypes.ts +12 -0
  98. package/src/presets/parseToolCall.ts +60 -0
  99. package/src/presets/presetClientAdapter.ts +29 -0
  100. package/src/presets/presetCompletion.ts +91 -0
  101. package/src/presets/presetEventHelpers.ts +45 -0
  102. package/src/presets/registry.ts +128 -0
  103. package/src/presets/streaming/mediaFrameUpload.ts +93 -0
  104. package/src/presets/types.ts +74 -0
  105. package/src/presets/upload/pickUploadFile.ts +256 -0
  106. package/src/presets/upload/uploadFormats.ts +163 -0
  107. package/src/presets/upload/uploadUtils.ts +68 -0
  108. package/src/react/PresetRenderer.tsx +144 -0
  109. package/src/react/index.ts +1 -0
  110. package/src/runtime/browserRuntime.ts +54 -0
  111. package/src/runtime/platform.ts +17 -0
  112. package/src/runtime/reactNativeRuntime.ts +68 -0
  113. package/src/runtime/resolveRuntimeConfig.ts +75 -0
  114. package/src/runtime/runtimeBundles.ts +74 -0
  115. package/src/runtime/types.ts +135 -0
  116. package/src/types/react-native-incall-manager.d.ts +17 -0
  117. package/src/types/react-native-webrtc.d.ts +47 -0
  118. package/src/types.ts +133 -0
  119. package/src/ui/VaniraWidget.ts +87 -0
  120. package/src/ui/abstraction/AbstractWidgetProvider.ts +18 -0
  121. package/src/ui/abstraction/interfaces.ts +12 -0
  122. package/src/ui/adapters/VaniraChatAdapter.ts +42 -0
  123. package/src/ui/components/AvatarView.ts +81 -0
  124. package/src/ui/components/ChatWindow.ts +263 -0
  125. package/src/ui/components/FloatingButton.ts +163 -0
  126. package/src/ui/components/FloatingWelcomeChips.ts +137 -0
  127. package/src/ui/components/Panel.ts +120 -0
  128. package/src/ui/components/VoiceOrb.ts +79 -0
  129. package/src/ui/components/VoiceOverlay.ts +497 -0
  130. package/src/ui/components/index.ts +7 -0
  131. package/src/ui/factory/WidgetFactory.ts +16 -0
  132. package/src/ui/icons_data.ts +2 -0
  133. package/src/ui/presets/WidgetPresetRenderer.ts +1802 -0
  134. package/src/ui/presets/types.ts +16 -0
  135. package/src/ui/providers/VaniraInternalProvider.ts +1066 -0
  136. package/src/ui/styles/index.ts +323 -0
  137. package/src/ui/styles/keyframes.ts +76 -0
  138. package/src/ui/styles/theme.ts +57 -0
  139. package/src/ui/styles/widget.css.ts +838 -0
  140. package/src/ui/utils.ts +37 -0
  141. package/src/ui/views/AbstractChatView.ts +93 -0
  142. package/src/ui/views/AbstractVoiceView.ts +57 -0
  143. package/src/ui/views/AvatarOnlyView.ts +78 -0
  144. package/src/ui/views/ChatAvatarView.ts +66 -0
  145. package/src/ui/views/ChatOnlyView.ts +28 -0
  146. package/src/ui/views/ChatVoiceView.ts +15 -0
  147. package/src/ui/views/VoiceOnlyView.ts +25 -0
  148. package/src/ui/views/index.ts +5 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Parse vanira_form fields from client_fields — web-compatible + extended types.
3
+ */
4
+
5
+ export type FormFieldType =
6
+ | 'text'
7
+ | 'textarea'
8
+ | 'select'
9
+ | 'radio'
10
+ | 'checkbox'
11
+ | 'email'
12
+ | 'phone'
13
+ | 'number'
14
+ | 'date';
15
+
16
+ export type FormFieldOption = string | {label: string; value: string};
17
+
18
+ export type FormFieldDefinition = {
19
+ id: string;
20
+ label: string;
21
+ type: FormFieldType;
22
+ required: boolean;
23
+ placeholder?: string;
24
+ options: FormFieldOption[];
25
+ };
26
+
27
+ export function normalizeFieldId(label: string): string {
28
+ return label.toLowerCase().replace(/[^a-z0-9]/g, '_');
29
+ }
30
+
31
+ function inferTypeFromId(id: string, explicit?: string): FormFieldType {
32
+ const t = explicit?.toLowerCase();
33
+ if (
34
+ t &&
35
+ [
36
+ 'text',
37
+ 'textarea',
38
+ 'select',
39
+ 'radio',
40
+ 'checkbox',
41
+ 'email',
42
+ 'phone',
43
+ 'number',
44
+ 'date',
45
+ 'tel',
46
+ ].includes(t)
47
+ ) {
48
+ if (t === 'tel') {
49
+ return 'phone';
50
+ }
51
+ return t as FormFieldType;
52
+ }
53
+ if (id.includes('email')) {
54
+ return 'email';
55
+ }
56
+ if (id.includes('phone')) {
57
+ return 'phone';
58
+ }
59
+ if (id.includes('date') && !id.includes('update')) {
60
+ return 'date';
61
+ }
62
+ return 'text';
63
+ }
64
+
65
+ function normalizeOptions(raw: unknown): FormFieldOption[] {
66
+ if (!raw) {
67
+ return [];
68
+ }
69
+ if (Array.isArray(raw)) {
70
+ return raw.map(item => {
71
+ if (typeof item === 'string') {
72
+ return item;
73
+ }
74
+ if (item && typeof item === 'object') {
75
+ const obj = item as {label?: string; value?: string};
76
+ return {
77
+ label: String(obj.label ?? obj.value ?? ''),
78
+ value: String(obj.value ?? obj.label ?? ''),
79
+ };
80
+ }
81
+ return String(item);
82
+ });
83
+ }
84
+ if (typeof raw === 'string') {
85
+ return raw
86
+ .split(',')
87
+ .map(s => s.trim())
88
+ .filter(Boolean);
89
+ }
90
+ return [];
91
+ }
92
+
93
+ function fieldFromObject(obj: Record<string, unknown>): FormFieldDefinition | null {
94
+ const label = String(
95
+ obj.label ?? obj.name ?? obj.title ?? obj.field ?? '',
96
+ ).trim();
97
+ if (!label) {
98
+ return null;
99
+ }
100
+ const id = String(obj.id ?? obj.key ?? normalizeFieldId(label));
101
+ const type = inferTypeFromId(id, String(obj.type ?? ''));
102
+ return {
103
+ id,
104
+ label,
105
+ type,
106
+ required: obj.required !== false,
107
+ placeholder: obj.placeholder ? String(obj.placeholder) : undefined,
108
+ options: normalizeOptions(obj.options ?? obj.choices ?? obj.values),
109
+ };
110
+ }
111
+
112
+ function fieldFromLabel(label: string): FormFieldDefinition {
113
+ const trimmed = label.trim();
114
+ const id = normalizeFieldId(trimmed);
115
+ return {
116
+ id,
117
+ label: trimmed,
118
+ type: inferTypeFromId(id),
119
+ required: true,
120
+ placeholder: `Enter your ${trimmed.toLowerCase()}`,
121
+ options: [],
122
+ };
123
+ }
124
+
125
+ /** Parse `fields` from merged client_fields / args (web + extended JSON). */
126
+ export function parseFormFields(rawFields: unknown): FormFieldDefinition[] {
127
+ if (rawFields === undefined || rawFields === null) {
128
+ return [];
129
+ }
130
+
131
+ if (typeof rawFields === 'string') {
132
+ const trimmed = rawFields.trim();
133
+ if (trimmed.startsWith('[')) {
134
+ try {
135
+ const parsed = JSON.parse(trimmed) as unknown;
136
+ return parseFormFields(parsed);
137
+ } catch {
138
+ /* fall through to comma split */
139
+ }
140
+ }
141
+ return trimmed
142
+ .split(',')
143
+ .map(s => s.trim())
144
+ .filter(Boolean)
145
+ .map(fieldFromLabel);
146
+ }
147
+
148
+ if (Array.isArray(rawFields)) {
149
+ const out: FormFieldDefinition[] = [];
150
+ for (const item of rawFields) {
151
+ if (typeof item === 'string') {
152
+ out.push(fieldFromLabel(item));
153
+ continue;
154
+ }
155
+ if (item && typeof item === 'object') {
156
+ const def = fieldFromObject(item as Record<string, unknown>);
157
+ if (def) {
158
+ out.push(def);
159
+ }
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+
165
+ if (typeof rawFields === 'object') {
166
+ const def = fieldFromObject(rawFields as Record<string, unknown>);
167
+ return def ? [def] : [];
168
+ }
169
+
170
+ return [];
171
+ }
@@ -0,0 +1,155 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {navigateToTarget} from '../navigation/navigationBridge';
3
+ import type {PresetContext} from '../types';
4
+ import {VaniraPresetId} from '../registry';
5
+ import {
6
+ dispatchHostPresetAction,
7
+ type HostPresetAction,
8
+ } from './hostPresetBridge';
9
+
10
+ type Props = {
11
+ ctx: PresetContext;
12
+ onComplete: (payload: Record<string, unknown>) => void;
13
+ onCancel: (reason?: string) => void;
14
+ onDismiss: () => void;
15
+ };
16
+
17
+ function merged(ctx: PresetContext): Record<string, unknown> {
18
+ return {...ctx.args, ...ctx.clientFields};
19
+ }
20
+
21
+ function elementIdFrom(mergedFields: Record<string, unknown>): string {
22
+ return String(
23
+ mergedFields.element_id ??
24
+ mergedFields.id ??
25
+ mergedFields.css_selector ??
26
+ mergedFields.selector ??
27
+ '',
28
+ )
29
+ .trim()
30
+ .replace(/^#/, '');
31
+ }
32
+
33
+ function routeFrom(mergedFields: Record<string, unknown>): string | undefined {
34
+ const route = String(mergedFields.route ?? mergedFields.target_url ?? '').trim();
35
+ return route || undefined;
36
+ }
37
+
38
+ function buildAction(ctx: PresetContext): HostPresetAction | null {
39
+ const fields = merged(ctx);
40
+ const elementId = elementIdFrom(fields);
41
+ const route = routeFrom(fields);
42
+
43
+ switch (ctx.presetId) {
44
+ case VaniraPresetId.HighlightElement:
45
+ return {
46
+ type: 'highlight',
47
+ elementId: String(fields.css_selector ?? fields.selector ?? elementId),
48
+ cssSelector: String(fields.css_selector ?? fields.selector ?? ''),
49
+ color: String(fields.highlight_color ?? fields.color ?? '#a855f7'),
50
+ durationMs: Number(fields.duration_ms ?? 3000),
51
+ route,
52
+ };
53
+ case VaniraPresetId.ClickElement:
54
+ if (!elementId) {
55
+ return null;
56
+ }
57
+ return {
58
+ type: 'click',
59
+ elementId,
60
+ scrollBeforeClick:
61
+ fields.scroll_before_click !== false &&
62
+ fields.scroll_before_click !== 'false',
63
+ route,
64
+ };
65
+ case VaniraPresetId.SelectOption: {
66
+ if (!elementId) {
67
+ return null;
68
+ }
69
+ const optionValue = String(fields.option_value ?? '').trim();
70
+ const optionLabel = String(
71
+ fields.option_label ?? fields.option_text ?? '',
72
+ ).trim();
73
+ if (!optionValue && !optionLabel) {
74
+ return null;
75
+ }
76
+ return {
77
+ type: 'select',
78
+ elementId,
79
+ optionValue,
80
+ optionLabel,
81
+ route,
82
+ };
83
+ }
84
+ case VaniraPresetId.SetDate: {
85
+ if (!elementId) {
86
+ return null;
87
+ }
88
+ const date = String(fields.date ?? '').trim();
89
+ if (!date) {
90
+ return null;
91
+ }
92
+ return {
93
+ type: 'set_date',
94
+ elementId,
95
+ date,
96
+ inputKind: String(fields.input_kind ?? 'text'),
97
+ textFormat: String(fields.text_format ?? 'YYYY-MM-DD'),
98
+ route,
99
+ };
100
+ }
101
+ default:
102
+ return null;
103
+ }
104
+ }
105
+
106
+ export function HostElementPresetHandler({
107
+ ctx,
108
+ onComplete,
109
+ onCancel,
110
+ onDismiss,
111
+ }: Props) {
112
+ const ranRef = useRef(false);
113
+
114
+ useEffect(() => {
115
+ if (ranRef.current) {
116
+ return;
117
+ }
118
+ ranRef.current = true;
119
+
120
+ (async () => {
121
+ const action = buildAction(ctx);
122
+ if (!action) {
123
+ onCancel('Missing required element_id or preset arguments');
124
+ onDismiss();
125
+ return;
126
+ }
127
+
128
+ try {
129
+ if (action.route && !/^https?:\/\//i.test(action.route)) {
130
+ const nav = await navigateToTarget(action.route);
131
+ if (!nav.handled && !nav.external) {
132
+ onCancel(`Route not handled: ${action.route}`);
133
+ onDismiss();
134
+ return;
135
+ }
136
+ }
137
+
138
+ const result = await dispatchHostPresetAction(action);
139
+ onComplete({
140
+ status: 'success',
141
+ preset_id: ctx.presetId,
142
+ ...result,
143
+ });
144
+ } catch (err: unknown) {
145
+ const message =
146
+ err instanceof Error ? err.message : 'Host preset action failed';
147
+ onCancel(message);
148
+ } finally {
149
+ onDismiss();
150
+ }
151
+ })();
152
+ }, [ctx, onCancel, onComplete, onDismiss]);
153
+
154
+ return null;
155
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Host-app bridge for presets that target logical element IDs (web: getElementById).
3
+ * Register handlers in your React Native app to wire agent tools to native UI.
4
+ */
5
+
6
+ export type HostHighlightAction = {
7
+ type: 'highlight';
8
+ elementId: string;
9
+ cssSelector?: string;
10
+ color?: string;
11
+ durationMs?: number;
12
+ route?: string;
13
+ };
14
+
15
+ export type HostClickAction = {
16
+ type: 'click';
17
+ elementId: string;
18
+ scrollBeforeClick?: boolean;
19
+ route?: string;
20
+ };
21
+
22
+ export type HostSelectAction = {
23
+ type: 'select';
24
+ elementId: string;
25
+ optionValue?: string;
26
+ optionLabel?: string;
27
+ route?: string;
28
+ };
29
+
30
+ export type HostSetDateAction = {
31
+ type: 'set_date';
32
+ elementId: string;
33
+ date: string;
34
+ inputKind?: string;
35
+ textFormat?: string;
36
+ route?: string;
37
+ };
38
+
39
+ export type HostPresetAction =
40
+ | HostHighlightAction
41
+ | HostClickAction
42
+ | HostSelectAction
43
+ | HostSetDateAction;
44
+
45
+ export type HostPresetHandler = (
46
+ action: HostPresetAction,
47
+ ) => Promise<Record<string, unknown>> | Record<string, unknown>;
48
+
49
+ let hostPresetHandler: HostPresetHandler | null = null;
50
+
51
+ export function registerHostPresetHandler(
52
+ handler: HostPresetHandler | null,
53
+ ): () => void {
54
+ hostPresetHandler = handler;
55
+ return () => {
56
+ if (hostPresetHandler === handler) {
57
+ hostPresetHandler = null;
58
+ }
59
+ };
60
+ }
61
+
62
+ export async function dispatchHostPresetAction(
63
+ action: HostPresetAction,
64
+ ): Promise<Record<string, unknown>> {
65
+ if (!hostPresetHandler) {
66
+ throw new Error(
67
+ `No host preset handler registered for ${action.type}. Call registerHostPresetHandler() in your app.`,
68
+ );
69
+ }
70
+ return Promise.resolve(hostPresetHandler(action));
71
+ }
@@ -0,0 +1,63 @@
1
+ export {NativePresetRenderer} from './NativePresetRenderer';
2
+ export {PresetHostProvider, usePresetHost} from './PresetHost';
3
+ export {parseToolCall} from './parseToolCall';
4
+ export {
5
+ extractPresetId,
6
+ extractToolName,
7
+ toClientToolCallPayload,
8
+ } from './presetEventHelpers';
9
+ export {
10
+ getLastNavigation,
11
+ navigateToTarget,
12
+ subscribeNavigation,
13
+ type NavigationEvent,
14
+ } from './navigation/navigationBridge';
15
+ export {registerInternalRouteHandler} from './navigation/internalRouteRegistry';
16
+ export {
17
+ prepareCallAudioForCapture,
18
+ activateCallAudioPlayback,
19
+ startCallAudioSession,
20
+ stopCallAudioSession,
21
+ setSpeakerphoneOn,
22
+ toggleSpeakerphone,
23
+ subscribeCallAudio,
24
+ isSpeakerOn,
25
+ isCallAudioSessionActive,
26
+ type CallAudioSnapshot,
27
+ } from '../adapters/react-native/callAudioRouting';
28
+ export {CallSpeakerToggle} from './call/CallSpeakerToggle';
29
+ export {useCallSpeaker} from './call/useCallSpeaker';
30
+ export {
31
+ resetBoardSession,
32
+ abortBoardTools,
33
+ isBoardQueuedPreset,
34
+ } from './chalkboard/boardQueue';
35
+ export {subscribeBoardAbort, dispatchBoardAbort} from './chalkboard/boardAbort';
36
+ export {
37
+ VaniraPresetId,
38
+ VANIRA_PRESET_IDS,
39
+ isVaniraPresetId,
40
+ isBoardQueuedPreset as isBoardQueuedPresetId,
41
+ isFireAndForgetPreset,
42
+ isNonInteractivePreset,
43
+ resolvePresetId,
44
+ TOOL_NAME_TO_PRESET_ID,
45
+ } from './registry';
46
+ export {
47
+ registerHostPresetHandler,
48
+ dispatchHostPresetAction,
49
+ type HostPresetAction,
50
+ type HostPresetHandler,
51
+ type HostHighlightAction,
52
+ type HostClickAction,
53
+ type HostSelectAction,
54
+ type HostSetDateAction,
55
+ } from './host/hostPresetBridge';
56
+ export {registerClipRegionCaptureTarget} from './clipRegion/clipRegionBridge';
57
+ export type {
58
+ NativePresetRendererProps,
59
+ ParsedToolCall,
60
+ PresetContext,
61
+ PresetClient,
62
+ PresetId,
63
+ } from './types';
@@ -0,0 +1,36 @@
1
+ import {useEffect, useRef} from 'react';
2
+ import type {PresetClient, PresetContext} from '../types';
3
+ import {stopLiveScreen} from './liveScreenSession';
4
+
5
+ type Props = {
6
+ client: PresetClient | null;
7
+ ctx: PresetContext;
8
+ onComplete: (payload: Record<string, unknown>) => void;
9
+ onDismiss: () => void;
10
+ };
11
+
12
+ export function CloseLiveScreenHandler({
13
+ client,
14
+ ctx,
15
+ onComplete,
16
+ onDismiss,
17
+ }: Props) {
18
+ const ranRef = useRef(false);
19
+
20
+ useEffect(() => {
21
+ if (ranRef.current) {
22
+ return;
23
+ }
24
+ ranRef.current = true;
25
+
26
+ stopLiveScreen(client);
27
+ onComplete({
28
+ status: 'success',
29
+ preset_id: ctx.presetId,
30
+ closed: true,
31
+ });
32
+ onDismiss();
33
+ }, [client, ctx.presetId, onComplete, onDismiss]);
34
+
35
+ return null;
36
+ }