@kontextso/sdk-react-native 0.0.9 → 0.0.10-rc.1

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