@kontextso/sdk-react-native 0.0.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.mjs ADDED
@@ -0,0 +1,1205 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/formats/InlineAd.tsx
9
+ import { useContext as useContext6, useEffect as useEffect8, useState as useState8 } from "react";
10
+ import {
11
+ Image,
12
+ Linking as Linking3,
13
+ Platform as Platform2,
14
+ Text as Text3,
15
+ TouchableOpacity as TouchableOpacity2,
16
+ View as View3
17
+ } from "react-native";
18
+
19
+ // src/hooks/useAdViewed.tsx
20
+ import { useEffect as useEffect5, useState as useState5 } from "react";
21
+
22
+ // src/context/AdsProvider.tsx
23
+ import React, { useState as useState4 } from "react";
24
+
25
+ // src/hooks/useInitializeAds.tsx
26
+ import { useEffect, useState } from "react";
27
+
28
+ // src/utils.ts
29
+ var UNRETRIABLE_ERRORS = [403, 429];
30
+ var fetchWithTimeout = async (input, init) => {
31
+ const { timeout = 16e3 } = init || {};
32
+ const controller = new AbortController();
33
+ const id = setTimeout(() => controller.abort(), timeout);
34
+ try {
35
+ const response = await fetch(input, {
36
+ ...init,
37
+ signal: controller.signal
38
+ });
39
+ clearTimeout(id);
40
+ return response;
41
+ } catch (e) {
42
+ clearTimeout(id);
43
+ throw e;
44
+ }
45
+ };
46
+ var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
47
+ let retries = 0;
48
+ let response;
49
+ let unretriableError;
50
+ while (retries < maxRetries) {
51
+ try {
52
+ response = await fetchWithTimeout(input, init);
53
+ if (UNRETRIABLE_ERRORS.includes(response.status)) {
54
+ unretriableError = new Error(response.statusText);
55
+ break;
56
+ }
57
+ if (response.ok) {
58
+ return response;
59
+ } else {
60
+ throw new Error(`Failed with status ${response.status}`);
61
+ }
62
+ } catch (error) {
63
+ retries++;
64
+ await new Promise((resolve) => setTimeout(resolve, retryPeriod));
65
+ }
66
+ }
67
+ if (unretriableError) {
68
+ throw unretriableError;
69
+ }
70
+ throw new Error("Failed to fetch after multiple retries");
71
+ };
72
+ var fixUrl = (adserverUrl, ad) => {
73
+ if (ad.content) {
74
+ ad.content = ad.content.replace("/ad/", `${adserverUrl}/ad/`);
75
+ }
76
+ return { ...ad, url: `${adserverUrl}${ad.url}` };
77
+ };
78
+ var mergeAds = ({
79
+ initAds,
80
+ preloadAds,
81
+ streamAds,
82
+ viewedAds
83
+ }) => {
84
+ const ads = [...initAds];
85
+ if (Array.isArray(preloadAds)) {
86
+ for (const ad of preloadAds) {
87
+ const index = ads.findIndex((a) => a.code === ad.code);
88
+ if (index !== -1) {
89
+ ads[index] = ad;
90
+ } else {
91
+ ads.push(ad);
92
+ }
93
+ }
94
+ }
95
+ if (Array.isArray(streamAds)) {
96
+ for (const ad of streamAds) {
97
+ const index = ads.findIndex(
98
+ (a) => a.code === ad.code && a.messageId === ad.messageId
99
+ );
100
+ if (index !== -1) {
101
+ ads[index] = ad;
102
+ } else {
103
+ ads.push(ad);
104
+ }
105
+ }
106
+ }
107
+ for (const ad of ads) {
108
+ ad.viewed = viewedAds.includes(ad?.id || "placeholder");
109
+ }
110
+ return ads;
111
+ };
112
+ function mergeStyles(propStyles, defaultStyles, overridingStyles) {
113
+ function deepMerge(target, source) {
114
+ if (typeof target !== "object" || typeof source !== "object") {
115
+ return source;
116
+ }
117
+ const result = { ...target };
118
+ for (const key in source) {
119
+ if (source.hasOwnProperty(key)) {
120
+ if (typeof source[key] === "object" && !Array.isArray(source[key])) {
121
+ result[key] = deepMerge(target[key], source[key]);
122
+ } else {
123
+ result[key] = source[key];
124
+ }
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+ let mergedStyles = deepMerge({ ...defaultStyles }, propStyles || {});
130
+ mergedStyles = deepMerge(mergedStyles, overridingStyles || {});
131
+ return mergedStyles;
132
+ }
133
+ var parseMessageText = (text) => {
134
+ const parts = [];
135
+ let start = 0;
136
+ const regex = /(\*(.*?)\*)|\[([^\]]+)\]\(([^)]+)\)/g;
137
+ let match;
138
+ while ((match = regex.exec(text)) !== null) {
139
+ if (match.index > start) {
140
+ parts.push({ text: text.substring(start, match.index), textType: "normal" });
141
+ }
142
+ if (match[1]) {
143
+ const themedText = match[2];
144
+ const innerMatches = themedText.match(/\[([^\]]+)\]\(([^)]+)\)/g);
145
+ if (innerMatches) {
146
+ let innerStart = 0;
147
+ innerMatches.forEach((linkMatch) => {
148
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/;
149
+ const innerLink = linkMatch.match(linkRegex);
150
+ const linkIndex = themedText.indexOf(linkMatch, innerStart);
151
+ if (linkIndex > innerStart) {
152
+ parts.push({ text: themedText.substring(innerStart, linkIndex), textType: "themed" });
153
+ }
154
+ parts.push({ text: innerLink[1], textType: "link", url: innerLink[2] });
155
+ innerStart = linkIndex + linkMatch.length;
156
+ });
157
+ if (innerStart < themedText.length) {
158
+ parts.push({ text: themedText.substring(innerStart), textType: "themed" });
159
+ }
160
+ } else {
161
+ parts.push({ text: themedText, textType: "themed" });
162
+ }
163
+ } else if (match[3] && match[4]) {
164
+ parts.push({ text: match[3], textType: "link", url: match[4] });
165
+ }
166
+ start = match.index + match[0].length;
167
+ }
168
+ if (start < text.length) {
169
+ parts.push({ text: text.substring(start), textType: "normal" });
170
+ }
171
+ return parts;
172
+ };
173
+
174
+ // src/hooks/useInitializeAds.tsx
175
+ import log from "loglevel";
176
+
177
+ // package.json
178
+ var version = "0.0.4";
179
+
180
+ // src/hooks/useInitializeAds.tsx
181
+ async function initialize(adServerUrl, publisherToken, userId, conversationId, legacyVisitorId, character) {
182
+ log.log("[BRAIN] init ads started");
183
+ const response = await fetchRetry(
184
+ `${adServerUrl}/init`,
185
+ {
186
+ timeout: 1e4,
187
+ method: "POST",
188
+ body: JSON.stringify({
189
+ publisherToken,
190
+ userId,
191
+ visitorId: userId,
192
+ conversationId,
193
+ sdkVersion: `native-${version}`,
194
+ legacyVisitorId,
195
+ character
196
+ })
197
+ },
198
+ 3,
199
+ 1e3
200
+ );
201
+ const {
202
+ sessionId,
203
+ enabledPlacements,
204
+ ads,
205
+ sessionDisabled,
206
+ streamAdServer,
207
+ onlyStream,
208
+ defaultReactNativeStyles,
209
+ overridingReactNativeStyles
210
+ } = await response.json();
211
+ const fixedAds = ads.map((ad) => {
212
+ return fixUrl(adServerUrl, ad);
213
+ });
214
+ log.log("[BRAIN] init ads done");
215
+ return {
216
+ sessionDisabled,
217
+ sessionId,
218
+ enabledPlacements,
219
+ ads: fixedAds,
220
+ streamAdServer,
221
+ onlyStream,
222
+ defaultReactNativeStylesResponse: defaultReactNativeStyles,
223
+ overridingReactNativeStylesResponse: overridingReactNativeStyles
224
+ };
225
+ }
226
+ function useInitializeAds({
227
+ adServerUrl,
228
+ publisherToken,
229
+ userId,
230
+ conversationId,
231
+ isDisabled,
232
+ character,
233
+ legacyVisitorId
234
+ }) {
235
+ const [enabledPlacements, setEnabledPlacements] = useState([]);
236
+ const [ads, setAds] = useState([]);
237
+ const [error, setError] = useState();
238
+ const [streamAdServerUrl, setStreamAdServerUrl] = useState();
239
+ const [onlyStream, setOnlyStream] = useState(false);
240
+ const [sessionId, setSessionId] = useState();
241
+ const [defaultReactNativeStyles, setDefaultReactNativeStyles] = useState();
242
+ const [overridingReactNativeStyles, setOverridingReactNativeStyles] = useState();
243
+ useEffect(() => {
244
+ setSessionId(void 0);
245
+ if (!isDisabled && userId) {
246
+ log.debug("[BRAIN] Initializing ads.");
247
+ initialize(
248
+ adServerUrl,
249
+ publisherToken,
250
+ userId,
251
+ conversationId,
252
+ legacyVisitorId,
253
+ character
254
+ ).then(
255
+ ({
256
+ sessionId: sessionId2,
257
+ enabledPlacements: enabledPlacements2,
258
+ ads: ads2,
259
+ sessionDisabled,
260
+ streamAdServer,
261
+ onlyStream: onlyStream2,
262
+ defaultReactNativeStylesResponse,
263
+ overridingReactNativeStylesResponse
264
+ }) => {
265
+ if (!sessionDisabled) {
266
+ setSessionId(sessionId2);
267
+ setEnabledPlacements(enabledPlacements2);
268
+ setAds(ads2);
269
+ setStreamAdServerUrl(streamAdServer);
270
+ setOnlyStream(onlyStream2);
271
+ setDefaultReactNativeStyles(defaultReactNativeStylesResponse);
272
+ setOverridingReactNativeStyles(overridingReactNativeStylesResponse);
273
+ } else {
274
+ log.debug("[BRAIN] Session is disabled by server.");
275
+ }
276
+ }
277
+ ).catch((e) => {
278
+ log.error("[BRAIN] Error initializing ads", e);
279
+ setError(e.message);
280
+ });
281
+ } else {
282
+ log.debug("[BRAIN] Ads are disabled.");
283
+ }
284
+ }, [
285
+ adServerUrl,
286
+ publisherToken,
287
+ userId,
288
+ conversationId,
289
+ isDisabled
290
+ ]);
291
+ return {
292
+ ads,
293
+ sessionId,
294
+ enabledPlacements,
295
+ error,
296
+ streamAdServerUrl,
297
+ onlyStream,
298
+ defaultReactNativeStyles,
299
+ overridingReactNativeStyles
300
+ };
301
+ }
302
+
303
+ // src/hooks/usePreloadAds.tsx
304
+ import { useEffect as useEffect2, useState as useState2 } from "react";
305
+ import log2 from "loglevel";
306
+ function usePreloadAds({
307
+ userId,
308
+ sessionId,
309
+ conversationId,
310
+ adserverUrl,
311
+ messages,
312
+ publisherToken,
313
+ character,
314
+ onlyStream
315
+ }) {
316
+ const [preloadDone, setPreloadDone] = useState2(false);
317
+ const [ads, setAds] = useState2([]);
318
+ const userMessagesContent = messages.filter((m) => m.role === "user").map((m) => m.content).join(" ");
319
+ useEffect2(() => {
320
+ if (onlyStream) {
321
+ setPreloadDone(true);
322
+ log2.log("[BRAIN] skipping preload ads");
323
+ return;
324
+ }
325
+ async function preload() {
326
+ setPreloadDone(false);
327
+ if (!sessionId) {
328
+ return;
329
+ }
330
+ log2.log("[BRAIN] preload ads started");
331
+ try {
332
+ const response = await fetchRetry(
333
+ `${adserverUrl}/preload`,
334
+ {
335
+ timeout: 1e4,
336
+ method: "POST",
337
+ body: JSON.stringify({
338
+ messages,
339
+ sessionId,
340
+ userId,
341
+ visitorId: userId,
342
+ publisherToken,
343
+ conversationId,
344
+ character,
345
+ sdkVersion: "native"
346
+ })
347
+ },
348
+ 3,
349
+ 1e3
350
+ );
351
+ const { ads: ads2 } = await response.json();
352
+ setAds((oldAds) => {
353
+ const newAds = ads2.map((ad) => fixUrl(adserverUrl, ad));
354
+ return [...oldAds, ...newAds];
355
+ });
356
+ setPreloadDone(true);
357
+ log2.log("[BRAIN] preload ads finished");
358
+ } catch (e) {
359
+ log2.error("[BRAIN] Error preloading ads", e);
360
+ setPreloadDone(true);
361
+ }
362
+ }
363
+ preload();
364
+ }, [sessionId, userMessagesContent, conversationId]);
365
+ return { preloadDone, ads };
366
+ }
367
+
368
+ // src/hooks/useStreamAds.tsx
369
+ import { useEffect as useEffect3, useState as useState3 } from "react";
370
+
371
+ // src/hooks/data-stream.ts
372
+ var textStreamPart = {
373
+ code: "0",
374
+ name: "text",
375
+ parse: (value) => {
376
+ if (typeof value !== "string") {
377
+ throw new Error('"text" parts expect a string value.');
378
+ }
379
+ return { type: "text", value };
380
+ }
381
+ };
382
+ var functionCallStreamPart = {
383
+ code: "1",
384
+ name: "function_call",
385
+ parse: (value) => {
386
+ if (value == null || typeof value !== "object" || !("function_call" in value) || typeof value.function_call !== "object" || value.function_call == null || !("name" in value.function_call) || !("arguments" in value.function_call) || typeof value.function_call.name !== "string" || typeof value.function_call.arguments !== "string") {
387
+ throw new Error(
388
+ '"function_call" parts expect an object with a "function_call" property.'
389
+ );
390
+ }
391
+ return {
392
+ type: "function_call",
393
+ value
394
+ };
395
+ }
396
+ };
397
+ var dataStreamPart = {
398
+ code: "2",
399
+ name: "data",
400
+ parse: (value) => {
401
+ if (!Array.isArray(value)) {
402
+ throw new Error('"data" parts expect an array value.');
403
+ }
404
+ return { type: "data", value };
405
+ }
406
+ };
407
+ var errorStreamPart = {
408
+ code: "3",
409
+ name: "error",
410
+ parse: (value) => {
411
+ if (typeof value !== "string") {
412
+ throw new Error('"error" parts expect a string value.');
413
+ }
414
+ return { type: "error", value };
415
+ }
416
+ };
417
+ var assistantMessageStreamPart = {
418
+ code: "4",
419
+ name: "assistant_message",
420
+ parse: (value) => {
421
+ if (value == null || typeof value !== "object" || !("id" in value) || !("role" in value) || !("content" in value) || typeof value.id !== "string" || typeof value.role !== "string" || value.role !== "assistant" || !Array.isArray(value.content) || !value.content.every(
422
+ (item) => item != null && typeof item === "object" && "type" in item && item.type === "text" && "text" in item && item.text != null && typeof item.text === "object" && "value" in item.text && typeof item.text.value === "string"
423
+ )) {
424
+ throw new Error(
425
+ '"assistant_message" parts expect an object with an "id", "role", and "content" property.'
426
+ );
427
+ }
428
+ return {
429
+ type: "assistant_message",
430
+ value
431
+ };
432
+ }
433
+ };
434
+ var assistantControlDataStreamPart = {
435
+ code: "5",
436
+ name: "assistant_control_data",
437
+ parse: (value) => {
438
+ if (value == null || typeof value !== "object" || !("threadId" in value) || !("messageId" in value) || typeof value.threadId !== "string" || typeof value.messageId !== "string") {
439
+ throw new Error(
440
+ '"assistant_control_data" parts expect an object with a "threadId" and "messageId" property.'
441
+ );
442
+ }
443
+ return {
444
+ type: "assistant_control_data",
445
+ value: {
446
+ threadId: value.threadId,
447
+ messageId: value.messageId
448
+ }
449
+ };
450
+ }
451
+ };
452
+ var dataMessageStreamPart = {
453
+ code: "6",
454
+ name: "data_message",
455
+ parse: (value) => {
456
+ if (value == null || typeof value !== "object" || !("role" in value) || !("data" in value) || typeof value.role !== "string" || value.role !== "data") {
457
+ throw new Error(
458
+ '"data_message" parts expect an object with a "role" and "data" property.'
459
+ );
460
+ }
461
+ return {
462
+ type: "data_message",
463
+ value
464
+ };
465
+ }
466
+ };
467
+ var toolCallStreamPart = {
468
+ code: "7",
469
+ name: "tool_calls",
470
+ parse: (value) => {
471
+ if (value == null || typeof value !== "object" || !("tool_calls" in value) || typeof value.tool_calls !== "object" || value.tool_calls == null || !Array.isArray(value.tool_calls) || value.tool_calls.some((tc) => {
472
+ tc == null || typeof tc !== "object" || !("id" in tc) || typeof tc.id !== "string" || !("type" in tc) || typeof tc.type !== "string" || !("function" in tc) || tc.function == null || typeof tc.function !== "object" || !("arguments" in tc.function) || typeof tc.function.name !== "string" || typeof tc.function.arguments !== "string";
473
+ })) {
474
+ throw new Error(
475
+ '"tool_calls" parts expect an object with a ToolCallPayload.'
476
+ );
477
+ }
478
+ return {
479
+ type: "tool_calls",
480
+ value
481
+ };
482
+ }
483
+ };
484
+ var messageAnnotationsStreamPart = {
485
+ code: "8",
486
+ name: "message_annotations",
487
+ parse: (value) => {
488
+ if (!Array.isArray(value)) {
489
+ throw new Error('"message_annotations" parts expect an array value.');
490
+ }
491
+ return { type: "message_annotations", value };
492
+ }
493
+ };
494
+ var streamParts = [
495
+ textStreamPart,
496
+ functionCallStreamPart,
497
+ dataStreamPart,
498
+ errorStreamPart,
499
+ assistantMessageStreamPart,
500
+ assistantControlDataStreamPart,
501
+ dataMessageStreamPart,
502
+ toolCallStreamPart,
503
+ messageAnnotationsStreamPart
504
+ ];
505
+ var streamPartsByCode = {
506
+ [textStreamPart.code]: textStreamPart,
507
+ [functionCallStreamPart.code]: functionCallStreamPart,
508
+ [dataStreamPart.code]: dataStreamPart,
509
+ [errorStreamPart.code]: errorStreamPart,
510
+ [assistantMessageStreamPart.code]: assistantMessageStreamPart,
511
+ [assistantControlDataStreamPart.code]: assistantControlDataStreamPart,
512
+ [dataMessageStreamPart.code]: dataMessageStreamPart,
513
+ [toolCallStreamPart.code]: toolCallStreamPart,
514
+ [messageAnnotationsStreamPart.code]: messageAnnotationsStreamPart
515
+ };
516
+ var StreamStringPrefixes = {
517
+ [textStreamPart.name]: textStreamPart.code,
518
+ [functionCallStreamPart.name]: functionCallStreamPart.code,
519
+ [dataStreamPart.name]: dataStreamPart.code,
520
+ [errorStreamPart.name]: errorStreamPart.code,
521
+ [assistantMessageStreamPart.name]: assistantMessageStreamPart.code,
522
+ [assistantControlDataStreamPart.name]: assistantControlDataStreamPart.code,
523
+ [dataMessageStreamPart.name]: dataMessageStreamPart.code,
524
+ [toolCallStreamPart.name]: toolCallStreamPart.code,
525
+ [messageAnnotationsStreamPart.name]: messageAnnotationsStreamPart.code
526
+ };
527
+ var validCodes = streamParts.map((part) => part.code);
528
+ var parseStreamPart = (line) => {
529
+ const firstSeparatorIndex = line.indexOf(":");
530
+ if (firstSeparatorIndex === -1) {
531
+ throw new Error("Failed to parse stream string. No separator found.");
532
+ }
533
+ const prefix = line.slice(0, firstSeparatorIndex);
534
+ if (!validCodes.includes(prefix)) {
535
+ throw new Error(`Failed to parse stream string. Invalid code ${prefix}.`);
536
+ }
537
+ const code = prefix;
538
+ const textValue = line.slice(firstSeparatorIndex + 1);
539
+ const any = JSON.parse(textValue);
540
+ return streamPartsByCode[code].parse(any);
541
+ };
542
+ var NEWLINE = "\n".charCodeAt(0);
543
+ function concatChunks(chunks, totalLength) {
544
+ const concatenatedChunks = new Uint8Array(totalLength);
545
+ let offset = 0;
546
+ for (const chunk of chunks) {
547
+ concatenatedChunks.set(chunk, offset);
548
+ offset += chunk.length;
549
+ }
550
+ chunks.length = 0;
551
+ return concatenatedChunks;
552
+ }
553
+ async function* readDataStream(reader, {
554
+ isAborted
555
+ } = {}) {
556
+ const decoder = new TextDecoder();
557
+ const chunks = [];
558
+ let totalLength = 0;
559
+ while (true) {
560
+ const { value } = await reader.read();
561
+ if (value) {
562
+ chunks.push(value);
563
+ totalLength += value.length;
564
+ if (value[value.length - 1] !== NEWLINE) {
565
+ continue;
566
+ }
567
+ }
568
+ if (chunks.length === 0) {
569
+ break;
570
+ }
571
+ const concatenatedChunks = concatChunks(chunks, totalLength);
572
+ totalLength = 0;
573
+ const streamParts2 = decoder.decode(concatenatedChunks, { stream: true }).split("\n").filter((line) => line !== "").map(parseStreamPart);
574
+ let i = 0;
575
+ for (const streamPart of streamParts2) {
576
+ if (!(i % 5)) {
577
+ await new Promise((resolve) => setTimeout(resolve));
578
+ }
579
+ i++;
580
+ yield streamPart;
581
+ }
582
+ if (isAborted?.()) {
583
+ reader.cancel();
584
+ break;
585
+ }
586
+ }
587
+ }
588
+
589
+ // src/hooks/useStreamAds.tsx
590
+ import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
591
+ import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
592
+ import { Platform } from "react-native";
593
+ import log3 from "loglevel";
594
+ var patchFetch = fetch;
595
+ if (Platform.OS !== "web") {
596
+ polyfillEncoding();
597
+ polyfillReadableStream();
598
+ patchFetch = __require("react-native-fetch-api").fetch;
599
+ }
600
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
601
+ log3.error(error);
602
+ });
603
+ function useStreamAds({
604
+ userId,
605
+ sessionId,
606
+ conversationId,
607
+ adserverUrl,
608
+ messages,
609
+ isLoading,
610
+ preloadDone,
611
+ enabledPlacements,
612
+ character,
613
+ publisherToken
614
+ }) {
615
+ const [ads, setAds] = useState3([]);
616
+ const fetchStream = async (messages2, code) => {
617
+ const lastAssistantMessage = [...messages2].reverse().find((m) => m.role === "assistant");
618
+ if (!lastAssistantMessage) {
619
+ return;
620
+ }
621
+ setAds((oldAds) => {
622
+ const newAds = [...oldAds];
623
+ newAds.push({
624
+ isLoading: true,
625
+ code,
626
+ messageId: lastAssistantMessage.id
627
+ });
628
+ return newAds;
629
+ });
630
+ const body = JSON.stringify({
631
+ userId,
632
+ visitorId: userId,
633
+ sessionId,
634
+ code,
635
+ messages: messages2,
636
+ publisherToken,
637
+ character,
638
+ conversationId,
639
+ sdkVersion: "native"
640
+ });
641
+ try {
642
+ const response = await patchFetch(`${adserverUrl}/stream`, {
643
+ method: "POST",
644
+ body,
645
+ headers: {
646
+ "Content-Type": "application/json"
647
+ },
648
+ reactNative: { textStreaming: true }
649
+ });
650
+ if (!response.ok) {
651
+ throw new Error("Error streaming ad");
652
+ }
653
+ if (!response.body) {
654
+ throw new Error("Response body is not a readable stream");
655
+ }
656
+ setAds(
657
+ (oldAds) => oldAds.map((ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isLoading: false, isStreaming: true } : ad)
658
+ );
659
+ let data = "";
660
+ let adData = {};
661
+ log3.log(`[BRAIN] streaming ${code} ad started`);
662
+ const reader = response.body.getReader();
663
+ for await (const { type, value } of readDataStream(reader)) {
664
+ switch (type) {
665
+ case "text":
666
+ data += value;
667
+ break;
668
+ case "data": {
669
+ let val = value;
670
+ if (Array.isArray(val)) {
671
+ val = val[0];
672
+ }
673
+ adData = { ...adData, ...val };
674
+ break;
675
+ }
676
+ case "error":
677
+ throw new Error(`Error streaming ad ${value}`);
678
+ }
679
+ setAds(
680
+ (oldAds) => oldAds.map((ad) => {
681
+ if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
682
+ const content = adData.content ?? data;
683
+ const newAd = fixUrl(adserverUrl, {
684
+ ...ad,
685
+ ...adData,
686
+ isLoading: false,
687
+ isStreaming: true,
688
+ content: content.replace(
689
+ new RegExp(`/ad/${adData.product.id}/redirect`, "g"),
690
+ `/ad/${adData.id}/redirect`
691
+ )
692
+ });
693
+ return newAd;
694
+ }
695
+ return ad;
696
+ })
697
+ );
698
+ }
699
+ setAds(
700
+ (oldAds) => oldAds.map(
701
+ (ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isStreaming: false } : ad
702
+ )
703
+ );
704
+ log3.log(`[BRAIN] streaming ${code} ad done`);
705
+ } catch (e) {
706
+ log3.error("[BRAIN] Error streaming ad", e);
707
+ setAds((oldAds) => [...oldAds, { isError: true, code }]);
708
+ }
709
+ };
710
+ useEffect3(() => {
711
+ if (!isLoading && sessionId && messages.length > 2 && messages[messages.length - 1]?.role === "assistant" && preloadDone) {
712
+ enabledPlacements.forEach((placement) => {
713
+ if (["QUERY_STREAM", "INLINE_AD", "BOX_AD"].includes(placement.format)) {
714
+ fetchStream(messages, String(placement.code));
715
+ }
716
+ });
717
+ }
718
+ }, [isLoading, preloadDone, sessionId, messages[messages.length - 1]?.role]);
719
+ return { ads };
720
+ }
721
+
722
+ // src/context/AdsProvider.tsx
723
+ import log4 from "loglevel";
724
+ import { jsx } from "react/jsx-runtime";
725
+ var AdsContext = React.createContext(null);
726
+ AdsContext.displayName = "AdsContext";
727
+ var VISITOR_ID_KEY = "brain-visitor-id";
728
+ var AdsProvider = ({
729
+ children,
730
+ messages,
731
+ publisherToken,
732
+ isLoading,
733
+ adserverUrl,
734
+ isDisabled,
735
+ character,
736
+ conversationId,
737
+ userId,
738
+ logLevel,
739
+ onAdView,
740
+ onAdClick,
741
+ ...props
742
+ }) => {
743
+ const [viewedAds, setViewedAds] = useState4([]);
744
+ const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
745
+ log4.setLevel(logLevel || "ERROR");
746
+ const {
747
+ ads: initAds,
748
+ sessionId,
749
+ enabledPlacements,
750
+ error,
751
+ streamAdServerUrl,
752
+ onlyStream,
753
+ defaultReactNativeStyles,
754
+ overridingReactNativeStyles
755
+ } = useInitializeAds({
756
+ adServerUrl: adServerUrlOrDefault,
757
+ publisherToken,
758
+ userId,
759
+ legacyVisitorId: void 0,
760
+ conversationId,
761
+ character,
762
+ isDisabled: !!isDisabled
763
+ });
764
+ const styles = mergeStyles(props.styles, defaultReactNativeStyles, overridingReactNativeStyles);
765
+ const isInitialised = !!isDisabled || !!error || !!sessionId;
766
+ const { preloadDone, ads: preloadAds } = usePreloadAds({
767
+ sessionId,
768
+ adserverUrl: adServerUrlOrDefault,
769
+ messages,
770
+ publisherToken,
771
+ conversationId,
772
+ userId,
773
+ character,
774
+ onlyStream
775
+ });
776
+ const { ads: streamAds } = useStreamAds({
777
+ userId,
778
+ sessionId,
779
+ adserverUrl: onlyStream && streamAdServerUrl ? streamAdServerUrl : adServerUrlOrDefault,
780
+ conversationId,
781
+ messages,
782
+ isLoading,
783
+ character,
784
+ preloadDone,
785
+ enabledPlacements,
786
+ publisherToken
787
+ });
788
+ const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds });
789
+ const markAdAsViewed = (ad) => {
790
+ log4.debug("[Brain] Calling onAdView");
791
+ onAdView && onAdView({
792
+ id: ad.id,
793
+ code: ad.code,
794
+ messageId: ad.messageId,
795
+ content: ad.content
796
+ });
797
+ if (!ad.id) return;
798
+ if (viewedAds.includes(ad.id)) return;
799
+ setViewedAds((old) => [...old, ad.id]);
800
+ };
801
+ const onAdClickInternal = (ad) => {
802
+ log4.debug("[Brain] Calling onAdClick");
803
+ onAdClick && onAdClick({
804
+ id: ad.id,
805
+ code: ad.code,
806
+ messageId: ad.messageId,
807
+ content: ad.content
808
+ });
809
+ };
810
+ return /* @__PURE__ */ jsx(
811
+ AdsContext.Provider,
812
+ {
813
+ value: {
814
+ adserverUrl: adServerUrlOrDefault,
815
+ streamAdServerUrl,
816
+ children,
817
+ messages,
818
+ publisherToken,
819
+ isLoading,
820
+ ads,
821
+ sessionId,
822
+ isInitialised,
823
+ isDisabled,
824
+ onAdClickInternal,
825
+ enabledPlacements,
826
+ userId,
827
+ markAdAsViewed,
828
+ onlyStream,
829
+ styles
830
+ },
831
+ children
832
+ }
833
+ );
834
+ };
835
+
836
+ // src/hooks/useAdViewed.tsx
837
+ import { useContext } from "react";
838
+ import log5 from "loglevel";
839
+ function useAdViewed(ad) {
840
+ const context = useContext(AdsContext);
841
+ const [stillMounted, setStillMounted] = useState5(false);
842
+ const sendRequest = async () => {
843
+ if (!context?.adserverUrl || !ad?.id) {
844
+ return;
845
+ }
846
+ context.markAdAsViewed(ad);
847
+ let serverUrl = context.adserverUrl;
848
+ if (context.onlyStream && context.streamAdServerUrl) {
849
+ serverUrl = context.streamAdServerUrl;
850
+ }
851
+ try {
852
+ const response = await fetch(`${serverUrl}/ad/${ad.id}/view`, {
853
+ method: "POST"
854
+ });
855
+ if (!response.ok) {
856
+ throw new Error("Error sending view request");
857
+ }
858
+ log5.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
859
+ } catch (e) {
860
+ log5.error("[BRAIN] Error sending view request", e);
861
+ }
862
+ };
863
+ useEffect5(() => {
864
+ if (!ad || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed || stillMounted) {
865
+ return;
866
+ }
867
+ log5.log("[BRAIN] setting timeout", ad.id, ad.code);
868
+ setTimeout(() => {
869
+ log5.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
870
+ setStillMounted(true);
871
+ }, 1e3);
872
+ }, [ad]);
873
+ useEffect5(() => {
874
+ if (stillMounted) {
875
+ log5.log("[BRAIN] sending request");
876
+ sendRequest();
877
+ }
878
+ }, [stillMounted]);
879
+ }
880
+
881
+ // src/formats/InlineAd.tsx
882
+ import log8 from "loglevel";
883
+
884
+ // src/components/MarkdownText.tsx
885
+ import log6 from "loglevel";
886
+ import { useContext as useContext2 } from "react";
887
+ import {
888
+ Linking,
889
+ Text
890
+ } from "react-native";
891
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
892
+ function MarkdownText({
893
+ content,
894
+ onLinkClick
895
+ }) {
896
+ const context = useContext2(AdsContext);
897
+ const textParts = parseMessageText(content);
898
+ const styles = context?.styles?.markdownText;
899
+ const linkClickHandler = (href) => {
900
+ onLinkClick();
901
+ Linking.openURL(href).catch(
902
+ (err) => log6.error("Failed to open URL:", err)
903
+ );
904
+ return false;
905
+ };
906
+ return /* @__PURE__ */ jsx2(Fragment, { children: textParts.map((t, i) => {
907
+ if (t.textType === "link") {
908
+ return /* @__PURE__ */ jsx2(
909
+ Text,
910
+ {
911
+ style: styles?.link,
912
+ onPress: () => {
913
+ linkClickHandler(t.url);
914
+ },
915
+ children: t.text
916
+ },
917
+ i + "link"
918
+ );
919
+ } else if (t.textType === "themed") {
920
+ return /* @__PURE__ */ jsx2(Text, { style: styles?.em, children: t.text }, i + "em");
921
+ } else {
922
+ return /* @__PURE__ */ jsx2(Text, { style: styles?.normal, children: t.text }, i + "normal");
923
+ }
924
+ }) });
925
+ }
926
+
927
+ // src/components/VideoPlayer.tsx
928
+ import { ResizeMode, Video } from "expo-av";
929
+ import log7 from "loglevel";
930
+ import { useContext as useContext4, useEffect as useEffect7, useRef, useState as useState7 } from "react";
931
+ import { Linking as Linking2, Text as Text2, TouchableOpacity, View as View2 } from "react-native";
932
+
933
+ // src/components/VideoProgressBar.tsx
934
+ import { useState as useState6, useEffect as useEffect6, useContext as useContext3 } from "react";
935
+ import { View } from "react-native";
936
+ import { jsx as jsx3 } from "react/jsx-runtime";
937
+ function VideoProgressBar({ videoRef }) {
938
+ const [progress, setProgress] = useState6(0);
939
+ const styles = useContext3(AdsContext)?.styles?.videoPlayer?.videoProgress;
940
+ useEffect6(() => {
941
+ const interval = setInterval(() => {
942
+ if (!videoRef.current) {
943
+ return;
944
+ }
945
+ videoRef.current.getStatusAsync().then((status) => {
946
+ if (status.isLoaded) {
947
+ const currentTime = status.positionMillis;
948
+ const duration = status.durationMillis;
949
+ const percentage = currentTime / duration * 100;
950
+ setProgress(percentage);
951
+ }
952
+ });
953
+ }, 1e3);
954
+ return () => clearInterval(interval);
955
+ }, [videoRef.current]);
956
+ return /* @__PURE__ */ jsx3(View, { style: styles?.progressContainer, children: /* @__PURE__ */ jsx3(View, { style: [styles?.progressBar, { width: `${progress}%` }] }) });
957
+ }
958
+
959
+ // src/components/VideoPlayer.tsx
960
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
961
+ var VideoPlayer = ({
962
+ src,
963
+ onProgress,
964
+ endText,
965
+ badgeText,
966
+ videoPosterUrl,
967
+ videoOrientation,
968
+ ctaText,
969
+ clickUrl,
970
+ mediaPlacement,
971
+ onLinkClick
972
+ }) => {
973
+ const videoRef = useRef(null);
974
+ const [isPlaying, setIsPlaying] = useState7(false);
975
+ const [isPaused, setIsPaused] = useState7(true);
976
+ const [isEnded, setIsEnded] = useState7(false);
977
+ const [showDownloadBadge, setShowDownloadBadge] = useState7(false);
978
+ const lastCallbackTimeRef = useRef(0);
979
+ const styles = useContext4(AdsContext)?.styles?.videoPlayer;
980
+ useEffect7(() => {
981
+ const interval = setInterval(() => {
982
+ if (videoRef.current) {
983
+ videoRef.current.getStatusAsync().then((status) => {
984
+ if (status.isLoaded) {
985
+ const progress = status.positionMillis / status.durationMillis * 100;
986
+ const now = Date.now();
987
+ if (now - lastCallbackTimeRef.current >= 3e3) {
988
+ lastCallbackTimeRef.current = now;
989
+ onProgress(progress);
990
+ }
991
+ if (status.positionMillis >= 5e3 && !showDownloadBadge) {
992
+ setShowDownloadBadge(true);
993
+ }
994
+ }
995
+ });
996
+ }
997
+ }, 1e3);
998
+ return () => clearInterval(interval);
999
+ }, [showDownloadBadge]);
1000
+ const handlePlayPause = () => {
1001
+ if (videoRef.current) {
1002
+ if (isPlaying) {
1003
+ videoRef.current.pauseAsync();
1004
+ setIsPaused(true);
1005
+ handleLinkClick(clickUrl);
1006
+ } else {
1007
+ videoRef.current.playAsync();
1008
+ setIsPaused(false);
1009
+ setIsEnded(false);
1010
+ }
1011
+ setIsPlaying(!isPlaying);
1012
+ }
1013
+ };
1014
+ const handleBadgeClick = () => {
1015
+ if (isPlaying) {
1016
+ videoRef.current?.pauseAsync();
1017
+ setIsPaused(true);
1018
+ setIsPlaying(false);
1019
+ }
1020
+ handleLinkClick(clickUrl);
1021
+ };
1022
+ const handleLinkClick = (url) => {
1023
+ if (url) {
1024
+ Linking2.openURL(url).catch((err) => log7.error("Failed to open URL:", err));
1025
+ onLinkClick();
1026
+ }
1027
+ };
1028
+ const handleEnded = () => {
1029
+ setIsPlaying(false);
1030
+ setIsEnded(true);
1031
+ setIsPaused(false);
1032
+ onProgress(100);
1033
+ };
1034
+ return /* @__PURE__ */ jsxs(View2, { style: [videoOrientation === "horizontal" ? styles?.videoOuterContainerHorizontal : styles?.videoOuterContainerVertical, mediaPlacement === "top" && styles?.videoPlacementBottom, mediaPlacement === "bottom" && styles?.videoPlacementBottom], children: [
1035
+ /* @__PURE__ */ jsx4(
1036
+ Video,
1037
+ {
1038
+ ref: videoRef,
1039
+ source: { uri: src },
1040
+ resizeMode: ResizeMode.CONTAIN,
1041
+ shouldPlay: false,
1042
+ onTouchStart: handlePlayPause,
1043
+ style: videoOrientation === "horizontal" ? styles?.videoInnerContainerHorizontal : styles?.videoInnerContainerVertical,
1044
+ videoStyle: videoOrientation === "horizontal" ? styles?.videoElementHorizontal : styles?.videoElementVertical,
1045
+ onPlaybackStatusUpdate: (status) => {
1046
+ if (status.isLoaded && status.didJustFinish && !status.isLooping) {
1047
+ handleEnded();
1048
+ }
1049
+ }
1050
+ }
1051
+ ),
1052
+ /* @__PURE__ */ jsx4(VideoProgressBar, { videoRef }),
1053
+ !isEnded && /* @__PURE__ */ jsx4(TouchableOpacity, { style: isPlaying ? styles?.playOverlay : styles?.pauseOverlay, onPress: handlePlayPause, children: !isPlaying && /* @__PURE__ */ jsx4(Text2, { style: styles?.playButton, children: "\u25B6" }) }),
1054
+ (isPaused && !isEnded || showDownloadBadge && !isEnded) && badgeText && /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles?.videoBadge, onPress: handleBadgeClick, children: /* @__PURE__ */ jsx4(Text2, { style: styles?.videoBadgeText, children: showDownloadBadge && !isPaused && !isEnded ? ctaText : badgeText }) }),
1055
+ isEnded && /* @__PURE__ */ jsxs(View2, { style: styles?.endOverlay, children: [
1056
+ /* @__PURE__ */ jsx4(TouchableOpacity, { style: styles?.endOverlayTouch, onPress: () => handleLinkClick(clickUrl), children: /* @__PURE__ */ jsx4(Text2, { style: styles?.endButton, children: ctaText }) }),
1057
+ /* @__PURE__ */ jsx4(Text2, { style: styles?.endText, children: endText })
1058
+ ] })
1059
+ ] });
1060
+ };
1061
+
1062
+ // src/hooks/useAd.tsx
1063
+ import { useContext as useContext5 } from "react";
1064
+ function useAd({ code, messageId }) {
1065
+ const context = useContext5(AdsContext);
1066
+ if (!context) {
1067
+ return null;
1068
+ }
1069
+ const ad = context.ads.find((ad2) => {
1070
+ if (messageId) {
1071
+ return ad2.code === code && ad2.messageId === messageId;
1072
+ }
1073
+ return ad2.code === code;
1074
+ });
1075
+ if (!ad) {
1076
+ return null;
1077
+ }
1078
+ const placement = context.enabledPlacements.find((p) => p.code === code);
1079
+ if (!placement) {
1080
+ return null;
1081
+ }
1082
+ const dependencies = placement.config.dependencies?.split(",").map((d) => d.trim()).filter((d) => d);
1083
+ if (!dependencies || dependencies.length === 0) {
1084
+ return ad;
1085
+ }
1086
+ const dependencyAds = context.enabledPlacements.filter(
1087
+ (p) => dependencies.includes(p.code)
1088
+ );
1089
+ if (!dependencyAds.length) {
1090
+ return ad;
1091
+ }
1092
+ const allAdsLoadedAndNotStreaming = dependencyAds.every((p) => {
1093
+ let a;
1094
+ if (messageId) {
1095
+ a = context.ads.find(
1096
+ (ad2) => ad2.code === p.code && ad2.messageId === messageId
1097
+ );
1098
+ } else {
1099
+ a = context.ads.find((ad2) => ad2.code === p.code);
1100
+ }
1101
+ return a && !a.isLoading && !a.isStreaming || a && a.isError;
1102
+ });
1103
+ if (!allAdsLoadedAndNotStreaming) {
1104
+ return null;
1105
+ }
1106
+ return ad;
1107
+ }
1108
+
1109
+ // src/formats/InlineAd.tsx
1110
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1111
+ var InlineAd = ({ code, messageId, wrapper }) => {
1112
+ const ad = useAd({ code, messageId });
1113
+ const [linkIncluded, setLinkIncluded] = useState8(false);
1114
+ const context = useContext6(AdsContext);
1115
+ const styles = context?.styles?.inlineAd;
1116
+ useAdViewed(ad);
1117
+ useEffect8(() => {
1118
+ if (ad?.content && !ad.isStreaming && !ad.content.match(/\[.*?\]\(.*?\)/) && !linkIncluded) {
1119
+ setLinkIncluded(true);
1120
+ }
1121
+ }, [ad]);
1122
+ if (!ad || !ad.content || context?.isDisabled) return null;
1123
+ if (ad.isLoading || ad.content?.trim().toLowerCase().includes("none"))
1124
+ return null;
1125
+ const onProgress = (progress) => {
1126
+ log8.log(`Progress: ${progress}`);
1127
+ };
1128
+ const handleImageClick = (url) => {
1129
+ if (url) {
1130
+ context?.onAdClickInternal(ad);
1131
+ if (Platform2.OS === "web") {
1132
+ window.open(url, "_blank");
1133
+ } else {
1134
+ Linking3.openURL(url).catch(
1135
+ (err) => log8.error("Failed to open URL:", err)
1136
+ );
1137
+ }
1138
+ }
1139
+ };
1140
+ const content = /* @__PURE__ */ jsxs2(View3, { style: styles?.container, children: [
1141
+ /* @__PURE__ */ jsx5(View3, { style: styles?.adBadgeContainer, children: /* @__PURE__ */ jsx5(Text3, { style: styles?.adBadgeText, children: "Ad" }) }),
1142
+ ad.imageUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ jsx5(
1143
+ TouchableOpacity2,
1144
+ {
1145
+ style: styles?.imageContainer,
1146
+ onPress: () => handleImageClick(ad.url),
1147
+ children: /* @__PURE__ */ jsx5(Image, { source: { uri: ad.imageUrl }, style: styles?.image })
1148
+ }
1149
+ ),
1150
+ ad.videoUrl && ad.mediaPlacement === "top" && !ad.isStreaming && /* @__PURE__ */ jsx5(
1151
+ VideoPlayer,
1152
+ {
1153
+ src: ad.videoUrl,
1154
+ onProgress,
1155
+ endText: ad.videoEndText,
1156
+ badgeText: ad.videoBadgeText,
1157
+ videoPosterUrl: ad.videoPosterUrl,
1158
+ videoOrientation: ad.videoOrientation,
1159
+ mediaPlacement: ad.mediaPlacement,
1160
+ ctaText: ad.videoCtaText,
1161
+ onLinkClick: () => context?.onAdClickInternal(ad),
1162
+ clickUrl: ad.url
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ jsx5(Text3, { style: styles?.contentContainer, children: /* @__PURE__ */ jsx5(
1166
+ MarkdownText,
1167
+ {
1168
+ onLinkClick: () => context?.onAdClickInternal(ad),
1169
+ content: ad.content
1170
+ }
1171
+ ) }),
1172
+ ad.imageUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ jsx5(
1173
+ TouchableOpacity2,
1174
+ {
1175
+ style: styles?.imageContainer,
1176
+ onPress: () => handleImageClick(ad.url),
1177
+ children: /* @__PURE__ */ jsx5(Image, { source: { uri: ad.imageUrl }, style: styles?.image })
1178
+ }
1179
+ ),
1180
+ ad.videoUrl && ad.mediaPlacement === "bottom" && !ad.isStreaming && /* @__PURE__ */ jsx5(
1181
+ VideoPlayer,
1182
+ {
1183
+ src: ad.videoUrl,
1184
+ onProgress,
1185
+ endText: ad.videoEndText,
1186
+ badgeText: ad.videoBadgeText,
1187
+ videoPosterUrl: ad.videoPosterUrl,
1188
+ videoOrientation: ad.videoOrientation,
1189
+ mediaPlacement: ad.mediaPlacement,
1190
+ ctaText: ad.videoCtaText,
1191
+ onLinkClick: () => context?.onAdClickInternal(ad),
1192
+ clickUrl: ad.url
1193
+ }
1194
+ )
1195
+ ] });
1196
+ return wrapper ? wrapper(content) : content;
1197
+ };
1198
+ var InlineAd_default = InlineAd;
1199
+ export {
1200
+ AdsContext,
1201
+ AdsProvider,
1202
+ InlineAd_default as InlineAd,
1203
+ VISITOR_ID_KEY,
1204
+ useAd
1205
+ };