ttp-agent-sdk 2.38.4 β†’ 2.40.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.
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>TTP E-Commerce Widget - Test</title>
6
+ <title>TTP Hotels Flavor - Test</title>
7
7
  <style>
8
8
  body {
9
9
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -43,7 +43,7 @@
43
43
  .status.error { background: #FEE2E2; color: #991B1B; }
44
44
 
45
45
  button {
46
- background: #4F46E5;
46
+ background: #2563EB;
47
47
  color: white;
48
48
  border: none;
49
49
  padding: 12px 24px;
@@ -52,13 +52,11 @@
52
52
  font-size: 16px;
53
53
  margin: 10px 10px 10px 0;
54
54
  }
55
- button:hover { background: #4338CA; }
55
+ button:hover { background: #1D4ED8; }
56
56
  button.secondary { background: #6B7280; }
57
57
  button.secondary:hover { background: #4B5563; }
58
58
  button.success { background: #059669; }
59
59
  button.success:hover { background: #047857; }
60
- button.warning { background: #D97706; }
61
- button.warning:hover { background: #B45309; }
62
60
 
63
61
  label {
64
62
  display: flex;
@@ -68,7 +66,7 @@
68
66
  color: #374151;
69
67
  }
70
68
 
71
- select, input[type="text"], input[type="number"] {
69
+ select, input[type="text"] {
72
70
  padding: 8px 12px;
73
71
  border: 1px solid #D1D5DB;
74
72
  border-radius: 6px;
@@ -110,21 +108,6 @@
110
108
  color: #111827;
111
109
  }
112
110
 
113
- .cart-display {
114
- background: #F0FDF4;
115
- border: 1px solid #BBF7D0;
116
- border-radius: 8px;
117
- padding: 16px;
118
- margin: 16px 0;
119
- font-family: monospace;
120
- font-size: 13px;
121
- }
122
-
123
- .cart-display pre {
124
- margin: 0;
125
- white-space: pre-wrap;
126
- }
127
-
128
111
  @media (max-width: 768px) {
129
112
  body { margin: 0; padding: 10px; }
130
113
  .container { padding: 16px; border-radius: 8px; }
@@ -136,16 +119,16 @@
136
119
  </head>
137
120
  <body>
138
121
  <div class="container">
139
- <h1>πŸ›’ TTP E-Commerce Widget Test</h1>
122
+ <h1>TTP Hotels Flavor Test</h1>
140
123
 
141
124
  <div class="info">
142
- <strong>What is TTPEcommerceWidget?</strong>
125
+ <strong>How it works:</strong>
143
126
  <ul>
144
- <li>Wraps the standard TTPChatWidget with an e-commerce layer</li>
145
- <li>Displays product cards when the agent sends <code>show_products</code> messages</li>
146
- <li>Manages a local cart with toast notifications and cart summary</li>
147
- <li>Supports partner integration via <code>addToCartFn</code> / <code>getCartFn</code> callbacks</li>
148
- <li>Test API available at <code>window.__TTP_ECOMMERCE__</code></li>
127
+ <li>Uses the standard <code>TTPChatWidget</code> with <code>flavor: { type: 'hotels', partnerId: 'mock-hotel' }</code></li>
128
+ <li>The backend injects hotel tools (<code>search_rooms</code>, <code>select_room</code>, <code>add_extra</code>, <code>get_booking</code>)</li>
129
+ <li>Room cards display when the agent sends <code>show_items</code> messages</li>
130
+ <li>Booking summary updates on <code>booking_updated</code> messages</li>
131
+ <li>Gallery (<code>show_media</code>) opens fullscreen with widget minimized</li>
149
132
  </ul>
150
133
  </div>
151
134
 
@@ -155,9 +138,11 @@
155
138
  <div class="settings-grid">
156
139
  <label>Agent
157
140
  <select id="agentSelect" onchange="handleAgentChange()">
158
- <option value="agent_e165840eb|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|en|ltr">e-commerce_english (agent_e165840eb)</option>
159
- <option value="agent_ed18369b3|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|he|rtl">e-commerce_hebrew (agent_ed18369b3)</option>
160
- <option value="agent_d119003d8|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|es|ltr">ecommerce_spanish (agent_d119003d8)</option>
141
+ <option value="agent_ed18369b3|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|ecommerce|mock-store">Ecommerce Demo (agent_ed18369b3)</option>
142
+ <option value="agent_d0774f1af|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|hotels|mock-hotel" selected>Hotels Demo (agent_d0774f1af)</option>
143
+ <option value="agent_PHARMA_ID|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|pharma|mock-pharm">Pharma Demo (update agent ID)</option>
144
+ <option value="agent_RESTAURANT_ID|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|restaurants|mock-restaurant">Restaurant Demo (update agent ID)</option>
145
+ <option value="agent_TOUR_ID|app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo|tours|mock-tour">Tour Demo (update agent ID)</option>
161
146
  <option value="custom">Custom...</option>
162
147
  </select>
163
148
  </label>
@@ -179,17 +164,19 @@
179
164
  <option value="rtl">RTL</option>
180
165
  </select>
181
166
  </label>
182
- <label>Widget Mode
183
- <select id="modeSelect">
184
- <option value="unified" selected>Unified (Both)</option>
185
- <option value="voice-only">Voice Only</option>
186
- <option value="text-only">Text Only</option>
167
+ <label>STT Provider
168
+ <select id="sttSelect">
169
+ <option value="">Default (server)</option>
170
+ <option value="azure-stt" selected>Azure STT</option>
171
+ <option value="cartesia-stt">Cartesia (Ink-Whisper)</option>
172
+ <option value="openai-realtime">OpenAI Realtime</option>
173
+ <option value="soniox">Soniox</option>
187
174
  </select>
188
175
  </label>
189
176
  <label>Start Open
190
177
  <select id="startOpenSelect">
191
- <option value="true" selected>Yes</option>
192
- <option value="false">No</option>
178
+ <option value="true">Yes</option>
179
+ <option value="false" selected>No</option>
193
180
  </select>
194
181
  </label>
195
182
  </div>
@@ -199,66 +186,54 @@
199
186
  <button id="destroyBtn" class="secondary">Destroy Widget</button>
200
187
  </div>
201
188
 
202
- <h2>E-Commerce Test Actions</h2>
189
+ <h2>Test Actions (Simulated Messages)</h2>
203
190
 
204
191
  <div class="test-section">
205
- <h3>Show Products (simulated)</h3>
192
+ <h3>Show Rooms</h3>
206
193
  <p style="color: #6B7280; font-size: 14px; margin-bottom: 12px;">
207
- Injects a <code>show_products</code> message into the ecommerce layer β€” no voice call needed.
194
+ Injects a <code>show_items</code> message with mock hotel rooms β€” no voice call needed.
208
195
  </p>
209
- <div class="settings-grid">
210
- <label>Search Query
211
- <input id="productQuery" type="text" value="milk" />
212
- </label>
213
- <label>Layout
214
- <select id="productLayout">
215
- <option value="auto">Auto (cards &le;4, list 5+)</option>
216
- <option value="cards">Cards</option>
217
- <option value="list">List</option>
218
- </select>
219
- </label>
220
- </div>
221
- <button id="showProductsBtn" class="success">Show Products</button>
196
+ <button id="showRoomsBtn" class="success">Show Rooms</button>
222
197
  </div>
223
198
 
224
199
  <div class="test-section">
225
- <h3>Cart Updated (simulated)</h3>
200
+ <h3>Show Gallery</h3>
226
201
  <p style="color: #6B7280; font-size: 14px; margin-bottom: 12px;">
227
- Injects a <code>cart_updated</code> message to test toast and summary UI.
202
+ Injects a <code>show_media</code> message to test fullscreen gallery.
228
203
  </p>
229
- <button id="cartAddBtn" class="success">Simulate: Product Added</button>
230
- <button id="cartClearBtn" class="secondary">Clear Cart</button>
204
+ <button id="showGalleryBtn" class="success">Show Gallery</button>
231
205
  </div>
232
206
 
233
207
  <div class="test-section">
234
- <h3>Cart State</h3>
235
- <div id="cartDisplay" class="cart-display">
236
- <pre>No widget created yet</pre>
237
- </div>
238
- <button id="refreshCartBtn" class="secondary">Refresh Cart State</button>
208
+ <h3>Booking Updated</h3>
209
+ <p style="color: #6B7280; font-size: 14px; margin-bottom: 12px;">
210
+ Injects a <code>booking_updated</code> message to test the booking summary bar.
211
+ </p>
212
+ <button id="bookingBtn" class="success">Simulate Booking</button>
213
+ <button id="clearBookingBtn" class="secondary">Clear Booking</button>
239
214
  </div>
240
215
 
241
216
  <h2>Console Commands</h2>
242
- <div class="code-block">// Show products via test API (fetches from mock API)
243
- window.__TTP_ECOMMERCE__.showProducts('milk');
244
- window.__TTP_ECOMMERCE__.showProducts('bread', 3);
245
-
246
- // Inject raw messages
247
- window.__TTP_ECOMMERCE__.handleMessage({
248
- t: 'show_products',
249
- products: [
250
- { id: '1', name: 'Test Product', price: 9.99, image: '' }
217
+ <div class="code-block">// Access the widget instance
218
+ window.testWidget
219
+
220
+ // Inject messages manually via the flavor's messageHandlers
221
+ window.testWidget._flavor.messageHandlers['show_items']({
222
+ t: 'show_items',
223
+ items: [
224
+ { roomId: 'room-1', roomName: 'Test Room', imageUrl: '...', pricePerNight: 150, currency: 'USD', beds: 2, amenities: ['WiFi', 'Pool'] }
251
225
  ],
252
- title: 'Test Results'
226
+ title: 'Test Rooms'
253
227
  });
254
228
 
255
- // Cart operations
256
- window.__TTP_ECOMMERCE__.getCart();
257
- window.__TTP_ECOMMERCE__.clearCart();</div>
229
+ window.testWidget._flavor.messageHandlers['show_media']({
230
+ t: 'show_media',
231
+ images: [{ url: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=800', caption: 'Standard Room' }],
232
+ title: 'Hotel Photos'
233
+ });</div>
258
234
  </div>
259
235
 
260
236
  <script>
261
- // Load SDK - use fetch with ngrok-skip-browser-warning when on ngrok (fixes iPhone/mobile)
262
237
  (function() {
263
238
  var isNgrok = window.location.hostname.includes('ngrok');
264
239
  if (isNgrok) {
@@ -278,7 +253,7 @@ window.__TTP_ECOMMERCE__.clearCart();</div>
278
253
  </script>
279
254
 
280
255
  <script>
281
- let ecomWidget = null;
256
+ let chatWidget = null;
282
257
 
283
258
  function updateStatus(msg, type = '') {
284
259
  const el = document.getElementById('status');
@@ -286,27 +261,11 @@ window.__TTP_ECOMMERCE__.clearCart();</div>
286
261
  el.className = 'status ' + type;
287
262
  }
288
263
 
289
- function refreshCartDisplay() {
290
- const el = document.getElementById('cartDisplay');
291
- if (!window.__TTP_ECOMMERCE__) {
292
- el.innerHTML = '<pre>No widget created yet</pre>';
293
- return;
294
- }
295
- const cart = window.__TTP_ECOMMERCE__.getCart();
296
- el.innerHTML = '<pre>' + JSON.stringify(cart, null, 2) + '</pre>';
297
- }
298
-
299
264
  function handleAgentChange() {
300
265
  const sel = document.getElementById('agentSelect');
301
266
  const isCustom = sel.value === 'custom';
302
267
  document.getElementById('customAgentLabel').style.display = isCustom ? '' : 'none';
303
268
  document.getElementById('customAppLabel').style.display = isCustom ? '' : 'none';
304
-
305
- if (!isCustom) {
306
- const [, , lang, dir] = sel.value.split('|');
307
- document.getElementById('languageSelect').value = lang;
308
- document.getElementById('dirSelect').value = dir;
309
- }
310
269
  }
311
270
 
312
271
  function getAgentConfig() {
@@ -314,132 +273,156 @@ window.__TTP_ECOMMERCE__.clearCart();</div>
314
273
  if (sel.value === 'custom') {
315
274
  return {
316
275
  agentId: document.getElementById('customAgentId').value,
317
- appId: document.getElementById('customAppId').value
276
+ appId: document.getElementById('customAppId').value,
277
+ flavorType: 'ecommerce',
278
+ partnerId: 'mock-store'
318
279
  };
319
280
  }
320
- const [agentId, appId] = sel.value.split('|');
321
- return { agentId, appId };
281
+ const [agentId, appId, flavorType, partnerId] = sel.value.split('|');
282
+ return { agentId, appId, flavorType, partnerId };
322
283
  }
323
284
 
324
285
  function createWidget() {
325
- if (ecomWidget) {
326
- ecomWidget.destroy();
327
- ecomWidget = null;
286
+ if (chatWidget) {
287
+ chatWidget.destroy();
288
+ chatWidget = null;
328
289
  }
329
290
 
330
291
  const TTPAgentSDK = window.TTPAgentSDK;
331
- if (!TTPAgentSDK || !TTPAgentSDK.TTPEcommerceWidget) {
332
- updateStatus('TTPEcommerceWidget not available in SDK', 'error');
292
+ if (!TTPAgentSDK || !TTPAgentSDK.TTPChatWidget) {
293
+ updateStatus('TTPChatWidget not available in SDK', 'error');
333
294
  return;
334
295
  }
335
296
 
336
297
  const agent = getAgentConfig();
337
298
 
338
- ecomWidget = new TTPAgentSDK.TTPEcommerceWidget({
299
+ chatWidget = new TTPAgentSDK.TTPChatWidget({
339
300
  agentId: agent.agentId,
340
301
  appId: agent.appId,
341
302
  language: document.getElementById('languageSelect').value,
342
303
  direction: document.getElementById('dirSelect').value,
304
+ agentSettingsOverride: (() => {
305
+ const override = {};
306
+ const stt = document.getElementById('sttSelect').value;
307
+ if (stt) override.sttProvider = stt;
308
+ return override;
309
+ })(),
343
310
  behavior: {
344
- mode: document.getElementById('modeSelect').value,
345
311
  startOpen: document.getElementById('startOpenSelect').value === 'true'
346
312
  },
313
+ flavor: {
314
+ type: agent.flavorType,
315
+ partnerId: agent.partnerId
316
+ },
347
317
  icon: {
348
318
  type: 'custom',
349
319
  customImage: 'https://talktopc.com/logo192.png',
350
320
  size: 'medium',
351
321
  backgroundColor: '#FFFFFF'
352
322
  },
353
- partner: { name: 'TestStore' },
354
- cart: {
355
- addToCartFn: async (productId, qty) => {
356
- console.log('[TestPage] addToCartFn called:', productId, qty);
357
- return { ok: true };
358
- },
359
- getCartFn: async () => {
360
- console.log('[TestPage] getCartFn called');
361
- return window.__TTP_ECOMMERCE__?.getCart() || { items: [], total: 0, count: 0 };
362
- }
323
+ whatsapp: {
324
+ number: '+972508665824',
325
+ text: 'Χ”Χ™Χ™, ΧΧ©ΧžΧ— אם ΧͺΧ•Χ›ΧœΧ™ ΧœΧ’Χ–Χ•Χ¨ ΧœΧ™ '
363
326
  }
364
327
  });
365
328
 
366
- window.testWidget = ecomWidget;
367
- updateStatus('TTPEcommerceWidget created successfully', 'success');
368
- refreshCartDisplay();
329
+ window.testWidget = chatWidget;
330
+ const sttLabel = document.getElementById('sttSelect').selectedOptions[0].text;
331
+ updateStatus('TTPChatWidget created β€” ' + agent.flavorType + ' flavor (' + agent.partnerId + '), STT: ' + sttLabel, 'success');
369
332
  }
370
333
 
371
334
  // Button handlers
372
335
  document.getElementById('createBtn').onclick = createWidget;
373
336
 
374
337
  document.getElementById('destroyBtn').onclick = () => {
375
- if (ecomWidget) {
376
- ecomWidget.destroy();
377
- ecomWidget = null;
338
+ if (chatWidget) {
339
+ chatWidget.destroy();
340
+ chatWidget = null;
378
341
  updateStatus('Widget destroyed', '');
379
- refreshCartDisplay();
380
342
  }
381
343
  };
382
344
 
383
- document.getElementById('showProductsBtn').onclick = async () => {
384
- if (!window.__TTP_ECOMMERCE__) {
345
+ // Show Rooms β€” inject mock show_items
346
+ document.getElementById('showRoomsBtn').onclick = () => {
347
+ if (!chatWidget || !chatWidget._flavor) {
385
348
  updateStatus('Create widget first', 'error');
386
349
  return;
387
350
  }
388
- const query = document.getElementById('productQuery').value || 'milk';
389
- try {
390
- await window.__TTP_ECOMMERCE__.showProducts(query);
391
- updateStatus('showProducts("' + query + '") sent', 'success');
392
- } catch (e) {
393
- updateStatus('showProducts failed: ' + e.message, 'error');
394
- }
351
+ chatWidget._flavor.messageHandlers['show_items']({
352
+ t: 'show_items',
353
+ items: [
354
+ { roomId: 'room-standard', roomName: 'Standard Room', imageUrl: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=400&h=300&fit=crop', pricePerNight: 120, currency: 'USD', beds: 1, amenities: ['WiFi', 'TV', 'Air Conditioning'], available: true },
355
+ { roomId: 'room-deluxe', roomName: 'Deluxe Room', imageUrl: 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=400&h=300&fit=crop', pricePerNight: 200, currency: 'USD', beds: 2, amenities: ['WiFi', 'TV', 'Pool', 'Mini Bar'], available: true },
356
+ { roomId: 'room-suite', roomName: 'Executive Suite', imageUrl: 'https://images.unsplash.com/photo-1566665797739-1674de7a421a?w=400&h=300&fit=crop', pricePerNight: 350, currency: 'USD', beds: 2, amenities: ['WiFi', 'TV', 'Pool', 'Spa', 'Balcony'], available: true },
357
+ { roomId: 'room-penthouse', roomName: 'Penthouse Suite', imageUrl: 'https://images.unsplash.com/photo-1578683010236-d716f9a3f461?w=400&h=300&fit=crop', pricePerNight: 600, currency: 'USD', beds: 3, amenities: ['WiFi', 'TV', 'Pool', 'Spa', 'Balcony', 'Butler'], available: true }
358
+ ],
359
+ title: '4 rooms available'
360
+ });
361
+ updateStatus('show_items injected with 4 rooms', 'success');
395
362
  };
396
363
 
397
- document.getElementById('cartAddBtn').onclick = async () => {
398
- if (!window.__TTP_ECOMMERCE__) {
364
+ // Show Gallery β€” inject mock show_media
365
+ document.getElementById('showGalleryBtn').onclick = () => {
366
+ if (!chatWidget || !chatWidget._flavor) {
399
367
  updateStatus('Create widget first', 'error');
400
368
  return;
401
369
  }
402
- try {
403
- const baseUrl = window.__TTP_MOCK_API__ || 'https://backend.talktopc.com';
404
- const response = await fetch(`${baseUrl}/api/partner/mock-store/products/search?q=milk&limit=1&sessionId=test`);
405
- const data = await response.json();
406
- const product = data.products[0];
407
- window.__TTP_ECOMMERCE__.handleMessage({
408
- t: 'cart_updated',
409
- product: product,
410
- action: 'added',
411
- cartTotal: product.price,
412
- cartItemCount: 1
413
- });
414
- updateStatus('Simulated cart_updated for "' + product.name + '"', 'success');
415
- setTimeout(refreshCartDisplay, 200);
416
- } catch (e) {
417
- updateStatus('Failed to fetch product: ' + e.message, 'error');
418
- }
370
+ chatWidget._flavor.messageHandlers['show_media']({
371
+ t: 'show_media',
372
+ images: [
373
+ { url: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=800', caption: 'Standard Room' },
374
+ { url: 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=800', caption: 'Deluxe Room' },
375
+ { url: 'https://images.unsplash.com/photo-1566665797739-1674de7a421a?w=800', caption: 'Executive Suite' },
376
+ { url: 'https://images.unsplash.com/photo-1578683010236-d716f9a3f461?w=800', caption: 'Penthouse Suite' }
377
+ ],
378
+ title: 'Hotel Rooms'
379
+ });
380
+ updateStatus('show_media injected with 4 images (fullscreen gallery)', 'success');
419
381
  };
420
382
 
421
- document.getElementById('cartClearBtn').onclick = () => {
422
- if (!window.__TTP_ECOMMERCE__) {
383
+ // Booking Updated β€” inject mock booking_updated
384
+ document.getElementById('bookingBtn').onclick = () => {
385
+ if (!chatWidget || !chatWidget._flavor) {
423
386
  updateStatus('Create widget first', 'error');
424
387
  return;
425
388
  }
426
- window.__TTP_ECOMMERCE__.clearCart();
427
- updateStatus('Cart cleared', '');
428
- refreshCartDisplay();
389
+ chatWidget._flavor.messageHandlers['booking_updated']({
390
+ t: 'booking_updated',
391
+ booking: {
392
+ roomId: 'room-deluxe',
393
+ roomName: 'Deluxe Room',
394
+ nights: 3,
395
+ guests: 2,
396
+ pricePerNight: 200,
397
+ totalPrice: 650,
398
+ currency: 'USD',
399
+ extras: [{ extraId: 'breakfast', name: 'Breakfast Buffet', price: 25, quantity: 1 }]
400
+ }
401
+ });
402
+ updateStatus('booking_updated injected (Deluxe Room, 3 nights, breakfast)', 'success');
429
403
  };
430
404
 
431
- document.getElementById('refreshCartBtn').onclick = refreshCartDisplay;
405
+ document.getElementById('clearBookingBtn').onclick = () => {
406
+ if (!chatWidget || !chatWidget._flavor) {
407
+ updateStatus('Create widget first', 'error');
408
+ return;
409
+ }
410
+ chatWidget._flavor.messageHandlers['booking_updated']({
411
+ t: 'booking_updated',
412
+ booking: {}
413
+ });
414
+ updateStatus('Booking cleared', '');
415
+ };
432
416
 
433
- // Wait for SDK and auto-create widget
417
+ // Wait for SDK and auto-create
434
418
  function waitForSDK() {
435
419
  return new Promise((resolve, reject) => {
436
- if (window.TTPAgentSDK?.TTPEcommerceWidget) { resolve(); return; }
420
+ if (window.TTPAgentSDK?.TTPChatWidget) { resolve(); return; }
437
421
  let attempts = 0;
438
- const maxAttempts = 150; // 15s - allows for slow mobile/ngrok
439
422
  const iv = setInterval(() => {
440
423
  attempts++;
441
- if (window.TTPAgentSDK?.TTPEcommerceWidget) { clearInterval(iv); resolve(); }
442
- else if (attempts >= maxAttempts) { clearInterval(iv); reject(new Error('SDK failed to load')); }
424
+ if (window.TTPAgentSDK?.TTPChatWidget) { clearInterval(iv); resolve(); }
425
+ else if (attempts >= 150) { clearInterval(iv); reject(new Error('SDK failed to load')); }
443
426
  }, 100);
444
427
  });
445
428
  }
@@ -316,6 +316,51 @@
316
316
  <span class="badge badge--new">E-Commerce</span>
317
317
  </div>
318
318
  </a>
319
+
320
+ <a href="../examples/test-pharma.html" class="test-card">
321
+ <span class="test-card__icon">πŸ’Š</span>
322
+ <h3 class="test-card__title">Pharma Widget Test</h3>
323
+ <p class="test-card__description">
324
+ Test the pharmacy flavor β€” medication search, prescription management,
325
+ and medication cards with Rx/OTC badges. Simulate show_items and
326
+ prescription_updated messages without a live voice call.
327
+ </p>
328
+ <div class="test-card__badges">
329
+ <span class="badge badge--widget">Widget</span>
330
+ <span class="badge badge--voice">Voice</span>
331
+ <span class="badge badge--new">Pharma</span>
332
+ </div>
333
+ </a>
334
+
335
+ <a href="../examples/test-restaurant.html" class="test-card">
336
+ <span class="test-card__icon">🍽️</span>
337
+ <h3 class="test-card__title">Restaurant Widget Test</h3>
338
+ <p class="test-card__description">
339
+ Test the restaurant flavor β€” menu search, order management, menu item cards
340
+ with allergen/dietary tags, and photo gallery. Simulate show_items,
341
+ order_updated, and show_media messages without a live voice call.
342
+ </p>
343
+ <div class="test-card__badges">
344
+ <span class="badge badge--widget">Widget</span>
345
+ <span class="badge badge--voice">Voice</span>
346
+ <span class="badge badge--new">Restaurant</span>
347
+ </div>
348
+ </a>
349
+
350
+ <a href="../examples/test-tour.html" class="test-card">
351
+ <span class="test-card__icon">πŸ—ΊοΈ</span>
352
+ <h3 class="test-card__title">Tour Widget Test</h3>
353
+ <p class="test-card__description">
354
+ Test the tours flavor β€” tour search, booking management, tour cards
355
+ with activity tags, and photo gallery. Simulate show_items,
356
+ cart_updated, and show_media messages without a live voice call.
357
+ </p>
358
+ <div class="test-card__badges">
359
+ <span class="badge badge--widget">Widget</span>
360
+ <span class="badge badge--voice">Voice</span>
361
+ <span class="badge badge--new">Tours</span>
362
+ </div>
363
+ </a>
319
364
  </div>
320
365
  </div>
321
366