@action-x/ad-sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,678 +1,250 @@
1
1
  # @action-x/ad-sdk
2
2
 
3
- 零框架依赖的广告 SDK,适用于任何 JavaScript / TypeScript 项目。
4
- 支持原生 JS、Vue、Angular、Svelte、React 等所有框架。
3
+ An ad SDK for AI conversation experiences, with zero framework dependencies. Works with Vanilla JS, Vue, React, Angular, Svelte, and more.
5
4
 
6
5
  ---
7
6
 
8
- ## 目录
9
-
10
- - [特性](#特性)
11
- - [安装](#安装)
12
- - [快速上手](#快速上手)
13
- - [核心概念](#核心概念)
14
- - [API 参考](#api-参考)
15
- - [AdManager](#admanager)
16
- - [fetchAds / adaptAdToKoahAd](#fetchads--adaptadtokoahad)
17
- - [HTMLRenderer](#htmlrenderer)
18
- - [DOMRenderer](#domrenderer)
19
- - [Analytics](#analytics)
20
- - [ViewabilityTracker](#viewabilitytracker)
21
- - [ClientInfo](#clientinfo)
22
- - [EventEmitter](#eventemitter)
23
- - [类型定义](#类型定义)
24
- - [CSS 样式](#css-样式)
25
- - [广告格式说明](#广告格式说明)
26
- - [高级用法](#高级用法)
27
- - [依赖 API 端点](#依赖-api-端点)
28
-
29
- ---
30
-
31
- ## 特性
32
-
33
- - **零运行时依赖** — 无 React、无 swr、无任何第三方运行时库
34
- - **框架无关** — 兼容原生 JS、Vue、React、Angular、Svelte 等所有框架
35
- - **OpenRTB 兼容** — 自动采集设备信息,符合 OpenRTB 2.5/2.6 规范
36
- - **完整追踪** — IAB/MRC 标准可视性追踪 + 曝光/点击事件上报,自动重试
37
- - **TypeScript 优先** — 完整类型声明,开箱即用,无需额外 `@types`
38
- - **多格式输出** — ESM / CommonJS / UMD,适配所有构建工具和 CDN
39
-
40
- ---
41
-
42
- ## 安装
7
+ ## Installation
43
8
 
44
9
  ```bash
45
10
  npm install @action-x/ad-sdk
46
- #
11
+ # or
47
12
  yarn add @action-x/ad-sdk
48
- #
13
+ # or
49
14
  pnpm add @action-x/ad-sdk
50
15
  ```
51
16
 
52
- **引入样式(必须):**
17
+ **Import styles (required):**
53
18
 
54
19
  ```js
55
- // 在你的入口文件或组件中引入
56
20
  import '@action-x/ad-sdk/dist/style.css';
57
21
  ```
58
22
 
59
- 或在 HTML 中:
60
-
61
- ```html
62
- <link rel="stylesheet" href="node_modules/@action-x/ad-sdk/dist/style.css" />
63
- ```
64
-
65
23
  ---
66
24
 
67
- ## 快速上手
25
+ ## AdCard Component
68
26
 
69
- ### 方式一:AdManager(推荐)
27
+ Below are AdCard wrapper examples for different frameworks. The component takes `query` and `response` from the AI conversation, then automatically requests and renders an ad card after the response is complete.
70
28
 
71
- 适合需要统一管理多个广告位、自动处理 Analytics 的场景。
29
+ ### Vue 3
30
+
31
+ ```vue
32
+ <!-- AdCard.vue -->
33
+ <template>
34
+ <div ref="adEl" />
35
+ </template>
72
36
 
73
- ```typescript
37
+ <script setup>
38
+ import { ref, onMounted, onUnmounted } from 'vue';
74
39
  import { AdManager } from '@action-x/ad-sdk';
75
40
  import '@action-x/ad-sdk/dist/style.css';
76
41
 
77
- const manager = new AdManager({
78
- apiBaseUrl: 'https://your-api.example.com/api/v1',
79
- apiKey: 'ak_your_api_key',
80
- slots: [
81
- {
82
- slotId: 'banner-top',
83
- slotName: '顶部横幅',
84
- format: 'action_card',
85
- variant: 'horizontal',
86
- size: { width: 400, height: 120, aspectRatio: '3.3:1' },
87
- count: 1,
88
- placement: { position: 'above_fold', context: 'Chat response top' },
89
- },
90
- {
91
- slotId: 'suffix-ad',
92
- slotName: '底部追加广告',
93
- format: 'suffix',
94
- size: { width: 600, height: 60 },
95
- count: 1,
96
- placement: { position: 'below_fold', context: 'After AI response' },
97
- },
98
- ],
99
- });
100
-
101
- // 监听事件
102
- manager.on('adsUpdated', (slots) => console.log('广告更新:', Object.keys(slots)));
103
- manager.on('adsError', (err) => console.error('广告请求失败:', err.message));
104
-
105
- // 请求广告(在 AI 回复完成后调用)
106
- await manager.requestAds({
107
- conversationContext: {
108
- query: '推荐一款蓝牙耳机',
109
- response: '以下是几款热门蓝牙耳机推荐...',
110
- },
111
- userContext: { sessionId: 'session-abc-123' },
112
- });
42
+ const props = defineProps(['query', 'response']);
43
+ const adEl = ref(null);
44
+ let manager;
113
45
 
114
- // 渲染到 DOM
115
- manager.render('banner-top', document.getElementById('ad-banner'));
116
- manager.render('suffix-ad', document.getElementById('ad-suffix'));
117
- ```
118
-
119
- ---
120
-
121
- ### 方式二:fetchAds + DOMRenderer(更灵活)
122
-
123
- 适合需要自行控制渲染时机的场景。
124
-
125
- ```typescript
126
- import { fetchAds, DOMRenderer } from '@action-x/ad-sdk';
127
- import '@action-x/ad-sdk/dist/style.css';
128
-
129
- const result = await fetchAds(
130
- {
131
- conversationContext: { query: '推荐一款笔记本电脑' },
132
- slots: [{
133
- slotId: 'card-1',
134
- slotName: '产品推荐卡片',
135
- format: 'action_card',
136
- size: { width: 280, height: 100 },
137
- placement: { position: 'inline', context: 'product recommendation' },
138
- }],
139
- },
140
- { apiBaseUrl: 'https://your-api.example.com/api/v1', apiKey: 'ak_xxx' }
141
- );
142
-
143
- result.slots.forEach((slot) => {
144
- if (slot.status !== 'filled') return;
145
- slot.ads.forEach((ad, index) => {
146
- const container = document.createElement('div');
147
- document.getElementById('ad-area').appendChild(container);
148
- DOMRenderer.renderActionCard(ad, container, {
149
- analytics: {
150
- requestId: result.requestId,
151
- slotId: slot.slotId,
152
- position: index,
153
- totalAds: slot.ads.length,
154
- },
155
- onClick: (ad) => console.log('点击:', ad.original.id),
156
- });
46
+ onMounted(async () => {
47
+ manager = new AdManager({
48
+ apiBaseUrl: 'https://your-api.example.com/api/v1',
49
+ apiKey: 'ak_your_api_key',
157
50
  });
158
- });
159
- ```
160
-
161
- ---
162
51
 
163
- ### 方式三:HTMLRenderer(纯字符串,适合 SSR)
52
+ await manager.requestAds({ query: props.query, response: props.response });
164
53
 
165
- ```typescript
166
- import { HTMLRenderer } from '@action-x/ad-sdk';
54
+ manager.render(adEl.value);
55
+ });
167
56
 
168
- const html = HTMLRenderer.renderActionCard(ad, { variant: 'horizontal' });
169
- document.getElementById('ad-container').innerHTML = html;
57
+ onUnmounted(() => manager?.destroy());
58
+ </script>
170
59
  ```
171
60
 
172
- ---
173
-
174
- ## 核心概念
61
+ ### React
175
62
 
176
- ### 广告请求流程
63
+ ```tsx
64
+ // components/AdCard.tsx
65
+ import { useEffect, useRef } from 'react';
66
+ import { AdManager } from '@action-x/ad-sdk';
67
+ import '@action-x/ad-sdk/dist/style.css';
177
68
 
178
- ```
179
- 用户发起对话
180
-
181
- 调用 requestAds() / fetchAds()
182
-
183
- SDK 自动采集 ClientInfo(设备、系统、地理位置)
184
-
185
- POST /api/v1/ads/request(conversationContext + slots + clientInfo)
186
-
187
- 后端返回 AdResponseBatch(requestId + 各广告位广告列表)
188
-
189
- 调用 render() / DOMRenderer.renderXxx()
190
-
191
- DOM 绑定点击事件 → 点击追踪(异步)+ 打开目标 URL
192
- IntersectionObserver → 50% 可见 + 1s 延迟 → 曝光追踪
193
- ```
69
+ interface Props {
70
+ query: string;
71
+ response: string;
72
+ }
194
73
 
195
- ### Analytics 追踪机制
74
+ export function AdCard({ query, response }: Props) {
75
+ const containerRef = useRef<HTMLDivElement>(null);
196
76
 
197
- | 事件 | 触发时机 | 端点 |
198
- |------|---------|------|
199
- | 曝光(impression)| 50% 可见 + 持续 1000ms | `POST /api/v1/ads/impression` |
200
- | 点击(click)| 用户点击 CTA 按钮 | `POST /api/v1/ads/click` |
201
- | 可视性(viewability)| 全程监测,批量上报 | `POST /api/v1/ads/viewability/batch` |
77
+ useEffect(() => {
78
+ const manager = new AdManager({
79
+ apiBaseUrl: 'https://your-api.example.com/api/v1',
80
+ apiKey: 'ak_your_api_key',
81
+ });
202
82
 
203
- ### API Key 传递
83
+ manager
84
+ .requestAds({ query, response })
85
+ .then(() => {
86
+ if (containerRef.current) {
87
+ manager.render(containerRef.current);
88
+ }
89
+ });
204
90
 
205
- ```ts
206
- // 方式 1:构造函数传入(推荐)
207
- new AdManager({ apiKey: 'ak_xxx', ... })
208
- fetchAds(params, { apiKey: 'ak_xxx' })
91
+ return () => manager.destroy();
92
+ }, [query, response]);
209
93
 
210
- // 方式 2:全局变量(适合动态配置场景)
211
- window.__AD_CONFIG__ = { apiKey: 'ak_xxx', apiBaseUrl: '...' };
94
+ return <div ref={containerRef} />;
95
+ }
212
96
  ```
213
97
 
214
- ---
215
-
216
- ## API 参考
217
-
218
- ### AdManager
219
-
220
- 主控类,管理广告请求、缓存、渲染和事件。
221
-
222
- #### 构造函数
98
+ ### Vanilla JavaScript
223
99
 
224
- ```typescript
225
- new AdManager(config: AdManagerConfig)
226
-
227
- interface AdManagerConfig {
228
- apiBaseUrl: string; // 必填:API 基础地址
229
- apiKey?: string; // 推荐:API 认证密钥
230
- slots: AdSlotRequest[]; // 必填:广告位配置列表
231
- enabled?: boolean; // 是否启用,默认 true
232
- debug?: boolean; // 调试日志,默认 false
233
- }
234
- ```
100
+ ```js
101
+ import { AdManager } from '@action-x/ad-sdk';
102
+ import '@action-x/ad-sdk/dist/style.css';
235
103
 
236
- #### `requestAds(context): Promise<AdResponseBatch>`
104
+ const manager = new AdManager({
105
+ apiBaseUrl: 'https://your-api.example.com/api/v1',
106
+ apiKey: 'ak_your_api_key',
107
+ });
237
108
 
238
- ```typescript
109
+ // Request and render ads after the AI response is complete
239
110
  await manager.requestAds({
240
- conversationContext: {
241
- query: string; // 必填:用户查询
242
- response?: string; // AI 回复(post-response 阶段)
243
- intent?: IntentResult;
244
- conversationHistory?: ConversationMessage[];
245
- },
246
- userContext?: {
247
- sessionId: string; // 必填
248
- userId?: string;
249
- demographics?: { country?: string; language?: string };
250
- profile?: UserProfile;
251
- },
111
+ query: 'Recommend a Bluetooth headset',
112
+ response: 'Here are several popular Bluetooth headset options...',
252
113
  });
253
- ```
254
114
 
255
- #### `render(slotId, container, options?): HTMLElement | null`
256
-
257
- ```typescript
258
- manager.render(
259
- slotId: string,
260
- container: HTMLElement,
261
- options?: {
262
- adIndex?: number; // 渲染第几条广告,默认 0
263
- variant?: string; // 'horizontal' | 'block' | 'card' 等
264
- onClick?: (ad) => void;
265
- onImpression?: (ad) => void;
266
- }
267
- )
115
+ manager.render(document.getElementById('ad-card'));
268
116
  ```
269
117
 
270
- #### 事件
271
-
272
- ```typescript
273
- manager.on('adsUpdated', (slots: Record<string, AdSlotResponse>) => void);
274
- manager.on('adsLoading', () => void);
275
- manager.on('adsError', (error: Error) => void);
276
- manager.on('adClicked', (adId: string, slotId: string) => void);
277
- manager.on('adImpression', (adId: string, slotId: string) => void);
278
- ```
118
+ ### CDN (No Build Tool)
279
119
 
280
- #### 其他方法
281
-
282
- | 方法 | 说明 |
283
- |------|------|
284
- | `getSlots(slotId?)` | 获取广告位数据 |
285
- | `hasAds(slotId)` | 广告位是否有广告 |
286
- | `getAds(slotId)` | 获取广告数组 |
287
- | `getLoadingStatus()` | 是否请求中 |
288
- | `setEnabled(enabled)` | 启用/禁用广告 |
289
- | `updateConfig(config)` | 动态更新配置 |
290
- | `clearSlots()` | 清空广告数据 |
291
- | `getCurrentRequestId()` | 获取最近 requestId |
292
- | `getAdAnalytics(adId)` | 获取广告 Analytics 信息 |
293
- | `trackAdClick(adId, url, adData?)` | 手动触发点击追踪 |
294
- | `trackAdImpressionAPI(adId, adData?)` | 手动触发曝光追踪 |
295
- | `destroy()` | 销毁实例,清理监听器 |
296
-
297
- ---
120
+ ```html
121
+ <link rel="stylesheet" href="https://unpkg.com/@action-x/ad-sdk/dist/style.css" />
122
+ <script src="https://unpkg.com/@action-x/ad-sdk/dist/index.umd.js"></script>
298
123
 
299
- ### fetchAds / adaptAdToKoahAd
124
+ <div id="ad-card"></div>
300
125
 
301
- ```typescript
302
- import { fetchAds, adaptAdToKoahAd } from '@action-x/ad-sdk';
126
+ <script>
127
+ const { AdManager } = ActionXAdSDK;
303
128
 
304
- // 请求广告(自动注入 ClientInfo)
305
- const result: AdResponseBatch = await fetchAds(
306
- {
307
- conversationContext: { query: '...', response?: '...' },
308
- userContext?: { sessionId: '...' },
309
- slots: AdSlotRequest[],
310
- },
311
- { apiBaseUrl?: string, apiKey?: string }
312
- );
129
+ const manager = new AdManager({
130
+ apiBaseUrl: 'https://your-api.example.com/api/v1',
131
+ apiKey: 'ak_your_api_key',
132
+ });
313
133
 
314
- // 转换 AdaptedAdContent → KoahAd
315
- const koahAd: KoahAd = adaptAdToKoahAd(adaptedAdContent);
134
+ manager
135
+ .requestAds({ query: 'User question', response: 'AI response' })
136
+ .then(() => {
137
+ manager.render(document.getElementById('ad-card'));
138
+ });
139
+ </script>
316
140
  ```
317
141
 
318
142
  ---
319
143
 
320
- ### HTMLRenderer
321
-
322
- 生成广告 HTML 字符串,适合 SSR 或自行控制 DOM。
323
-
324
- ```typescript
325
- import { HTMLRenderer } from '@action-x/ad-sdk';
326
-
327
- // Action Card
328
- HTMLRenderer.renderActionCard(ad, { variant?: 'horizontal' | 'vertical' | 'compact', includeWrapper?: boolean })
144
+ ## Usage Example
329
145
 
330
- // Suffix Ad
331
- HTMLRenderer.renderSuffixAd(ad)
146
+ ### Render an Ad Card After AI Response Completes
332
147
 
333
- // Sponsored Source
334
- HTMLRenderer.renderSponsoredSource(ad)
148
+ Using React as an example, render AdCard after streaming output finishes:
335
149
 
336
- // Lead Gen
337
- HTMLRenderer.renderLeadGenAd(ad)
150
+ ```tsx
151
+ import { useState } from 'react';
152
+ import { AdCard } from './components/AdCard';
338
153
 
339
- // 批量渲染
340
- HTMLRenderer.renderAds(ads, renderFn?)
341
- ```
342
-
343
- ---
344
-
345
- ### DOMRenderer
346
-
347
- 创建真实 DOM 并绑定事件追踪,不依赖 AdManager。
348
-
349
- ```typescript
350
- import { DOMRenderer } from '@action-x/ad-sdk';
154
+ export function ChatMessage() {
155
+ const [query, setQuery] = useState('');
156
+ const [response, setResponse] = useState('');
157
+ const [isStreaming, setIsStreaming] = useState(false);
351
158
 
352
- // Action Card(click 绑定 .ax-ad-cta)
353
- DOMRenderer.renderActionCard(ad, container, {
354
- variant?: 'horizontal' | 'vertical' | 'compact',
355
- analytics?: AdAnalyticsInfo,
356
- onClick?: (ad) => void,
357
- onImpression?: (ad) => void,
358
- })
159
+ async function handleSend(userQuery: string) {
160
+ setQuery(userQuery);
161
+ setIsStreaming(true);
162
+ setResponse('');
359
163
 
360
- // Suffix Ad(click 绑定 .ax-ad-suffix-link)
361
- DOMRenderer.renderSuffixAd(ad, container, {
362
- variant?: 'block' | 'inline' | 'minimal', // 默认 'block'
363
- analytics?, onClick?, onImpression?,
364
- })
164
+ // Receive AI response as a stream
165
+ const stream = await fetchAIResponse(userQuery);
166
+ let fullResponse = '';
167
+ for await (const chunk of stream) {
168
+ fullResponse += chunk;
169
+ setResponse(fullResponse);
170
+ }
365
171
 
366
- // Sponsored Source(click 绑定 .ax-ad-source-link)
367
- DOMRenderer.renderSponsoredSource(ad, container, {
368
- variant?: 'card' | 'list-item' | 'minimal', // 默认 'card'
369
- analytics?, onClick?, onImpression?,
370
- })
172
+ setIsStreaming(false);
173
+ // After response completes, AdCard will request and render automatically
174
+ }
371
175
 
372
- // Lead Gen(click 绑定 .ax-ad-leadgen-cta)
373
- DOMRenderer.renderLeadGenAd(ad, container, {
374
- analytics?, onClick?, onImpression?,
375
- })
176
+ return (
177
+ <div>
178
+ <div className="response">{response}</div>
376
179
 
377
- // 批量渲染
378
- DOMRenderer.renderAds(ads, container, renderFn?, options?)
180
+ {/* Show ad card after response streaming ends */}
181
+ {!isStreaming && response && (
182
+ <AdCard query={query} response={response} />
183
+ )}
184
+ </div>
185
+ );
186
+ }
379
187
  ```
380
188
 
381
- **点击流程**:异步发送 Analytics click 事件(不阻塞跳转)→ `window.open(clickUrl, '_blank')`
382
-
383
- **曝光流程**:IntersectionObserver 监听 → 50% 可见 + 1000ms → Analytics impression 事件
384
-
385
189
  ---
386
190
 
387
- ### Analytics
388
-
389
- ```typescript
390
- import {
391
- trackAdImpression, trackAdClick,
392
- getSessionId, generateViewToken,
393
- trackImpressionsBatch, trackClicksBatch,
394
- SessionManager, AnalyticsSender,
395
- createAnalytics, createSession,
396
- } from '@action-x/ad-sdk';
397
-
398
- // 快捷追踪
399
- await trackAdImpression({
400
- requestId, adId, position, totalAds, sessionId: getSessionId(),
401
- slotId?, adTitle?, format?,
402
- });
403
-
404
- await trackAdClick({
405
- requestId, adId, sessionId: getSessionId(), destinationUrl,
406
- slotId?, adTitle?, format?,
407
- });
408
-
409
- // View Token
410
- const token = generateViewToken(adId, sessionId);
191
+ ## Configuration
411
192
 
412
- // 批量
413
- await trackImpressionsBatch([event1, event2]);
414
- await trackClicksBatch([event1, event2]);
193
+ ### `new AdManager(config)`
415
194
 
416
- // 自定义实例
417
- const analytics = createAnalytics({ baseUrl: '...', timeout: 5000, debug: true });
418
- const session = createSession({ storage: 'memory' });
419
- ```
420
-
421
- #### SessionManager
195
+ | Parameter | Type | Required | Description |
196
+ |------|------|------|------|
197
+ | `apiBaseUrl` | `string` | Yes | API base URL, e.g. `https://your-api.example.com/api/v1` |
198
+ | `apiKey` | `string` | Yes | API authentication key |
199
+ | `cardOption` | `CardOption` | No | Card options with sensible defaults |
422
200
 
423
- ```typescript
424
- new SessionManager({ sessionKey?: string, storage?: 'sessionStorage' | 'memory' })
425
- // .getSessionId() 获取或创建 session ID
426
- // .regenerateSessionId() 强制生成新 ID
427
- // .clearSessionId() 清除 ID
428
- ```
429
-
430
- #### AnalyticsSender
431
-
432
- ```typescript
433
- new AnalyticsSender({
434
- baseUrl?: string, // 默认 '/api/v1/ads'
435
- timeout?: number, // 默认 10000ms
436
- retryAttempts?: number, // 默认 2
437
- retryDelay?: number, // 默认 1000ms(指数退避)
438
- debug?: boolean,
439
- })
440
- ```
441
-
442
- ---
201
+ ### Card Options `cardOption`
443
202
 
444
- ### ViewabilityTracker
203
+ | Parameter | Type | Default | Description |
204
+ |------|------|--------|------|
205
+ | `variant` | `'horizontal' \| 'vertical' \| 'compact'` | `'horizontal'` | Layout style |
206
+ | `count` | `number` | `1` | Number of ads to request |
445
207
 
446
- IAB/MRC 标准可视性追踪(>50% 可见 + >1 秒 = 达标)。
208
+ **Customization example:**
447
209
 
448
- ```typescript
449
- import { getViewabilityTracker } from '@action-x/ad-sdk';
450
-
451
- // 推荐:使用单例,自动注册 beforeunload
452
- const tracker = getViewabilityTracker(
453
- {
454
- minVisiblePercentage?: 50, // 默认 50
455
- minViewableDuration?: 1000, // 默认 1000ms
456
- maxTrackingDuration?: 60000, // 默认 60000ms
457
- batchConfig?: { maxBatchSize?: 5, maxBatchWaitMs?: 10000 },
458
- debug?: false,
210
+ ```js
211
+ new AdManager({
212
+ apiBaseUrl: '...',
213
+ apiKey: '...',
214
+ cardOption: {
215
+ variant: 'vertical',
216
+ count: 2,
459
217
  },
460
- baseUrl?: string // 默认 '/api/v1'
461
- );
462
-
463
- tracker.startTracking(adId, element, sessionId, requestId?, viewToken?);
464
- tracker.getMetrics(adId); // ViewabilityMetrics
465
- tracker.stopTracking(adId);
466
- tracker.stopAll();
467
- ```
468
-
469
- ---
470
-
471
- ### ClientInfo
472
-
473
- 零配置,自动采集 OpenRTB 2.5/2.6 规范的客户端信息。
474
-
475
- ```typescript
476
- import {
477
- getClientInfo, getDeviceInfo, getUserInfo,
478
- getAppInfo, getGeoInfo, getUserId,
479
- clearClientInfoCache, getClientInfoJSON, getClientInfoSummary,
480
- } from '@action-x/ad-sdk';
481
-
482
- const info = getClientInfo();
483
- // info.device.os → 'macOS'
484
- // info.device.model → 'iPhone14,2'
485
- // info.user.id → 'uuid-xxx'(localStorage 持久化)
486
- // info.geo.country → 'CN'(从时区/语言推断)
487
- // info.app.bundle → 'example.com'
488
-
489
- // 覆盖默认值
490
- const info = getClientInfo({
491
- device: { ifa: 'your-idfa', lmt: 0 },
492
- app: { bundle: 'com.your.app', ver: '2.1.0' },
493
- geo: { lat: 39.9, lon: 116.4 },
494
218
  });
495
219
  ```
496
220
 
497
- ---
221
+ ### `requestAds(context)`
498
222
 
499
- ### EventEmitter
223
+ | Parameter | Type | Required | Description |
224
+ |------|------|------|------|
225
+ | `query` | `string` | Yes | User question |
226
+ | `response` | `string` | Yes | AI response text |
500
227
 
501
- ```typescript
502
- import { EventEmitter } from '@action-x/ad-sdk';
228
+ ### `render(container, options?)`
503
229
 
504
- const emitter = new EventEmitter();
505
- emitter.on('event', handler);
506
- emitter.emit('event', data);
507
- emitter.off('event', handler);
508
- emitter.removeAllListeners('event');
509
- emitter.listenerCount('event');
510
- ```
511
-
512
- ---
513
-
514
- ## 类型定义
515
-
516
- ```typescript
517
- // 广告格式
518
- type AdFormat = 'action_card' | 'suffix' | 'followup' | 'source' | 'static' | 'lead_gen' | 'entity_link';
519
-
520
- // 广告位请求
521
- interface AdSlotRequest {
522
- slotId: string;
523
- slotName: string;
524
- format: AdFormat;
525
- variant?: string;
526
- size: { width: number; height: number; aspectRatio?: string };
527
- count?: number;
528
- preferences?: { maxTitleLength?: number; requireImage?: boolean; showPrice?: boolean };
529
- placement: { position: 'above_fold' | 'below_fold' | 'sidebar' | 'inline'; context: string };
530
- }
531
-
532
- // 广告响应批次
533
- interface AdResponseBatch {
534
- requestId: string;
535
- timestamp: number;
536
- intent: IntentResult;
537
- slots: AdSlotResponse[];
538
- }
539
-
540
- // 广告位响应
541
- interface AdSlotResponse {
542
- slotId: string;
543
- status: 'filled' | 'no_fill' | 'error';
544
- ads: AdaptedAdContent[];
545
- }
546
-
547
- // 适配后的广告内容
548
- interface AdaptedAdContent {
549
- original: { id: string; type: AdFormat; score: number };
550
- adapted: { title: string; body: string; ctaText: string; image?; price?; rating?; brand? };
551
- tracking: { impressionUrl: string; clickUrl: string; viewToken: string };
552
- }
553
-
554
- // Analytics 信息(传给 DOMRenderer)
555
- interface AdAnalyticsInfo {
556
- requestId: string;
557
- slotId: string;
558
- position: number;
559
- totalAds: number;
560
- }
561
- ```
562
-
563
- 完整类型定义参见 `dist/index.d.ts`。
564
-
565
- ---
566
-
567
- ## CSS 样式
568
-
569
- 所有样式使用 `ax-ad-*` 命名空间,不污染全局。
570
-
571
- | CSS 类 | 用途 |
572
- |-------|------|
573
- | `.ax-ad-card` | Action Card 基础容器 |
574
- | `.ax-ad-card-horizontal/vertical/compact` | 布局变体 |
575
- | `.ax-ad-cta` | CTA 按钮(蓝色 #3b82f6) |
576
- | `.ax-ad-suffix` | Suffix 广告容器 |
577
- | `.ax-ad-suffix-link` | Suffix 链接 |
578
- | `.ax-ad-variant-block/inline/minimal` | Suffix 变体 |
579
- | `.ax-ad-source-link` | Source 广告链接 |
580
- | `.ax-ad-variant-card/list-item/minimal` | Source 变体 |
581
- | `.ax-ad-leadgen-cta` | LeadGen CTA |
582
- | `.ax-ad-sponsored-badge` | 赞助商标识 |
583
- | `.ax-ads-container` | 多广告外层容器 |
584
-
585
- **自定义样式:**
586
-
587
- ```css
588
- /* 覆盖 CTA 颜色 */
589
- .ax-ad-cta { background: #7c3aed; }
590
- .ax-ad-cta:hover { background: #6d28d9; }
591
-
592
- /* 覆盖卡片圆角 */
593
- .ax-ad-card { border-radius: 12px; }
594
- ```
595
-
596
- ---
597
-
598
- ## 广告格式说明
599
-
600
- | 格式 | 说明 | 典型位置 |
601
- |------|------|---------|
602
- | `action_card` | 产品卡片,含图片/价格/评分/CTA | AI 回复下方 |
603
- | `suffix` | 低干扰追加文本,append 在回复末尾 | AI 回复末尾 |
604
- | `source` | 混入引用来源列表的赞助链接 | 引用/来源区域 |
605
- | `lead_gen` | 线索收集,引导留资 / 注册 | 侧边栏 / 对话末尾 |
606
- | `followup` | 混入相关问题建议 | 问题推荐区域 |
607
- | `static` | 静态 Banner 展示 | 固定位置 |
230
+ | Parameter | Type | Description |
231
+ |------|------|------|
232
+ | `container` | `HTMLElement` | Mount container |
233
+ | `options.onClick` | `(ad) => void` | Click callback |
234
+ | `options.onImpression` | `(ad) => void` | Impression callback |
608
235
 
609
236
  ---
610
237
 
611
- ## 高级用法
612
-
613
- ### Vue 3
614
-
615
- ```typescript
616
- // composables/useAds.ts
617
- import { ref, onUnmounted } from 'vue';
618
- import { AdManager } from '@action-x/ad-sdk';
619
-
620
- export function useAds(slots) {
621
- const isLoading = ref(false);
622
- const manager = new AdManager({
623
- apiBaseUrl: import.meta.env.VITE_AD_API_URL,
624
- apiKey: import.meta.env.VITE_AD_API_KEY,
625
- slots,
626
- });
627
- manager.on('adsLoading', () => (isLoading.value = true));
628
- manager.on('adsUpdated', () => (isLoading.value = false));
629
- onUnmounted(() => manager.destroy());
630
- return { manager, isLoading };
631
- }
632
- ```
633
-
634
- ### React
635
-
636
- ```typescript
637
- // hooks/useAdManager.ts
638
- import { useEffect, useRef } from 'react';
639
- import { AdManager } from '@action-x/ad-sdk';
238
+ ## Event Listeners
640
239
 
641
- export function useAdManager(slots) {
642
- const ref = useRef(new AdManager({
643
- apiBaseUrl: process.env.NEXT_PUBLIC_AD_API_URL,
644
- apiKey: process.env.NEXT_PUBLIC_AD_API_KEY,
645
- slots,
646
- }));
647
- useEffect(() => () => ref.current.destroy(), []);
648
- return ref.current;
649
- }
650
- ```
651
-
652
- ### CDN / 无构建工具
653
-
654
- ```html
655
- <link rel="stylesheet" href="https://unpkg.com/@action-x/ad-sdk/dist/style.css" />
656
- <script src="https://unpkg.com/@action-x/ad-sdk/dist/index.umd.js"></script>
657
- <script>
658
- const { AdManager } = ActionXAdSDK;
659
- const manager = new AdManager({ apiBaseUrl: '...', apiKey: '...', slots: [...] });
660
- </script>
240
+ ```js
241
+ manager.on('adsUpdated', (slots) => { /* ad data ready */ });
242
+ manager.on('adsLoading', () => { /* requesting */ });
243
+ manager.on('adsError', (err) => { /* request failed */ });
244
+ manager.on('adClicked', (adId, slotId) => { });
245
+ manager.on('adImpression', (adId, slotId) => { });
661
246
  ```
662
247
 
663
248
  ---
664
249
 
665
- ## 依赖 API 端点
666
-
667
- | 端点 | 方法 | 用途 |
668
- |------|------|------|
669
- | `/api/v1/ads/request` | POST | 请求广告批次 |
670
- | `/api/v1/ads/impression` | POST | 曝光事件上报 |
671
- | `/api/v1/ads/click` | POST | 点击事件上报 |
672
- | `/api/v1/ads/viewability/batch` | POST | 可视性事件批量上报 |
673
-
674
- 所有请求若配置了 `apiKey`,均自动携带 `X-API-Key` 请求头。
675
-
676
- ---
677
-
678
- **版本**:`0.1.0` | **许可证**:MIT
250
+ **Version**: `0.1.0` | **License**: MIT