@simula/ads 1.1.0 → 1.2.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/README.md +18 -68
- package/dist/InChatAdSlot.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +28 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -43
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/api.d.ts +0 -1
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/styling.d.ts.map +1 -1
- package/dist/utils/validation.d.ts +0 -5
- package/dist/utils/validation.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,8 +45,7 @@ function ChatInterface() {
|
|
|
45
45
|
{msg.role === "assistant" && (
|
|
46
46
|
<InChatAdSlot
|
|
47
47
|
messages={messages.slice(0, i + 1)}
|
|
48
|
-
|
|
49
|
-
theme={{ theme: "light", accent: "blue" }}
|
|
48
|
+
theme={{ mode: "light", accent: "blue" }}
|
|
50
49
|
/>
|
|
51
50
|
)}
|
|
52
51
|
</div>
|
|
@@ -120,8 +119,7 @@ Displays an ad based on conversation context.
|
|
|
120
119
|
| -------------- | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
120
|
| `messages` | `Message[]` | ✅ | — | Array of `{ role, content }`; pass recent conversation (e.g. last 6 turns). |
|
|
122
121
|
| `trigger` | `Promise<any>` | ❌ | Fires immediately on viewability | Promise to await before fetching the ad (e.g. LLM call). |
|
|
123
|
-
| `
|
|
124
|
-
| `theme` | `SimulaTheme` | ❌ | `{ theme: 'auto', width: 'auto', accent: ['neutral','image'], font: 'sans-serif', cornerRadius: 8 }` | Customize ad appearance (see Theme Options). Arrays trigger A/B testing. |
|
|
122
|
+
| `theme` | `SimulaTheme` | ❌ | `{ mode: 'auto', width: 'auto', accent: ['neutral','image'], font: 'sans-serif', cornerRadius: 8 }` | Customize ad appearance (see Theme Options). Arrays trigger A/B testing. |
|
|
125
123
|
| `charDesc` | `string` | ❌ | `undefined` | Character description for additional context to improve ad targeting. |
|
|
126
124
|
| `debounceMs` | `number` | ❌ | `0` | Delay in milliseconds before fetching. |
|
|
127
125
|
| `onImpression` | `(ad: AdData) => void` | ❌ | `undefined` | Callback when ad is viewable (50% visible for ≥1s). |
|
|
@@ -141,7 +139,7 @@ Displays an ad based on conversation context.
|
|
|
141
139
|
|
|
142
140
|
```ts
|
|
143
141
|
interface SimulaTheme {
|
|
144
|
-
|
|
142
|
+
mode?: "light" | "dark" | "auto"; // default: "auto"
|
|
145
143
|
accent?: AccentOption | AccentOption[]; // default: ["neutral", "image"] (A/B tested)
|
|
146
144
|
font?: FontOption | FontOption[]; // default: "sans-serif"
|
|
147
145
|
width?: number | string; // default: "auto" (min 320px)
|
|
@@ -158,26 +156,28 @@ interface SimulaTheme {
|
|
|
158
156
|
> **Width:** min **320px**, accepts px/%, or `auto`
|
|
159
157
|
|
|
160
158
|
> **A/B Testing:**
|
|
161
|
-
> When you pass an **array** (e.g., `accent: ['blue', 'green', 'purple']`), Simula will **automatically A/B test** across the provided options—colors
|
|
159
|
+
> When you pass an **array** (e.g., `accent: ['blue', 'green', 'purple']`), Simula will **automatically A/B test** across the provided options—colors or fonts—and **optimize over time for the best-performing variant**.
|
|
160
|
+
>
|
|
161
|
+
> **💡 We strongly encourage adding the `"image"` option for accent** (e.g., `accent: ['blue', 'image']`)
|
|
162
162
|
|
|
163
163
|
```tsx
|
|
164
|
-
// Default
|
|
164
|
+
// Default mode (auto)
|
|
165
165
|
<InChatAdSlot messages={messages} />
|
|
166
166
|
|
|
167
|
-
// Light
|
|
168
|
-
<InChatAdSlot messages={messages} theme={{
|
|
167
|
+
// Light mode
|
|
168
|
+
<InChatAdSlot messages={messages} theme={{ mode: "light", accent: "blue" }} />
|
|
169
169
|
|
|
170
|
-
// Dark
|
|
170
|
+
// Dark mode with custom width
|
|
171
171
|
<InChatAdSlot
|
|
172
172
|
messages={messages}
|
|
173
|
-
theme={{
|
|
173
|
+
theme={{ mode: "dark", accent: "purple", width: 600, cornerRadius: 12 }}
|
|
174
174
|
/>
|
|
175
175
|
|
|
176
|
-
// A/B testing
|
|
176
|
+
// A/B testing (recommended: include "image" for best performance)
|
|
177
177
|
<InChatAdSlot
|
|
178
178
|
messages={messages}
|
|
179
179
|
theme={{
|
|
180
|
-
accent: ["blue", "green", "
|
|
180
|
+
accent: ["blue", "green", "image"], // A/B test colors - include "image" for best CPM
|
|
181
181
|
font: ["sans-serif", "serif"], // A/B test fonts
|
|
182
182
|
width: "100%"
|
|
183
183
|
}}
|
|
@@ -247,8 +247,7 @@ function ChatApp() {
|
|
|
247
247
|
key={`adslot-${i}`} // ✅ Required if rendering in a list
|
|
248
248
|
trigger={msg.llmPromise} // default: fires immediately if not provided
|
|
249
249
|
messages={messages.slice(0, i + 1)}
|
|
250
|
-
|
|
251
|
-
theme={{ theme: "light", accent: "blue", width: "auto" }}
|
|
250
|
+
theme={{ mode: "light", accent: "blue", width: "auto" }}
|
|
252
251
|
/>
|
|
253
252
|
)}
|
|
254
253
|
</div>
|
|
@@ -284,7 +283,7 @@ function ChatApp() {
|
|
|
284
283
|
* **Session Management** – Automatic
|
|
285
284
|
* **Robust Error Handling** – Graceful degradation & callbacks
|
|
286
285
|
* **TypeScript Support** – Built-in type definitions
|
|
287
|
-
* **Built-in A/B Testing** – Test multiple colors
|
|
286
|
+
* **Built-in A/B Testing** – Test multiple colors or fonts by passing arrays and let Simula optimize performance over time
|
|
288
287
|
|
|
289
288
|
---
|
|
290
289
|
|
|
@@ -342,6 +341,8 @@ MIT
|
|
|
342
341
|
|
|
343
342
|
## 📑 Appendix: Ad Formats
|
|
344
343
|
|
|
344
|
+
Format examples are shown below, as well as more custom formats. If you want any formats excluded, reach out to us and we can exclude it programmatically in the backend. But we strongly encourage allowing all formats so we can A/B test the one your users are most receptive towards to maximize CPM.
|
|
345
|
+
|
|
345
346
|
Visual examples of all available ad formats across mobile and desktop:
|
|
346
347
|
|
|
347
348
|
| Format | Mobile | Desktop |
|
|
@@ -352,55 +353,4 @@ Visual examples of all available ad formats across mobile and desktop:
|
|
|
352
353
|
| **text** | <img src="./assets/text_mobile.png" width="200" alt="Text format on mobile" /> | <img src="./assets/text_desktop.png" width="400" alt="Text format on desktop" /> |
|
|
353
354
|
| **highlight** | <img src="./assets/highlight_mobile.png" width="200" alt="Highlight format on mobile" /> | <img src="./assets/highlight_desktop.png" width="400" alt="Highlight format on desktop" /> |
|
|
354
355
|
| **visual_banner** | <img src="./assets/visual_banner_mobile.png" width="200" alt="Visual banner format on mobile" /> | <img src="./assets/visual_banner_desktop.png" width="400" alt="Visual banner format on desktop" /> |
|
|
355
|
-
|
|
|
356
|
-
|
|
357
|
-
> **Note:** The `'all'` format allows Simula to automatically select the best format based on context and performance.
|
|
358
|
-
|
|
359
|
-
---
|
|
360
|
-
|
|
361
|
-
## 📑 Appendix: Invalid Format & Accent Combinations
|
|
362
|
-
|
|
363
|
-
Certain ad formats have restrictions on which accent colors can be used. **These restrictions only apply when you specify an invalid combination with no other valid options.** When A/B testing with arrays, Simula automatically selects valid combinations from the available options.
|
|
364
|
-
|
|
365
|
-
| Format | Allowed Accents | Restrictions |
|
|
366
|
-
|--------|----------------|--------------|
|
|
367
|
-
| **interactive** | All colors, `'image'`, `'neutral'`, `'gray'`, `'tan'` | ❌ Cannot use `'transparent'` |
|
|
368
|
-
| **tips** | All colors, `'image'`, `'neutral'`, `'gray'`, `'tan'` | ❌ Cannot use `'transparent'` |
|
|
369
|
-
| **text** | **Only** `'transparent'` | ❌ Cannot use colors or `'image'` |
|
|
370
|
-
| **highlight** | All colors, `'image'`, `'neutral'`, `'gray'`, `'tan'` | ❌ Cannot use `'transparent'` |
|
|
371
|
-
| **visual_banner** | All colors, `'neutral'`, `'gray'`, `'tan'` | ❌ Cannot use `'image'` or `'transparent'` |
|
|
372
|
-
| **image_feature** | All colors, `'neutral'`, `'gray'`, `'tan'` | ❌ Cannot use `'image'` or `'transparent'` |
|
|
373
|
-
| **suggestions** | All accents allowed | ✅ No restrictions |
|
|
374
|
-
|
|
375
|
-
**Color accents:** `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'`, `'pink'`, `'orange'`, `'neutral'`, `'gray'`, `'tan'`
|
|
376
|
-
|
|
377
|
-
### Examples
|
|
378
|
-
|
|
379
|
-
```tsx
|
|
380
|
-
// ✅ Valid - single format, single accent
|
|
381
|
-
<InChatAdSlot formats="interactive" theme={{ accent: "blue" }} />
|
|
382
|
-
<InChatAdSlot formats="text" theme={{ accent: "transparent" }} />
|
|
383
|
-
<InChatAdSlot formats="visual_banner" theme={{ accent: "purple" }} />
|
|
384
|
-
|
|
385
|
-
// ✅ Valid - A/B testing with arrays (Simula auto-selects valid combinations)
|
|
386
|
-
<InChatAdSlot
|
|
387
|
-
formats={['text', 'highlight']}
|
|
388
|
-
theme={{ accent: ['image', 'transparent'] }}
|
|
389
|
-
/>
|
|
390
|
-
// If 'text' is selected → uses 'transparent' (skips 'image')
|
|
391
|
-
// If 'highlight' is selected → uses 'image' (skips 'transparent')
|
|
392
|
-
|
|
393
|
-
<InChatAdSlot
|
|
394
|
-
formats={['interactive', 'visual_banner']}
|
|
395
|
-
theme={{ accent: ['blue', 'transparent'] }}
|
|
396
|
-
/>
|
|
397
|
-
// If 'interactive' is selected → uses 'blue' (skips 'transparent')
|
|
398
|
-
// If 'visual_banner' is selected → uses 'blue' (skips 'transparent')
|
|
399
|
-
|
|
400
|
-
// ❌ Invalid - no valid options available
|
|
401
|
-
<InChatAdSlot formats="interactive" theme={{ accent: "transparent" }} /> // interactive cannot use transparent (no fallback)
|
|
402
|
-
<InChatAdSlot formats="text" theme={{ accent: "blue" }} /> // text can ONLY use transparent (no fallback)
|
|
403
|
-
<InChatAdSlot formats="text" theme={{ accent: ['blue', 'red', 'image'] }} /> // text needs transparent but none provided
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
> **Key Point:** Restrictions only matter when there are **no valid alternatives**. When A/B testing with multiple formats or accents, Simula intelligently matches compatible combinations.
|
|
356
|
+
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InChatAdSlot.d.ts","sourceRoot":"","sources":["../src/InChatAdSlot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AASjF,OAAO,EAAE,iBAAiB,EAAU,MAAM,SAAS,CAAC;AAepD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"InChatAdSlot.d.ts","sourceRoot":"","sources":["../src/InChatAdSlot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AASjF,OAAO,EAAE,iBAAiB,EAAU,MAAM,SAAS,CAAC;AAepD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAqXpD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import React$1 from 'react';
|
|
|
3
3
|
type AccentOption = 'blue' | 'red' | 'green' | 'yellow' | 'purple' | 'pink' | 'orange' | 'neutral' | 'gray' | 'tan' | 'transparent' | 'image';
|
|
4
4
|
type FontOption = 'san-serif' | 'serif' | 'monospace';
|
|
5
5
|
interface SimulaTheme {
|
|
6
|
+
mode?: 'light' | 'dark' | 'auto';
|
|
6
7
|
theme?: 'light' | 'dark' | 'auto';
|
|
7
8
|
accent?: AccentOption | AccentOption[];
|
|
8
9
|
font?: FontOption | FontOption[];
|
package/dist/index.js
CHANGED
|
@@ -52,14 +52,19 @@ const fetchAd = async (request) => {
|
|
|
52
52
|
try {
|
|
53
53
|
const conversationHistory = request.messages;
|
|
54
54
|
// Normalize theme accent and font to arrays for backend
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
// Also handle backward compatibility: prefer 'mode' over 'theme', but support both
|
|
56
|
+
const normalizedTheme = request.theme ? (() => {
|
|
57
|
+
var _a;
|
|
58
|
+
const { theme: themeDeprecated, ...themeRest } = request.theme;
|
|
59
|
+
return {
|
|
60
|
+
...themeRest,
|
|
61
|
+
mode: (_a = request.theme.mode) !== null && _a !== void 0 ? _a : themeDeprecated, // Prefer 'mode', fallback to 'theme' for backward compatibility
|
|
62
|
+
accent: request.theme.accent ? (Array.isArray(request.theme.accent) ? request.theme.accent : [request.theme.accent]) : undefined,
|
|
63
|
+
font: request.theme.font ? (Array.isArray(request.theme.font) ? request.theme.font : [request.theme.font]) : undefined,
|
|
64
|
+
};
|
|
65
|
+
})() : undefined;
|
|
60
66
|
const requestBody = {
|
|
61
67
|
messages: conversationHistory,
|
|
62
|
-
types: request.formats,
|
|
63
68
|
slot_id: request.slotId,
|
|
64
69
|
theme: normalizedTheme,
|
|
65
70
|
session_id: request.sessionId,
|
|
@@ -212,10 +217,6 @@ const validateInChatAdSlotProps = (props) => {
|
|
|
212
217
|
throw new Error(`Invalid message at index ${i}: "content" must be a string`);
|
|
213
218
|
}
|
|
214
219
|
});
|
|
215
|
-
// Validate formats (optional)
|
|
216
|
-
if (props.formats !== undefined) {
|
|
217
|
-
validateFormats(props.formats);
|
|
218
|
-
}
|
|
219
220
|
// Validate theme (optional)
|
|
220
221
|
if (props.theme !== undefined) {
|
|
221
222
|
validateTheme(props.theme);
|
|
@@ -246,50 +247,33 @@ const validateInChatAdSlotProps = (props) => {
|
|
|
246
247
|
// Note: trigger validation (Promise check) is skipped because checking instanceof Promise
|
|
247
248
|
// is unreliable across different Promise implementations and contexts
|
|
248
249
|
};
|
|
249
|
-
/**
|
|
250
|
-
* Validates formats - accepts string or string[]
|
|
251
|
-
* Throws descriptive errors for invalid formats
|
|
252
|
-
*/
|
|
253
|
-
const validateFormats = (formats) => {
|
|
254
|
-
if (!formats)
|
|
255
|
-
return;
|
|
256
|
-
const validFormatOptions = ['all', 'tips', 'interactive', 'suggestions', 'text', 'highlight', 'visual_banner', 'image_feature'];
|
|
257
|
-
// Normalize to array for validation
|
|
258
|
-
const formatsArray = Array.isArray(formats) ? formats : [formats];
|
|
259
|
-
formatsArray.forEach((format, i) => {
|
|
260
|
-
if (typeof format !== 'string') {
|
|
261
|
-
throw new Error(`Invalid format type at index ${i}: "${typeof format}". Must be a string. Valid values: ${validFormatOptions.join(', ')}`);
|
|
262
|
-
}
|
|
263
|
-
if (!validFormatOptions.includes(format)) {
|
|
264
|
-
throw new Error(`Invalid format value${Array.isArray(formats) ? ` at index ${i}` : ''}: "${format}". Valid values: ${validFormatOptions.join(', ')}`);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
};
|
|
268
250
|
/**
|
|
269
251
|
* Validates theme object
|
|
270
252
|
* Throws descriptive errors for invalid theme properties
|
|
271
253
|
*/
|
|
272
254
|
const validateTheme = (theme) => {
|
|
255
|
+
var _a;
|
|
273
256
|
if (!theme || typeof theme !== 'object') {
|
|
274
257
|
throw new Error('Invalid "theme": must be an object');
|
|
275
258
|
}
|
|
276
|
-
const
|
|
259
|
+
const validModeOptions = ['light', 'dark', 'auto'];
|
|
277
260
|
const validAccentOptions = ['blue', 'red', 'green', 'yellow', 'purple', 'pink', 'orange', 'neutral', 'gray', 'tan', 'transparent', 'image'];
|
|
278
261
|
const validFontOptions = ['san-serif', 'serif', 'monospace'];
|
|
279
|
-
const validKeys = ['theme', 'accent', 'font', 'width', 'cornerRadius'];
|
|
262
|
+
const validKeys = ['mode', 'theme', 'accent', 'font', 'width', 'cornerRadius']; // 'theme' kept for backward compatibility
|
|
280
263
|
// Check for invalid top-level keys
|
|
281
264
|
Object.keys(theme).forEach(key => {
|
|
282
265
|
if (!validKeys.includes(key)) {
|
|
283
266
|
throw new Error(`Invalid theme parameter "${key}". Valid parameters: ${validKeys.join(', ')}`);
|
|
284
267
|
}
|
|
285
268
|
});
|
|
286
|
-
// Validate theme
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
269
|
+
// Validate mode value (prefer 'mode' over 'theme' for backward compatibility)
|
|
270
|
+
const modeValue = (_a = theme.mode) !== null && _a !== void 0 ? _a : theme.theme;
|
|
271
|
+
if (modeValue !== undefined) {
|
|
272
|
+
if (typeof modeValue !== 'string') {
|
|
273
|
+
throw new Error(`Invalid mode/theme type "${typeof modeValue}". Must be a string. Valid values: ${validModeOptions.join(', ')}`);
|
|
290
274
|
}
|
|
291
|
-
if (!
|
|
292
|
-
throw new Error(`Invalid theme value "${
|
|
275
|
+
if (!validModeOptions.includes(modeValue)) {
|
|
276
|
+
throw new Error(`Invalid mode/theme value "${modeValue}". Valid values: ${validModeOptions.join(', ')}`);
|
|
293
277
|
}
|
|
294
278
|
}
|
|
295
279
|
// Validate accent value(s)
|
|
@@ -1589,7 +1573,10 @@ const getFontStyles = (font = 'san-serif') => {
|
|
|
1589
1573
|
};
|
|
1590
1574
|
|
|
1591
1575
|
const createInChatAdSlotCSS = (theme = {}) => {
|
|
1592
|
-
|
|
1576
|
+
var _a, _b;
|
|
1577
|
+
// Backward compatibility: prefer 'mode' over 'theme', but support both
|
|
1578
|
+
const themeMode = (_b = (_a = theme.mode) !== null && _a !== void 0 ? _a : theme.theme) !== null && _b !== void 0 ? _b : 'light';
|
|
1579
|
+
const { accent = 'blue', font = 'san-serif', width = 'auto', } = theme;
|
|
1593
1580
|
// If accent or font is an array, use the first value for styling
|
|
1594
1581
|
Array.isArray(accent) ? accent[0] : accent;
|
|
1595
1582
|
const effectiveFont = Array.isArray(font) ? font[0] : font;
|
|
@@ -1744,9 +1731,8 @@ const hasValidMessages = (messages) => {
|
|
|
1744
1731
|
const InChatAdSlot = (props) => {
|
|
1745
1732
|
// Validate props early
|
|
1746
1733
|
validateInChatAdSlotProps(props);
|
|
1747
|
-
const { messages, trigger, formats
|
|
1748
|
-
|
|
1749
|
-
const formats = Array.isArray(formatsRaw) ? formatsRaw : [formatsRaw];
|
|
1734
|
+
const { messages, trigger, formats, // Deprecated: ignored for backward compatibility
|
|
1735
|
+
theme = {}, debounceMs = 0, charDesc, onImpression, onClick, onError, } = props;
|
|
1750
1736
|
const { apiKey, sessionId } = useSimula();
|
|
1751
1737
|
// Generate a stable slotId for this component instance
|
|
1752
1738
|
const slotId = react.useMemo(() => `slot-${v4()}`, []);
|
|
@@ -1878,7 +1864,6 @@ const InChatAdSlot = (props) => {
|
|
|
1878
1864
|
}
|
|
1879
1865
|
const result = await fetchAd({
|
|
1880
1866
|
messages,
|
|
1881
|
-
formats,
|
|
1882
1867
|
apiKey,
|
|
1883
1868
|
slotId,
|
|
1884
1869
|
theme: themeForBackend,
|
|
@@ -1909,7 +1894,7 @@ const InChatAdSlot = (props) => {
|
|
|
1909
1894
|
finally {
|
|
1910
1895
|
setLoading(false);
|
|
1911
1896
|
}
|
|
1912
|
-
}, [hasBeenViewed, loading, hasTriggered, error, messages,
|
|
1897
|
+
}, [hasBeenViewed, loading, hasTriggered, error, messages, apiKey, slotId, theme, onError, isBot, reasons, sessionId, measuredWidth]);
|
|
1913
1898
|
useDebounce(() => {
|
|
1914
1899
|
if (shouldFetch) {
|
|
1915
1900
|
fetchAdData();
|