@kontextso/sdk-react-native 0.0.9 → 0.0.10-rc.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.mjs CHANGED
@@ -6,53 +6,130 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/formats/InlineAd.tsx
9
- import { useContext as useContext6, useEffect as useEffect7, useState as useState8 } from "react";
9
+ import { useContext as useContext6, useEffect as useEffect8, useState as useState7 } from "react";
10
10
  import {
11
11
  Image,
12
12
  Linking as Linking3,
13
- Platform as Platform2,
13
+ Platform,
14
14
  Text as Text3,
15
15
  TouchableOpacity as TouchableOpacity2,
16
16
  View as View3
17
17
  } from "react-native";
18
18
 
19
- // src/hooks/useAdViewed.tsx
20
- import { useEffect as useEffect4, useState as useState5 } from "react";
21
-
22
- // src/context/AdsProvider.tsx
23
- import React2, { useState as useState4 } from "react";
24
-
25
- // src/hooks/useInitializeAds.tsx
19
+ // ../sdk-common/dist/index.mjs
20
+ import { useContext } from "react";
21
+ import React2, { useEffect as useEffect4, useState as useState4 } from "react";
26
22
  import { useEffect, useState } from "react";
27
-
28
- // src/utils.ts
29
- var UNRETRIABLE_ERRORS = [400, 403, 429];
30
- var fetchWithTimeout = async (input, init) => {
31
- const { timeout = 16e3 } = init || {};
23
+ import { useEffect as useEffect2, useState as useState2 } from "react";
24
+ import { useEffect as useEffect3, useState as useState3 } from "react";
25
+ import { jsx } from "react/jsx-runtime";
26
+ import { useContext as useContext2, useEffect as useEffect5 } from "react";
27
+ var version = "0.1.12";
28
+ var Logger = class {
29
+ localLevel = "log";
30
+ remoteLevel = "error";
31
+ remoteConfig = null;
32
+ levels = {
33
+ debug: 0,
34
+ info: 1,
35
+ log: 2,
36
+ warn: 3,
37
+ error: 4,
38
+ silent: 5
39
+ };
40
+ setLocalLevel(level) {
41
+ this.localLevel = level;
42
+ }
43
+ setRemoteLevel(level) {
44
+ this.remoteLevel = level;
45
+ }
46
+ configureRemote(url, params) {
47
+ this.remoteConfig = { url, params };
48
+ }
49
+ shouldLog(level, targetLevel) {
50
+ if (targetLevel === "silent") {
51
+ return false;
52
+ }
53
+ return this.levels[level] >= this.levels[targetLevel];
54
+ }
55
+ logToConsole(level, ...args) {
56
+ if (this.shouldLog(level, this.localLevel)) {
57
+ if (level === "silent") {
58
+ return;
59
+ }
60
+ console[level](...args);
61
+ }
62
+ }
63
+ logToRemote(level, ...args) {
64
+ if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
65
+ fetch(
66
+ `${this.remoteConfig.url}/log`,
67
+ {
68
+ method: "POST",
69
+ body: JSON.stringify({
70
+ ...this.remoteConfig.params,
71
+ level,
72
+ message: args,
73
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
74
+ })
75
+ }
76
+ ).catch((e) => {
77
+ });
78
+ }
79
+ }
80
+ debug(...args) {
81
+ this.logToConsole("debug", ...args);
82
+ this.logToRemote("debug", ...args);
83
+ }
84
+ info(...args) {
85
+ this.logToConsole("info", ...args);
86
+ this.logToRemote("info", ...args);
87
+ }
88
+ log(...args) {
89
+ this.logToConsole("log", ...args);
90
+ this.logToRemote("log", ...args);
91
+ }
92
+ warn(...args) {
93
+ this.logToConsole("warn", ...args);
94
+ this.logToRemote("warn", ...args);
95
+ }
96
+ error(...args) {
97
+ this.logToConsole("error", ...args);
98
+ this.logToRemote("error", ...args);
99
+ }
100
+ };
101
+ var log = new Logger();
102
+ var log_default = log;
103
+ var UNRETRIABLE_ERRORS = [403, 429, 404];
104
+ var fetchWithTimeout = async (input, init, customFetch2) => {
105
+ const { timeout = 16e3, ...rest } = init || {};
32
106
  const controller = new AbortController();
33
107
  const id = setTimeout(() => controller.abort(), timeout);
34
108
  try {
35
- const response = await fetch(input, {
36
- ...init,
109
+ const response = await (customFetch2 || fetch)(input, {
110
+ ...rest,
37
111
  signal: controller.signal
38
112
  });
39
- clearTimeout(id);
40
113
  return response;
41
- } catch (e) {
114
+ } catch (err) {
115
+ if (controller.signal.aborted) {
116
+ throw new Error(`Fetch aborted after ${timeout}ms`);
117
+ }
118
+ throw err;
119
+ } finally {
42
120
  clearTimeout(id);
43
- throw e;
44
121
  }
45
122
  };
46
- var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
123
+ var fetchRetry = async (input, init, singleRequestTimeout, maxRetries, onRetry, customFetch2) => {
47
124
  let retries = 0;
48
125
  let response;
49
- let unretriableError;
126
+ let lastError = null;
50
127
  while (retries < maxRetries) {
51
128
  try {
52
- response = await fetchWithTimeout(input, init);
129
+ const requestInit = { ...init, timeout: singleRequestTimeout };
130
+ response = await fetchWithTimeout(input, requestInit, customFetch2);
53
131
  if (UNRETRIABLE_ERRORS.includes(response.status)) {
54
- unretriableError = new Error(response.statusText);
55
- break;
132
+ throw new Error(`Unretriable error: ${response.statusText}`);
56
133
  }
57
134
  if (response.ok) {
58
135
  return response;
@@ -60,18 +137,20 @@ var fetchRetry = async (input, init, maxRetries = 3, retryPeriod = 500) => {
60
137
  throw new Error(`Failed with status ${response.status}`);
61
138
  }
62
139
  } catch (error) {
140
+ log_default.debug(`Retrying ${input} ${retries} times, error: ${error}`);
63
141
  retries++;
64
- await new Promise((resolve) => setTimeout(resolve, retryPeriod));
142
+ lastError = error;
143
+ onRetry?.(error, retries);
65
144
  }
66
145
  }
67
- if (unretriableError) {
68
- throw unretriableError;
69
- }
70
- throw new Error("Failed to fetch after multiple retries");
146
+ throw lastError || new Error("Failed to fetch after multiple retries");
71
147
  };
72
148
  var fixUrl = (adserverUrl, ad) => {
73
149
  if (ad.content) {
74
- ad.content = ad.content.replace("/impression/", `${adserverUrl}/impression/`);
150
+ ad.content = ad.content.replace(
151
+ "/impression/",
152
+ `${adserverUrl}/impression/`
153
+ );
75
154
  ad.content = ad.content.replace("/ad/", `${adserverUrl}/impression/`);
76
155
  }
77
156
  return { ...ad, url: `${adserverUrl}${ad.url}` };
@@ -112,6 +191,16 @@ var mergeAds = ({
112
191
  }
113
192
  return ads;
114
193
  };
194
+ var loadExternalCss = (serverUrl, publisherToken, visitorId) => {
195
+ const visitorIdSearchParam = visitorId ? `&visitorId=${visitorId}` : "";
196
+ const url = `${serverUrl}/sdk/styles?publisherToken=${publisherToken}&sdkVersion=${version}${visitorIdSearchParam}`;
197
+ const link = document.createElement("link");
198
+ link.rel = "stylesheet";
199
+ link.type = "text/css";
200
+ link.href = url;
201
+ document.head.appendChild(link);
202
+ return link;
203
+ };
115
204
  function mergeStyles(propStyles, defaultStyles, overridingStyles) {
116
205
  function deepMerge(target, source) {
117
206
  if (typeof target !== "object" || typeof source !== "object") {
@@ -133,165 +222,36 @@ function mergeStyles(propStyles, defaultStyles, overridingStyles) {
133
222
  mergedStyles = deepMerge(mergedStyles, overridingStyles || {});
134
223
  return mergedStyles;
135
224
  }
136
- var parseMessageText = (text) => {
137
- const parts = [];
138
- let start = 0;
139
- const regex = /(\*(.*?)\*)|\[([^\]]+)\]\(([^)]+)\)/g;
140
- let match;
141
- while ((match = regex.exec(text)) !== null) {
142
- if (match.index > start) {
143
- parts.push({ text: text.substring(start, match.index), textType: "normal" });
144
- }
145
- if (match[1]) {
146
- const themedText = match[2];
147
- const innerMatches = themedText.match(/\[([^\]]+)\]\(([^)]+)\)/g);
148
- if (innerMatches) {
149
- let innerStart = 0;
150
- innerMatches.forEach((linkMatch) => {
151
- const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/;
152
- const innerLink = linkMatch.match(linkRegex);
153
- const linkIndex = themedText.indexOf(linkMatch, innerStart);
154
- if (linkIndex > innerStart) {
155
- parts.push({ text: themedText.substring(innerStart, linkIndex), textType: "themed" });
156
- }
157
- parts.push({ text: innerLink[1], textType: "link", url: innerLink[2] });
158
- innerStart = linkIndex + linkMatch.length;
159
- });
160
- if (innerStart < themedText.length) {
161
- parts.push({ text: themedText.substring(innerStart), textType: "themed" });
162
- }
163
- } else {
164
- parts.push({ text: themedText, textType: "themed" });
165
- }
166
- } else if (match[3] && match[4]) {
167
- parts.push({ text: match[3], textType: "link", url: match[4] });
168
- }
169
- start = match.index + match[0].length;
170
- }
171
- if (start < text.length) {
172
- parts.push({ text: text.substring(start), textType: "normal" });
173
- }
174
- return parts;
175
- };
176
-
177
- // src/log.ts
178
- var Logger = class {
179
- localLevel = "log";
180
- remoteLevel = "error";
181
- remoteConfig = null;
182
- levels = {
183
- debug: 0,
184
- info: 1,
185
- log: 2,
186
- warn: 3,
187
- error: 4,
188
- silent: 5
189
- };
190
- setLocalLevel(level) {
191
- this.localLevel = level;
192
- }
193
- setRemoteLevel(level) {
194
- this.remoteLevel = level;
195
- }
196
- configureRemote(url, params) {
197
- this.remoteConfig = { url, params };
198
- }
199
- shouldLog(level, targetLevel) {
200
- if (targetLevel === "silent") {
201
- return false;
202
- }
203
- return this.levels[level] >= this.levels[targetLevel];
204
- }
205
- logToConsole(level, ...args) {
206
- if (this.shouldLog(level, this.localLevel)) {
207
- if (level === "silent") {
208
- return;
209
- }
210
- console[level](...args);
211
- }
212
- }
213
- logToRemote(level, ...args) {
214
- if (this.remoteConfig && this.shouldLog(level, this.remoteLevel)) {
215
- fetch(
216
- `${this.remoteConfig.url}/log`,
217
- {
218
- method: "POST",
219
- body: JSON.stringify({
220
- ...this.remoteConfig.params,
221
- level,
222
- message: args,
223
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
224
- })
225
- }
226
- ).catch((e) => {
227
- });
228
- }
229
- }
230
- debug(...args) {
231
- this.logToConsole("debug", ...args);
232
- this.logToRemote("debug", ...args);
233
- }
234
- info(...args) {
235
- this.logToConsole("info", ...args);
236
- this.logToRemote("info", ...args);
237
- }
238
- log(...args) {
239
- this.logToConsole("log", ...args);
240
- this.logToRemote("log", ...args);
241
- }
242
- warn(...args) {
243
- this.logToConsole("warn", ...args);
244
- this.logToRemote("warn", ...args);
245
- }
246
- error(...args) {
247
- this.logToConsole("error", ...args);
248
- this.logToRemote("error", ...args);
249
- }
250
- };
251
- var log = new Logger();
252
- var log_default = log;
253
-
254
- // package.json
255
- var version = "0.0.9";
256
-
257
- // src/hooks/useInitializeAds.tsx
258
- var RETRY_PERIOD_MS = 500;
259
- var INIT_RETRIES = 3;
260
- async function initialize(adServerUrl, publisherToken, userId, conversationId, legacyVisitorId, character) {
261
- log_default.log("[BRAIN] init ads started");
225
+ var SINGLE_INIT_TIMEOUT_BUDGET_MS = 5e3;
226
+ async function initialise(adServerUrl, publisherToken, visitorId, userId, conversationId, legacyVisitorId, character, customFetch2) {
227
+ log_default.log("[Kontext] init ads started");
262
228
  const response = await fetchRetry(
263
229
  `${adServerUrl}/init`,
264
230
  {
265
- timeout: 1e4,
266
231
  method: "POST",
267
232
  body: JSON.stringify({
268
233
  publisherToken,
269
234
  userId,
270
- visitorId: userId,
235
+ visitorId,
271
236
  conversationId,
272
- sdkVersion: `native-${version}`,
237
+ sdkVersion: version,
273
238
  legacyVisitorId,
274
239
  character
275
240
  })
276
241
  },
277
- INIT_RETRIES,
278
- RETRY_PERIOD_MS
242
+ SINGLE_INIT_TIMEOUT_BUDGET_MS,
243
+ 3,
244
+ () => {
245
+ log_default.warn("[Kontext] Reinitialising ads");
246
+ },
247
+ customFetch2
279
248
  );
280
- const {
281
- sessionId,
282
- enabledPlacements,
283
- ads,
284
- sessionDisabled,
285
- streamAdServer,
286
- onlyStream,
287
- defaultReactNativeStyles,
288
- overridingReactNativeStyles,
289
- remoteLogLevel
290
- } = await response.json();
249
+ const { sessionId, enabledPlacements, ads, sessionDisabled, streamAdServer, onlyStream, remoteLogLevel, preloadTimeout, streamTimeout, defaultReactNativeStyles, overridingReactNativeStyles } = await response.json();
291
250
  const fixedAds = ads.map((ad) => {
292
- return fixUrl(adServerUrl, ad);
251
+ const fixedAd = fixUrl(adServerUrl, ad);
252
+ return fixedAd;
293
253
  });
294
- log_default.log("[BRAIN] init ads done");
254
+ log_default.log("[Kontext] init ads done");
295
255
  return {
296
256
  sessionDisabled,
297
257
  sessionId,
@@ -299,126 +259,127 @@ async function initialize(adServerUrl, publisherToken, userId, conversationId, l
299
259
  ads: fixedAds,
300
260
  streamAdServer,
301
261
  onlyStream,
302
- defaultReactNativeStylesResponse: defaultReactNativeStyles,
303
- overridingReactNativeStylesResponse: overridingReactNativeStyles,
304
- remoteLogLevel
262
+ remoteLogLevel,
263
+ preloadTimeout,
264
+ streamTimeout,
265
+ defaultReactNativeStyles,
266
+ overridingReactNativeStyles
305
267
  };
306
268
  }
307
269
  function useInitializeAds({
308
270
  adServerUrl,
309
271
  publisherToken,
272
+ visitorId,
310
273
  userId,
311
274
  conversationId,
312
275
  isDisabled,
313
276
  character,
314
- legacyVisitorId
277
+ legacyVisitorId,
278
+ captureError,
279
+ customFetch: customFetch2,
280
+ reactNativePropStyles
315
281
  }) {
316
282
  const [enabledPlacements, setEnabledPlacements] = useState([]);
317
283
  const [ads, setAds] = useState([]);
318
284
  const [error, setError] = useState();
319
285
  const [streamAdServerUrl, setStreamAdServerUrl] = useState();
320
286
  const [onlyStream, setOnlyStream] = useState(false);
287
+ const [preloadTimeout, setPreloadTimeout] = useState(8e3);
288
+ const [streamTimeout, setStreamTimeout] = useState(5e3);
289
+ const [reactNativeStyles, setReactNativeStyles] = useState(null);
321
290
  const [sessionId, setSessionId] = useState();
322
- const [defaultReactNativeStyles, setDefaultReactNativeStyles] = useState();
323
- const [overridingReactNativeStyles, setOverridingReactNativeStyles] = useState();
324
291
  useEffect(() => {
325
- log_default.setRemoteLevel("debug");
292
+ setSessionId(void 0);
326
293
  log_default.configureRemote(adServerUrl, {
327
294
  publisherToken,
328
295
  userId,
329
- visitorId: userId,
296
+ visitorId,
330
297
  conversationId,
331
298
  character
332
299
  });
333
- setSessionId(void 0);
334
- if (!isDisabled && userId) {
335
- log_default.debug("[BRAIN] Initializing ads.");
336
- initialize(
300
+ if (!isDisabled && visitorId && userId) {
301
+ log_default.debug("[Kontext] Initalising ads.");
302
+ log_default.setRemoteLevel("debug");
303
+ initialise(
337
304
  adServerUrl,
338
305
  publisherToken,
306
+ visitorId,
339
307
  userId,
340
308
  conversationId,
341
309
  legacyVisitorId,
342
- character
343
- ).then(
344
- ({
345
- sessionId: sessionId2,
346
- enabledPlacements: enabledPlacements2,
347
- ads: ads2,
348
- sessionDisabled,
349
- streamAdServer,
350
- onlyStream: onlyStream2,
351
- defaultReactNativeStylesResponse,
352
- overridingReactNativeStylesResponse,
353
- remoteLogLevel
354
- }) => {
310
+ character,
311
+ customFetch2
312
+ ).then(({ sessionId: sessionId2, enabledPlacements: enabledPlacements2, ads: ads2, sessionDisabled, streamAdServer, onlyStream: onlyStream2, remoteLogLevel, preloadTimeout: preloadTimeout2, streamTimeout: streamTimeout2, defaultReactNativeStyles, overridingReactNativeStyles }) => {
313
+ log_default.configureRemote(adServerUrl, {
314
+ publisherToken,
315
+ adServerUrl,
316
+ visitorId,
317
+ userId,
318
+ conversationId,
319
+ legacyVisitorId,
320
+ character,
321
+ sessionId: sessionId2
322
+ });
323
+ if (remoteLogLevel) {
355
324
  log_default.setRemoteLevel(remoteLogLevel || "silent");
356
- log_default.configureRemote(adServerUrl, {
357
- publisherToken,
358
- adServerUrl,
359
- userId,
360
- conversationId,
361
- legacyVisitorId,
362
- character,
363
- sessionId: sessionId2
364
- });
365
- if (!sessionDisabled) {
366
- setSessionId(sessionId2);
367
- setEnabledPlacements(enabledPlacements2);
368
- setAds(ads2);
369
- setStreamAdServerUrl(streamAdServer);
370
- setOnlyStream(onlyStream2);
371
- setDefaultReactNativeStyles(defaultReactNativeStylesResponse);
372
- setOverridingReactNativeStyles(overridingReactNativeStylesResponse);
373
- } else {
374
- log_default.debug("[BRAIN] Session is disabled by server.");
325
+ }
326
+ const mergedStyles = mergeStyles(reactNativePropStyles, defaultReactNativeStyles, overridingReactNativeStyles);
327
+ setReactNativeStyles(mergedStyles);
328
+ if (!sessionDisabled) {
329
+ setSessionId(sessionId2);
330
+ setEnabledPlacements(enabledPlacements2);
331
+ setAds(ads2);
332
+ setStreamAdServerUrl(streamAdServer);
333
+ setOnlyStream(onlyStream2);
334
+ if (preloadTimeout2) {
335
+ setPreloadTimeout(preloadTimeout2);
375
336
  }
337
+ if (streamTimeout2) {
338
+ setStreamTimeout(streamTimeout2);
339
+ }
340
+ } else {
341
+ log_default.info(`[Kontext] Session is disabled. Reason: ${sessionId2}`);
342
+ setSessionId(null);
343
+ }
344
+ }).catch((e) => {
345
+ {
346
+ log_default.warn("[Kontext] Error initializing ads", e);
347
+ setError(e.toString());
348
+ captureError(e);
376
349
  }
377
- ).catch((e) => {
378
- log_default.warn("[BRAIN] Error initializing ads", e);
379
- setError(e.message);
380
350
  });
381
351
  } else {
382
- log_default.debug("[BRAIN] Ads are disabled.");
352
+ log_default.debug("[Kontext] Ads are disabled.");
353
+ setSessionId(null);
383
354
  }
384
- }, [
385
- adServerUrl,
386
- publisherToken,
387
- userId,
388
- conversationId,
389
- isDisabled
390
- ]);
391
- return {
392
- ads,
393
- sessionId,
394
- enabledPlacements,
395
- error,
396
- streamAdServerUrl,
397
- onlyStream,
398
- defaultReactNativeStyles,
399
- overridingReactNativeStyles
400
- };
355
+ }, [adServerUrl, publisherToken, visitorId, userId, conversationId, isDisabled]);
356
+ return { ads, sessionId, enabledPlacements, error, streamAdServerUrl, onlyStream, preloadTimeout, streamTimeout, reactNativeStyles };
401
357
  }
402
-
403
- // src/hooks/usePreloadAds.tsx
404
- import { useEffect as useEffect2, useState as useState2 } from "react";
405
358
  function usePreloadAds({
406
- userId,
407
359
  sessionId,
408
360
  conversationId,
409
361
  adserverUrl,
410
362
  messages,
411
363
  publisherToken,
412
364
  character,
413
- onlyStream
365
+ onlyStream,
366
+ captureError,
367
+ preloadTimeout,
368
+ customFetch: customFetch2
414
369
  }) {
415
370
  const [preloadDone, setPreloadDone] = useState2(false);
416
371
  const [ads, setAds] = useState2([]);
417
- const userMessagesContent = messages.filter((m) => m.role === "user").map((m) => m.content).join(" ");
372
+ const lastUserMessageContent = [...messages].reverse().find((m) => m.role === "user")?.content;
373
+ const userMessagesContent = messages.slice(-6).filter((m) => m.role === "user").map((m) => m.content).join(" ");
374
+ const numberOfAssistantMessagesAtTheEnd = messages.slice().reverse().filter((m) => m.role === "assistant" || m.role === "user").findIndex((m) => m.role === "user");
375
+ const numberOfAssistantFollowups = Math.max(
376
+ 0,
377
+ numberOfAssistantMessagesAtTheEnd - 1
378
+ );
418
379
  useEffect2(() => {
419
380
  if (onlyStream) {
420
381
  setPreloadDone(true);
421
- log_default.log("[BRAIN] skipping preload ads");
382
+ log_default.log("[Kontext] skipping preload ads");
422
383
  return;
423
384
  }
424
385
  async function preload() {
@@ -426,47 +387,57 @@ function usePreloadAds({
426
387
  if (!sessionId) {
427
388
  return;
428
389
  }
429
- log_default.log("[BRAIN] preload ads started");
430
- try {
431
- const response = await fetchRetry(
432
- `${adserverUrl}/preload`,
433
- {
434
- timeout: 1e4,
435
- method: "POST",
436
- body: JSON.stringify({
437
- messages,
438
- sessionId,
439
- userId,
440
- visitorId: userId,
441
- publisherToken,
442
- conversationId,
443
- character,
444
- sdkVersion: "native"
445
- })
446
- },
447
- 3,
448
- 1e3
449
- );
450
- const { ads: ads2 } = await response.json();
390
+ if (!lastUserMessageContent) {
391
+ return;
392
+ }
393
+ log_default.log("[Kontext] preload ads started");
394
+ const response = await fetchWithTimeout(
395
+ `${adserverUrl}/preload`,
396
+ {
397
+ method: "POST",
398
+ timeout: preloadTimeout,
399
+ body: JSON.stringify({
400
+ messages,
401
+ sessionId,
402
+ publisherToken,
403
+ conversationId,
404
+ character,
405
+ sdkVersion: version
406
+ })
407
+ },
408
+ customFetch2
409
+ );
410
+ if (!response.ok) {
411
+ throw new Error("Error preloading ads. Status: " + response.status);
412
+ }
413
+ const { ads: ads2 } = await response.json();
414
+ ads2.forEach((ad) => {
451
415
  setAds((oldAds) => {
452
- const newAds = ads2.map((ad) => fixUrl(adserverUrl, ad));
453
- return [...oldAds, ...newAds];
416
+ const fixedAd = fixUrl(adserverUrl, ad);
417
+ let found = false;
418
+ let newAds = oldAds.map((a) => {
419
+ if (a.code === ad.code) {
420
+ found = true;
421
+ return fixedAd;
422
+ }
423
+ return a;
424
+ });
425
+ if (!found) {
426
+ newAds.push(fixedAd);
427
+ }
428
+ return newAds;
454
429
  });
455
- setPreloadDone(true);
456
- log_default.log("[BRAIN] preload ads finished");
457
- } catch (e) {
458
- log_default.warn("[BRAIN] Error preloading ads", e);
459
- }
430
+ });
431
+ setPreloadDone(true);
432
+ log_default.log("[Kontext] preload ads finished");
460
433
  }
461
- preload();
462
- }, [sessionId, userMessagesContent, conversationId]);
434
+ preload().catch((e) => {
435
+ captureError(e);
436
+ log_default.error("[Kontext] Error preloading ads", e);
437
+ });
438
+ }, [sessionId, userMessagesContent, numberOfAssistantFollowups]);
463
439
  return { preloadDone, ads };
464
440
  }
465
-
466
- // src/hooks/useStreamAds.tsx
467
- import { useEffect as useEffect3, useState as useState3 } from "react";
468
-
469
- // src/hooks/data-stream.ts
470
441
  var textStreamPart = {
471
442
  code: "0",
472
443
  name: "text",
@@ -669,12 +640,7 @@ async function* readDataStream(reader, {
669
640
  const concatenatedChunks = concatChunks(chunks, totalLength);
670
641
  totalLength = 0;
671
642
  const streamParts2 = decoder.decode(concatenatedChunks, { stream: true }).split("\n").filter((line) => line !== "").map(parseStreamPart);
672
- let i = 0;
673
643
  for (const streamPart of streamParts2) {
674
- if (!(i % 5)) {
675
- await new Promise((resolve) => setTimeout(resolve));
676
- }
677
- i++;
678
644
  yield streamPart;
679
645
  }
680
646
  if (isAborted?.()) {
@@ -683,25 +649,8 @@ async function* readDataStream(reader, {
683
649
  }
684
650
  }
685
651
  }
686
-
687
- // src/hooks/useStreamAds.tsx
688
- import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
689
- import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
690
- import { Platform } from "react-native";
691
- var patchFetch = fetch;
692
- if (Platform.OS !== "web") {
693
- polyfillEncoding();
694
- polyfillReadableStream();
695
- patchFetch = __require("react-native-fetch-api").fetch;
696
- }
697
- ErrorUtils.setGlobalHandler((error, isFatal) => {
698
- if (!isFatal) {
699
- log_default.warn(error);
700
- } else {
701
- log_default.error(error);
702
- }
703
- });
704
652
  function useStreamAds({
653
+ visitorId,
705
654
  userId,
706
655
  sessionId,
707
656
  conversationId,
@@ -711,7 +660,10 @@ function useStreamAds({
711
660
  preloadDone,
712
661
  enabledPlacements,
713
662
  character,
714
- publisherToken
663
+ publisherToken,
664
+ captureError,
665
+ streamTimeout,
666
+ customFetch: customFetch2
715
667
  }) {
716
668
  const [ads, setAds] = useState3([]);
717
669
  const fetchStream = async (messages2, code) => {
@@ -729,43 +681,77 @@ function useStreamAds({
729
681
  return newAds;
730
682
  });
731
683
  const body = JSON.stringify({
684
+ visitorId,
732
685
  userId,
733
- visitorId: userId,
734
686
  sessionId,
735
687
  code,
736
688
  messages: messages2,
737
689
  publisherToken,
738
690
  character,
739
691
  conversationId,
740
- sdkVersion: "native"
692
+ sdkVersion: version
741
693
  });
742
694
  try {
743
- const response = await patchFetch(`${adserverUrl}/stream`, {
744
- method: "POST",
745
- body,
746
- headers: {
747
- "Content-Type": "application/json"
695
+ const response = await fetchWithTimeout(
696
+ `${adserverUrl}/stream`,
697
+ {
698
+ method: "POST",
699
+ body,
700
+ headers: {
701
+ "Content-Type": "application/json"
702
+ },
703
+ timeout: streamTimeout
748
704
  },
749
- reactNative: { textStreaming: true }
750
- });
705
+ customFetch2
706
+ );
751
707
  if (!response.ok) {
752
- throw new Error("Error streaming ad");
708
+ throw new Error("Error streaming ad. Status: " + response.status);
753
709
  }
754
710
  if (!response.body) {
755
- throw new Error("Response body is not a readable stream");
711
+ throw new Error("Response body is not readable stream");
756
712
  }
757
- setAds(
758
- (oldAds) => oldAds.map((ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isLoading: false, isStreaming: true } : ad)
759
- );
713
+ if (response.status === 204) {
714
+ setAds((oldAds) => {
715
+ const newAds = oldAds.map((ad) => {
716
+ if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
717
+ return {
718
+ ...ad,
719
+ isLoading: false,
720
+ isStreaming: false
721
+ };
722
+ }
723
+ return ad;
724
+ });
725
+ return newAds;
726
+ });
727
+ return;
728
+ }
729
+ setAds((oldAds) => {
730
+ const newAds = oldAds.map((ad) => {
731
+ if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
732
+ return {
733
+ ...ad,
734
+ isLoading: false,
735
+ isStreaming: true
736
+ };
737
+ }
738
+ return ad;
739
+ });
740
+ return newAds;
741
+ });
760
742
  let data = "";
761
743
  let adData = {};
762
- log_default.log(`[BRAIN] streaming ${code} ad started`);
744
+ log_default.log(`[Kontext] streaming ${code} ad started`);
745
+ if (!response.body) {
746
+ throw new Error("Response body is not readable stream");
747
+ }
763
748
  const reader = response.body.getReader();
764
749
  for await (const { type, value } of readDataStream(reader)) {
765
750
  switch (type) {
766
- case "text":
751
+ case "text": {
767
752
  data += value;
768
753
  break;
754
+ }
769
755
  case "data": {
770
756
  let val = value;
771
757
  if (Array.isArray(val)) {
@@ -774,11 +760,12 @@ function useStreamAds({
774
760
  adData = { ...adData, ...val };
775
761
  break;
776
762
  }
777
- case "error":
763
+ case "error": {
778
764
  throw new Error(`Error streaming ad ${value}`);
765
+ }
779
766
  }
780
- setAds(
781
- (oldAds) => oldAds.map((ad) => {
767
+ setAds((oldAds) => {
768
+ const newAds = oldAds.map((ad) => {
782
769
  if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
783
770
  const content = adData.content ?? data;
784
771
  const newAd = fixUrl(adserverUrl, {
@@ -787,81 +774,86 @@ function useStreamAds({
787
774
  isLoading: false,
788
775
  isStreaming: true,
789
776
  content: content.replace(
790
- new RegExp(`/impression/${adData.product.id}/redirect`, "g"),
777
+ new RegExp(`/(ad|impression)/${adData.product.id}/redirect`, "g"),
791
778
  `/impression/${adData.id}/redirect`
792
779
  )
793
780
  });
794
781
  return newAd;
795
782
  }
796
783
  return ad;
797
- })
798
- );
784
+ });
785
+ return newAds;
786
+ });
799
787
  }
800
- setAds(
801
- (oldAds) => oldAds.map(
802
- (ad) => ad.messageId === lastAssistantMessage.id && ad.code === code ? { ...ad, isStreaming: false } : ad
803
- )
804
- );
805
- log_default.log(`[BRAIN] streaming ${code} ad done`);
788
+ setAds((oldAds) => {
789
+ const newAds = oldAds.map((ad) => {
790
+ if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
791
+ return {
792
+ ...ad,
793
+ isStreaming: false
794
+ };
795
+ }
796
+ return ad;
797
+ });
798
+ return newAds;
799
+ });
800
+ log_default.log(`[Kontext] streaming ${code} ad done`);
806
801
  } catch (e) {
807
- log_default.warn("[BRAIN] Error streaming ad", e);
808
- setAds((oldAds) => [...oldAds, { isError: true, code }]);
802
+ captureError(e);
803
+ log_default.error("[Kontext] Error streaming ad", e);
804
+ setAds((oldAds) => {
805
+ let found = false;
806
+ const newAds = oldAds.map((ad) => {
807
+ if (ad.messageId === lastAssistantMessage.id && ad.code === code) {
808
+ found = true;
809
+ return {
810
+ ...ad,
811
+ isLoading: false,
812
+ isStreaming: false,
813
+ isError: true
814
+ };
815
+ }
816
+ return ad;
817
+ });
818
+ if (!found) {
819
+ newAds.push({
820
+ isLoading: false,
821
+ isStreaming: false,
822
+ isError: true,
823
+ code
824
+ });
825
+ }
826
+ return newAds;
827
+ });
809
828
  }
810
829
  };
811
830
  useEffect3(() => {
812
- if (!isLoading && sessionId && messages.length > 2 && messages[messages.length - 1]?.role === "assistant" && preloadDone) {
813
- enabledPlacements.forEach((placement) => {
814
- if (["QUERY_STREAM", "INLINE_AD", "BOX_AD"].includes(placement.format)) {
831
+ if (!isLoading && sessionId && messages.length > 0 && messages[messages.length - 1]?.role === "assistant" && preloadDone) {
832
+ for (const placement of enabledPlacements) {
833
+ if (placement.format === "QUERY_STREAM" || placement.format === "INLINE_AD" || placement.format === "BOX_AD") {
815
834
  fetchStream(messages, String(placement.code));
816
835
  }
817
- });
836
+ }
818
837
  }
819
- }, [isLoading, preloadDone, sessionId, messages[messages.length - 1]?.role]);
838
+ }, [isLoading, preloadDone, sessionId]);
820
839
  return { ads };
821
840
  }
822
-
823
- // src/components/ErrorBoundary.tsx
824
- import React from "react";
825
- var captureErrorFn = (adServerUrl, error, componentStack, context) => {
826
- fetch(`${adServerUrl}/error`, {
841
+ var DEFAULT_AD_SERVER_URL = "https://server.megabrain.co";
842
+ var captureErrorFn = (error, componentStack, additionalData) => {
843
+ const adserverUrl = additionalData?.adserverUrl || DEFAULT_AD_SERVER_URL;
844
+ fetch(`${adserverUrl}/error`, {
827
845
  method: "POST",
828
846
  body: JSON.stringify({
829
- error: error?.message,
847
+ error: error.message,
830
848
  stack: componentStack,
831
- context
849
+ additionalData
832
850
  })
833
851
  }).catch((e) => {
834
- log_default.warn("Error reporting client error", e);
852
+ log_default.error(e);
853
+ }).finally(() => {
854
+ log_default.error(error, componentStack);
835
855
  });
836
856
  };
837
- var ErrorBoundary = class extends React.Component {
838
- constructor(props) {
839
- super(props);
840
- this.state = {
841
- hasError: false
842
- };
843
- }
844
- static getDerivedStateFromError() {
845
- return {
846
- hasError: true
847
- };
848
- }
849
- componentDidCatch(error, info) {
850
- const adServerUrl = this.props.adserverUrl;
851
- if (adServerUrl) {
852
- captureErrorFn(adServerUrl, error, info?.componentStack, this.props.reqBodyParams);
853
- }
854
- }
855
- render() {
856
- if (this.state.hasError) {
857
- return this.props.fallback || null;
858
- }
859
- return this.props.children;
860
- }
861
- };
862
-
863
- // src/context/AdsProvider.tsx
864
- import { jsx } from "react/jsx-runtime";
865
857
  var AdsContext = React2.createContext(null);
866
858
  AdsContext.displayName = "AdsContext";
867
859
  var VISITOR_ID_KEY = "brain-visitor-id";
@@ -874,48 +866,89 @@ var AdsProviderWithoutBoundary = ({
874
866
  isDisabled,
875
867
  character,
876
868
  conversationId,
869
+ visitorId,
877
870
  userId,
878
871
  logLevel,
879
872
  onAdView,
880
873
  onAdClick,
881
- ...props
874
+ customFetch: customFetch2,
875
+ reactNativePropStyles,
876
+ isReactNative
882
877
  }) => {
878
+ const adServerUrlOrDefault = adserverUrl || DEFAULT_AD_SERVER_URL;
879
+ log_default.setLocalLevel(logLevel || "silent");
880
+ visitorId = visitorId || userId;
883
881
  const [viewedAds, setViewedAds] = useState4([]);
884
882
  const [clickedAds, setClickedAds] = useState4([]);
885
- const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
886
- log_default.setLocalLevel(logLevel || "silent");
883
+ const captureError = (error) => {
884
+ captureErrorFn(error, error.stack, {
885
+ publisherToken,
886
+ adserverUrl,
887
+ conversationId,
888
+ character,
889
+ visitorId,
890
+ userId,
891
+ isLoading,
892
+ messages
893
+ });
894
+ };
895
+ const legacyVisitorId = typeof window !== "undefined" && typeof window.localStorage !== "undefined" && localStorage.getItem(VISITOR_ID_KEY) ? localStorage.getItem(VISITOR_ID_KEY) : void 0;
896
+ useEffect4(() => {
897
+ if (isReactNative) {
898
+ return;
899
+ }
900
+ const linkElement = loadExternalCss(adServerUrlOrDefault, publisherToken, visitorId);
901
+ return () => {
902
+ try {
903
+ document.head.removeChild(linkElement);
904
+ } catch (e) {
905
+ }
906
+ };
907
+ }, [adServerUrlOrDefault, publisherToken, visitorId, isReactNative]);
887
908
  const {
888
909
  ads: initAds,
889
910
  sessionId,
890
911
  enabledPlacements,
891
- error,
912
+ error: initError,
892
913
  streamAdServerUrl,
893
914
  onlyStream,
894
- defaultReactNativeStyles,
895
- overridingReactNativeStyles
915
+ preloadTimeout,
916
+ streamTimeout,
917
+ reactNativeStyles
896
918
  } = useInitializeAds({
897
919
  adServerUrl: adServerUrlOrDefault,
898
920
  publisherToken,
899
921
  userId,
900
- legacyVisitorId: void 0,
922
+ visitorId,
923
+ legacyVisitorId,
901
924
  conversationId,
902
925
  character,
903
- isDisabled: !!isDisabled
926
+ isDisabled: !!isDisabled,
927
+ captureError,
928
+ customFetch: customFetch2,
929
+ reactNativePropStyles
904
930
  });
905
- const styles = mergeStyles(props.styles, defaultReactNativeStyles, overridingReactNativeStyles);
906
- const isInitialised = !!isDisabled || !!error || !!sessionId;
907
- const { preloadDone, ads: preloadAds } = usePreloadAds({
931
+ const isInitialised = !!isDisabled || !!initError || !!sessionId || sessionId === null;
932
+ const {
933
+ preloadDone,
934
+ ads: preloadAds
935
+ } = usePreloadAds({
908
936
  sessionId,
909
937
  adserverUrl: adServerUrlOrDefault,
910
938
  messages,
911
939
  publisherToken,
912
940
  conversationId,
913
941
  userId,
942
+ visitorId,
914
943
  character,
915
- onlyStream
944
+ onlyStream,
945
+ captureError,
946
+ preloadTimeout,
947
+ customFetch: customFetch2
916
948
  });
917
949
  const { ads: streamAds } = useStreamAds({
918
950
  userId,
951
+ visitorId,
919
952
  sessionId,
920
953
  adserverUrl: onlyStream && streamAdServerUrl ? streamAdServerUrl : adServerUrlOrDefault,
921
954
  conversationId,
@@ -924,23 +957,28 @@ var AdsProviderWithoutBoundary = ({
924
957
  character,
925
958
  preloadDone,
926
959
  enabledPlacements,
927
- publisherToken
960
+ publisherToken,
961
+ captureError,
962
+ streamTimeout,
963
+ customFetch: customFetch2
928
964
  });
929
965
  const ads = mergeAds({ initAds, preloadAds, streamAds, viewedAds, clickedAds });
930
966
  const markAdAsViewed = (ad) => {
931
- log_default.debug("[Brain] Calling onAdView");
932
967
  onAdView && onAdView({
933
968
  id: ad.id,
934
969
  code: ad.code,
935
970
  messageId: ad.messageId,
936
971
  content: ad.content
937
972
  });
938
- if (!ad.id) return;
939
- if (viewedAds.includes(ad.id)) return;
973
+ if (!ad.id) {
974
+ return;
975
+ }
976
+ if (viewedAds.includes(ad.id)) {
977
+ return;
978
+ }
940
979
  setViewedAds((old) => [...old, ad.id]);
941
980
  };
942
981
  const onAdClickInternal = (ad) => {
943
- log_default.debug("[Brain] Calling onAdClick");
944
982
  onAdClick && onAdClick({
945
983
  id: ad.id,
946
984
  code: ad.code,
@@ -963,81 +1001,73 @@ var AdsProviderWithoutBoundary = ({
963
1001
  streamAdServerUrl,
964
1002
  children,
965
1003
  messages,
1004
+ conversationId,
966
1005
  publisherToken,
967
1006
  isLoading,
968
1007
  ads,
969
1008
  sessionId,
970
1009
  isInitialised,
971
- conversationId,
972
- isDisabled,
973
- onAdClickInternal,
974
- enabledPlacements,
975
- userId,
976
- markAdAsViewed,
977
- onlyStream,
978
- styles
979
- },
980
- children
981
- }
982
- );
983
- };
984
- var AdsProvider = ({
985
- children,
986
- messages,
987
- publisherToken,
988
- isLoading,
989
- adserverUrl,
990
- isDisabled,
991
- character,
992
- conversationId,
993
- userId,
994
- logLevel,
995
- onAdView,
996
- onAdClick,
997
- ...props
998
- }) => {
999
- const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
1000
- return /* @__PURE__ */ jsx(
1001
- ErrorBoundary,
1002
- {
1003
- fallback: children,
1004
- adserverUrl: adServerUrlOrDefault,
1005
- reqBodyParams: {
1006
- publisherToken,
1007
- adserverUrl,
1008
- conversationId,
1009
- character,
1010
- userId,
1011
- isLoading,
1012
- messages
1013
- },
1014
- children: /* @__PURE__ */ jsx(
1015
- AdsProviderWithoutBoundary,
1016
- {
1017
- children,
1018
- messages,
1019
- publisherToken,
1020
- isLoading,
1021
- adserverUrl,
1022
- isDisabled,
1023
- character,
1024
- conversationId,
1025
- userId,
1026
- logLevel,
1027
- onAdView,
1028
- onAdClick,
1029
- ...props
1030
- }
1031
- )
1010
+ isDisabled,
1011
+ onAdClickInternal,
1012
+ enabledPlacements,
1013
+ userId,
1014
+ visitorId,
1015
+ markAdAsViewed,
1016
+ onlyStream,
1017
+ preloadTimeout,
1018
+ streamTimeout,
1019
+ reactNativeStyles
1020
+ },
1021
+ children
1032
1022
  }
1033
1023
  );
1034
1024
  };
1035
-
1036
- // src/hooks/useAdViewed.tsx
1037
- import { useContext } from "react";
1038
- function useAdViewed(ad) {
1025
+ function useAd({ code, messageId }) {
1039
1026
  const context = useContext(AdsContext);
1040
- const [stillMounted, setStillMounted] = useState5(false);
1027
+ if (!context) {
1028
+ return null;
1029
+ }
1030
+ const ad = context.ads.find((ad2) => {
1031
+ if (messageId) {
1032
+ return ad2.code === code && ad2.messageId === messageId;
1033
+ }
1034
+ return ad2.code === code;
1035
+ });
1036
+ if (!ad) {
1037
+ return null;
1038
+ }
1039
+ const placement = context.enabledPlacements.find((p) => p.code === code);
1040
+ if (!placement) {
1041
+ return null;
1042
+ }
1043
+ const dependencies = placement.config.dependencies?.split(",").map((d) => d.trim()).filter((d) => d);
1044
+ if (!dependencies || dependencies.length === 0) {
1045
+ return ad;
1046
+ }
1047
+ const dependencyAds = context.enabledPlacements.filter(
1048
+ (p) => dependencies.includes(p.code)
1049
+ );
1050
+ if (!dependencyAds.length) {
1051
+ return ad;
1052
+ }
1053
+ const allAdsLoadedAndNotStreaming = dependencyAds.every((p) => {
1054
+ let a;
1055
+ if (messageId) {
1056
+ a = context.ads.find(
1057
+ (ad2) => ad2.code === p.code && ad2.messageId === messageId
1058
+ );
1059
+ } else {
1060
+ a = context.ads.find((ad2) => ad2.code === p.code);
1061
+ }
1062
+ return a && !a.isLoading && !a.isStreaming || a && a.isError;
1063
+ });
1064
+ if (!allAdsLoadedAndNotStreaming) {
1065
+ return null;
1066
+ }
1067
+ return ad;
1068
+ }
1069
+ function useAdViewed(ad, ref) {
1070
+ const context = useContext2(AdsContext);
1041
1071
  const sendRequest = async () => {
1042
1072
  if (!context?.adserverUrl || !ad?.id) {
1043
1073
  return;
@@ -1054,43 +1084,95 @@ function useAdViewed(ad) {
1054
1084
  if (!response.ok) {
1055
1085
  throw new Error("Error sending view request");
1056
1086
  }
1057
- log_default.log("[BRAIN] ad marked as viewed", ad.id, ad.code);
1087
+ log_default.log("[Kontext] ad marked as viewed", ad.id, ad.code);
1058
1088
  } catch (e) {
1059
- log_default.warn("[BRAIN] Error sending view request", e);
1089
+ log_default.error("[Kontext] Error sending view request", e);
1060
1090
  }
1061
1091
  };
1062
- useEffect4(() => {
1063
- if (!ad || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed || stillMounted) {
1092
+ useEffect5(() => {
1093
+ if (!ref?.current || !ad || !context?.visitorId || !context?.userId || ad.isError || ad.isLoading || ad.isStreaming || ad.viewed) {
1064
1094
  return;
1065
1095
  }
1066
- log_default.log("[BRAIN] setting timeout", ad.id, ad.code);
1067
- setTimeout(() => {
1068
- log_default.log("[BRAIN] setting setStillMounted", ad.id, ad.code);
1069
- setStillMounted(true);
1070
- }, 1e3);
1071
- }, [ad]);
1072
- useEffect4(() => {
1073
- if (stillMounted) {
1074
- log_default.log("[BRAIN] sending request");
1096
+ if (typeof window.IntersectionObserver === "undefined") {
1075
1097
  sendRequest();
1098
+ return;
1076
1099
  }
1077
- }, [stillMounted]);
1100
+ const observer = new window.IntersectionObserver((entries, observer2) => {
1101
+ entries.forEach((entry) => {
1102
+ if (entry.isIntersecting) {
1103
+ sendRequest();
1104
+ observer2.unobserve(entry.target);
1105
+ }
1106
+ });
1107
+ });
1108
+ observer.observe(ref?.current);
1109
+ return () => {
1110
+ observer.disconnect();
1111
+ };
1112
+ }, [ad]);
1078
1113
  }
1079
1114
 
1080
1115
  // src/components/MarkdownText.tsx
1081
- import { useContext as useContext2 } from "react";
1116
+ import { useContext as useContext3 } from "react";
1082
1117
  import { Linking, Pressable, Text } from "react-native";
1118
+
1119
+ // src/utils.ts
1120
+ var getStyles = (context) => {
1121
+ return context.reactNativeStyles;
1122
+ };
1123
+ var parseMessageText = (text) => {
1124
+ const parts = [];
1125
+ let start = 0;
1126
+ const regex = /(\*(.*?)\*)|\[([^\]]+)\]\(([^)]+)\)/g;
1127
+ let match;
1128
+ while ((match = regex.exec(text)) !== null) {
1129
+ if (match.index > start) {
1130
+ parts.push({ text: text.substring(start, match.index), textType: "normal" });
1131
+ }
1132
+ if (match[1]) {
1133
+ const themedText = match[2];
1134
+ const innerMatches = themedText.match(/\[([^\]]+)\]\(([^)]+)\)/g);
1135
+ if (innerMatches) {
1136
+ let innerStart = 0;
1137
+ innerMatches.forEach((linkMatch) => {
1138
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/;
1139
+ const innerLink = linkMatch.match(linkRegex);
1140
+ const linkIndex = themedText.indexOf(linkMatch, innerStart);
1141
+ if (linkIndex > innerStart) {
1142
+ parts.push({ text: themedText.substring(innerStart, linkIndex), textType: "themed" });
1143
+ }
1144
+ parts.push({ text: innerLink[1], textType: "link", url: innerLink[2] });
1145
+ innerStart = linkIndex + linkMatch.length;
1146
+ });
1147
+ if (innerStart < themedText.length) {
1148
+ parts.push({ text: themedText.substring(innerStart), textType: "themed" });
1149
+ }
1150
+ } else {
1151
+ parts.push({ text: themedText, textType: "themed" });
1152
+ }
1153
+ } else if (match[3] && match[4]) {
1154
+ parts.push({ text: match[3], textType: "link", url: match[4] });
1155
+ }
1156
+ start = match.index + match[0].length;
1157
+ }
1158
+ if (start < text.length) {
1159
+ parts.push({ text: text.substring(start), textType: "normal" });
1160
+ }
1161
+ return parts;
1162
+ };
1163
+
1164
+ // src/components/MarkdownText.tsx
1083
1165
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
1084
1166
  function MarkdownText({
1085
1167
  content,
1086
1168
  onLinkClick
1087
1169
  }) {
1088
- const context = useContext2(AdsContext);
1170
+ const context = useContext3(AdsContext);
1089
1171
  const textParts = parseMessageText(content);
1090
- const styles = context?.styles?.markdownText;
1172
+ const styles = getStyles(context)?.markdownText;
1091
1173
  const linkClickHandler = (href) => {
1092
1174
  onLinkClick();
1093
- Linking.openURL(href).catch((err) => log_default.warn("Failed to open URL:", err));
1175
+ Linking.openURL(href).catch((err) => log.warn("Failed to open URL:", err));
1094
1176
  return false;
1095
1177
  };
1096
1178
  return /* @__PURE__ */ jsx2(Fragment, { children: textParts.map((t, i) => {
@@ -1118,17 +1200,18 @@ function MarkdownText({
1118
1200
 
1119
1201
  // src/components/VideoPlayer.tsx
1120
1202
  import { ResizeMode, Video } from "expo-av";
1121
- import { useContext as useContext4, useEffect as useEffect6, useRef, useState as useState7 } from "react";
1203
+ import { useContext as useContext5, useEffect as useEffect7, useRef, useState as useState6 } from "react";
1122
1204
  import { Linking as Linking2, Text as Text2, TouchableOpacity, View as View2 } from "react-native";
1123
1205
 
1124
1206
  // src/components/VideoProgressBar.tsx
1125
- import { useState as useState6, useEffect as useEffect5, useContext as useContext3 } from "react";
1207
+ import { useState as useState5, useEffect as useEffect6, useContext as useContext4 } from "react";
1126
1208
  import { View } from "react-native";
1127
1209
  import { jsx as jsx3 } from "react/jsx-runtime";
1128
1210
  function VideoProgressBar({ videoRef }) {
1129
- const [progress, setProgress] = useState6(0);
1130
- const styles = useContext3(AdsContext)?.styles?.videoPlayer?.videoProgress;
1131
- useEffect5(() => {
1211
+ const [progress, setProgress] = useState5(0);
1212
+ const context = useContext4(AdsContext);
1213
+ const styles = getStyles(context)?.videoPlayer?.videoProgress;
1214
+ useEffect6(() => {
1132
1215
  const interval = setInterval(() => {
1133
1216
  if (!videoRef.current) {
1134
1217
  return;
@@ -1162,13 +1245,14 @@ var VideoPlayer = ({
1162
1245
  onLinkClick
1163
1246
  }) => {
1164
1247
  const videoRef = useRef(null);
1165
- const [isPlaying, setIsPlaying] = useState7(false);
1166
- const [isPaused, setIsPaused] = useState7(true);
1167
- const [isEnded, setIsEnded] = useState7(false);
1168
- const [showDownloadBadge, setShowDownloadBadge] = useState7(false);
1248
+ const [isPlaying, setIsPlaying] = useState6(false);
1249
+ const [isPaused, setIsPaused] = useState6(true);
1250
+ const [isEnded, setIsEnded] = useState6(false);
1251
+ const [showDownloadBadge, setShowDownloadBadge] = useState6(false);
1169
1252
  const lastCallbackTimeRef = useRef(0);
1170
- const styles = useContext4(AdsContext)?.styles?.videoPlayer;
1171
- useEffect6(() => {
1253
+ const context = useContext5(AdsContext);
1254
+ const styles = getStyles(context)?.videoPlayer;
1255
+ useEffect7(() => {
1172
1256
  const interval = setInterval(() => {
1173
1257
  if (videoRef.current) {
1174
1258
  videoRef.current.getStatusAsync().then((status) => {
@@ -1212,7 +1296,7 @@ var VideoPlayer = ({
1212
1296
  };
1213
1297
  const handleLinkClick = (url) => {
1214
1298
  if (url) {
1215
- Linking2.openURL(url).catch((err) => log_default.warn("Failed to open URL:", err));
1299
+ Linking2.openURL(url).catch((err) => log.warn("Failed to open URL:", err));
1216
1300
  onLinkClick();
1217
1301
  }
1218
1302
  };
@@ -1250,62 +1334,15 @@ var VideoPlayer = ({
1250
1334
  ] });
1251
1335
  };
1252
1336
 
1253
- // src/hooks/useAd.tsx
1254
- import { useContext as useContext5 } from "react";
1255
- function useAd({ code, messageId }) {
1256
- const context = useContext5(AdsContext);
1257
- if (!context) {
1258
- return null;
1259
- }
1260
- const ad = context.ads.find((ad2) => {
1261
- if (messageId) {
1262
- return ad2.code === code && ad2.messageId === messageId;
1263
- }
1264
- return ad2.code === code;
1265
- });
1266
- if (!ad) {
1267
- return null;
1268
- }
1269
- const placement = context.enabledPlacements.find((p) => p.code === code);
1270
- if (!placement) {
1271
- return null;
1272
- }
1273
- const dependencies = placement.config.dependencies?.split(",").map((d) => d.trim()).filter((d) => d);
1274
- if (!dependencies || dependencies.length === 0) {
1275
- return ad;
1276
- }
1277
- const dependencyAds = context.enabledPlacements.filter(
1278
- (p) => dependencies.includes(p.code)
1279
- );
1280
- if (!dependencyAds.length) {
1281
- return ad;
1282
- }
1283
- const allAdsLoadedAndNotStreaming = dependencyAds.every((p) => {
1284
- let a;
1285
- if (messageId) {
1286
- a = context.ads.find(
1287
- (ad2) => ad2.code === p.code && ad2.messageId === messageId
1288
- );
1289
- } else {
1290
- a = context.ads.find((ad2) => ad2.code === p.code);
1291
- }
1292
- return a && !a.isLoading && !a.isStreaming || a && a.isError;
1293
- });
1294
- if (!allAdsLoadedAndNotStreaming) {
1295
- return null;
1296
- }
1297
- return ad;
1298
- }
1299
-
1300
1337
  // src/formats/InlineAd.tsx
1301
1338
  import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1302
1339
  var InlineAd = ({ code, messageId, wrapper }) => {
1303
1340
  const ad = useAd({ code, messageId });
1304
- const [linkIncluded, setLinkIncluded] = useState8(false);
1341
+ const [linkIncluded, setLinkIncluded] = useState7(false);
1305
1342
  const context = useContext6(AdsContext);
1306
- const styles = context?.styles?.inlineAd;
1343
+ const styles = getStyles(context)?.inlineAd;
1307
1344
  useAdViewed(ad);
1308
- useEffect7(() => {
1345
+ useEffect8(() => {
1309
1346
  if (ad?.content && !ad.isStreaming && !ad.content.match(/\[.*?\]\(.*?\)/) && !linkIncluded) {
1310
1347
  setLinkIncluded(true);
1311
1348
  }
@@ -1314,16 +1351,16 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1314
1351
  if (ad.isLoading || ad.content?.trim().toLowerCase().includes("none"))
1315
1352
  return null;
1316
1353
  const onProgress = (progress) => {
1317
- log_default.log(`Progress: ${progress}`);
1354
+ log.log(`Progress: ${progress}`);
1318
1355
  };
1319
1356
  const handleImageClick = (url) => {
1320
1357
  if (url) {
1321
1358
  context?.onAdClickInternal(ad);
1322
- if (Platform2.OS === "web") {
1359
+ if (Platform.OS === "web") {
1323
1360
  window.open(url, "_blank");
1324
1361
  } else {
1325
1362
  Linking3.openURL(url).catch(
1326
- (err) => log_default.warn("Failed to open URL:", err)
1363
+ (err) => log.warn("Failed to open URL:", err)
1327
1364
  );
1328
1365
  }
1329
1366
  }
@@ -1391,10 +1428,122 @@ var InlineAd = ({ code, messageId, wrapper }) => {
1391
1428
  return wrapper ? wrapper(content) : content;
1392
1429
  };
1393
1430
  var InlineAd_default = InlineAd;
1431
+
1432
+ // src/components/ErrorBoundary.tsx
1433
+ import React5 from "react";
1434
+ var captureErrorFn2 = (adServerUrl, error, componentStack, context) => {
1435
+ fetch(`${adServerUrl}/error`, {
1436
+ method: "POST",
1437
+ body: JSON.stringify({
1438
+ error: error?.message,
1439
+ stack: componentStack,
1440
+ context
1441
+ })
1442
+ }).catch((e) => {
1443
+ log.warn("Error reporting client error", e);
1444
+ });
1445
+ };
1446
+ var ErrorBoundary = class extends React5.Component {
1447
+ constructor(props) {
1448
+ super(props);
1449
+ this.state = {
1450
+ hasError: false
1451
+ };
1452
+ }
1453
+ static getDerivedStateFromError() {
1454
+ return {
1455
+ hasError: true
1456
+ };
1457
+ }
1458
+ componentDidCatch(error, info) {
1459
+ const adServerUrl = this.props.adserverUrl;
1460
+ if (adServerUrl) {
1461
+ captureErrorFn2(adServerUrl, error, info?.componentStack, this.props.reqBodyParams);
1462
+ }
1463
+ }
1464
+ render() {
1465
+ if (this.state.hasError) {
1466
+ return this.props.fallback || null;
1467
+ }
1468
+ return this.props.children;
1469
+ }
1470
+ };
1471
+
1472
+ // src/context/AdsProvider.tsx
1473
+ import { Platform as Platform2 } from "react-native";
1474
+ import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
1475
+ import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-stream";
1476
+ import { jsx as jsx6 } from "react/jsx-runtime";
1477
+ var VISITOR_ID_KEY2 = "brain-visitor-id";
1478
+ var customFetch = fetch;
1479
+ if (Platform2.OS !== "web") {
1480
+ polyfillEncoding();
1481
+ polyfillReadableStream();
1482
+ customFetch = __require("react-native-fetch-api").fetch;
1483
+ }
1484
+ ErrorUtils.setGlobalHandler((error, isFatal) => {
1485
+ if (!isFatal) {
1486
+ log.warn(error);
1487
+ } else {
1488
+ log.error(error);
1489
+ }
1490
+ });
1491
+ var AdsProvider = ({
1492
+ children,
1493
+ messages,
1494
+ publisherToken,
1495
+ isLoading,
1496
+ adserverUrl,
1497
+ isDisabled,
1498
+ character,
1499
+ conversationId,
1500
+ userId,
1501
+ logLevel,
1502
+ onAdView,
1503
+ onAdClick,
1504
+ styles
1505
+ }) => {
1506
+ const adServerUrlOrDefault = adserverUrl || "https://server.megabrain.co";
1507
+ return /* @__PURE__ */ jsx6(
1508
+ ErrorBoundary,
1509
+ {
1510
+ fallback: children,
1511
+ adserverUrl: adServerUrlOrDefault,
1512
+ reqBodyParams: {
1513
+ publisherToken,
1514
+ adserverUrl,
1515
+ conversationId,
1516
+ character,
1517
+ userId,
1518
+ isLoading,
1519
+ messages
1520
+ },
1521
+ children: /* @__PURE__ */ jsx6(
1522
+ AdsProviderWithoutBoundary,
1523
+ {
1524
+ children,
1525
+ messages,
1526
+ publisherToken,
1527
+ isLoading,
1528
+ adserverUrl,
1529
+ isDisabled,
1530
+ character,
1531
+ conversationId,
1532
+ userId,
1533
+ logLevel,
1534
+ onAdView,
1535
+ onAdClick,
1536
+ reactNativePropStyles: styles,
1537
+ customFetch,
1538
+ isReactNative: true
1539
+ }
1540
+ )
1541
+ }
1542
+ );
1543
+ };
1394
1544
  export {
1395
- AdsContext,
1396
1545
  AdsProvider,
1397
1546
  InlineAd_default as InlineAd,
1398
- VISITOR_ID_KEY,
1547
+ VISITOR_ID_KEY2 as VISITOR_ID_KEY,
1399
1548
  useAd
1400
1549
  };