acinguiux-preact-components 0.0.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.
Files changed (313) hide show
  1. package/package.json +56 -0
  2. package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
  3. package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
  4. package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
  5. package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
  6. package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
  7. package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
  8. package/src/content/themes/theme-allon/theme-allon.css +25 -0
  9. package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
  10. package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
  11. package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
  12. package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
  13. package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
  14. package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
  15. package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
  16. package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
  17. package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
  18. package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
  19. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
  20. package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
  21. package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
  22. package/src/content/themes/theme-base/theme-base.css +49 -0
  23. package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
  24. package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
  25. package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
  26. package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
  27. package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
  28. package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
  29. package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
  30. package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
  31. package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
  32. package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
  33. package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
  34. package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
  35. package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
  36. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
  37. package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
  38. package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
  39. package/src/content/themes/theme-base2/theme-base2.css +47 -0
  40. package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
  41. package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
  42. package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
  43. package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
  44. package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
  45. package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
  46. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
  47. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
  48. package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
  49. package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
  50. package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
  51. package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
  52. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
  53. package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
  54. package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
  55. package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
  56. package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
  57. package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
  58. package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
  59. package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
  60. package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
  61. package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
  62. package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
  63. package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
  64. package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
  65. package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
  66. package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
  67. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
  68. package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
  69. package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
  70. package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
  71. package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
  72. package/src/main/atoms/audio.js +16 -0
  73. package/src/main/atoms/box.js +5 -0
  74. package/src/main/atoms/button.js +40 -0
  75. package/src/main/atoms/card.js +22 -0
  76. package/src/main/atoms/form.js +30 -0
  77. package/src/main/atoms/heading.js +17 -0
  78. package/src/main/atoms/icon.js +24 -0
  79. package/src/main/atoms/img.js +131 -0
  80. package/src/main/atoms/input.js +55 -0
  81. package/src/main/atoms/link-text.js +21 -0
  82. package/src/main/atoms/link.js +60 -0
  83. package/src/main/atoms/list.js +12 -0
  84. package/src/main/atoms/logo.js +9 -0
  85. package/src/main/atoms/menu.js +10 -0
  86. package/src/main/atoms/message.js +5 -0
  87. package/src/main/atoms/nav-link.js +49 -0
  88. package/src/main/atoms/popup.js +47 -0
  89. package/src/main/atoms/rich-text.js +128 -0
  90. package/src/main/atoms/scroller.js +224 -0
  91. package/src/main/atoms/svg.js +65 -0
  92. package/src/main/atoms/table.js +32 -0
  93. package/src/main/atoms/textarea.js +10 -0
  94. package/src/main/atoms/time.js +12 -0
  95. package/src/main/atoms/video.js +100 -0
  96. package/src/main/export-main.js +12 -0
  97. package/src/main/export-matter.js +86 -0
  98. package/src/main/export-preact-hooks.js +1 -0
  99. package/src/main/export-preact.js +1 -0
  100. package/src/main/index.js +13 -0
  101. package/src/main/molecules/asset.js +23 -0
  102. package/src/main/molecules/glossary.js +44 -0
  103. package/src/main/molecules/links.js +23 -0
  104. package/src/main/molecules/promo-text.js +27 -0
  105. package/src/main/molecules/tags.js +15 -0
  106. package/src/main/molecules/tree.js +51 -0
  107. package/src/main/organisms/accordion-item.js +106 -0
  108. package/src/main/organisms/author.js +29 -0
  109. package/src/main/organisms/breadcrumb.js +69 -0
  110. package/src/main/organisms/call-to-action.js +24 -0
  111. package/src/main/organisms/carousel.js +178 -0
  112. package/src/main/organisms/cart-item.js +156 -0
  113. package/src/main/organisms/cart.js +162 -0
  114. package/src/main/organisms/contact-form.js +141 -0
  115. package/src/main/organisms/container/ab-test.js +47 -0
  116. package/src/main/organisms/container/default.js +6 -0
  117. package/src/main/organisms/container/filtered-section.js +293 -0
  118. package/src/main/organisms/container/footer.js +12 -0
  119. package/src/main/organisms/container/grid.js +44 -0
  120. package/src/main/organisms/container/header.js +13 -0
  121. package/src/main/organisms/container/list.js +7 -0
  122. package/src/main/organisms/container/main.js +6 -0
  123. package/src/main/organisms/container/raw.js +7 -0
  124. package/src/main/organisms/container/section.js +28 -0
  125. package/src/main/organisms/container.js +29 -0
  126. package/src/main/organisms/content-owner.js +15 -0
  127. package/src/main/organisms/date-entry.js +56 -0
  128. package/src/main/organisms/external-search.js +73 -0
  129. package/src/main/organisms/filtered-item.js +163 -0
  130. package/src/main/organisms/footer-item.js +17 -0
  131. package/src/main/organisms/image-gallery.js +164 -0
  132. package/src/main/organisms/last-modified.js +20 -0
  133. package/src/main/organisms/legal-footer.js +16 -0
  134. package/src/main/organisms/list-item.js +48 -0
  135. package/src/main/organisms/metadata.js +11 -0
  136. package/src/main/organisms/navigation.js +232 -0
  137. package/src/main/organisms/notification.js +87 -0
  138. package/src/main/organisms/order-tracker.js +203 -0
  139. package/src/main/organisms/page-header-banner.js +26 -0
  140. package/src/main/organisms/page-header.js +33 -0
  141. package/src/main/organisms/page-tags.js +14 -0
  142. package/src/main/organisms/page.js +260 -0
  143. package/src/main/organisms/press-release.js +24 -0
  144. package/src/main/organisms/product-admin.js +204 -0
  145. package/src/main/organisms/promo-banner.js +28 -0
  146. package/src/main/organisms/promo-bottom.js +23 -0
  147. package/src/main/organisms/promo-button.js +8 -0
  148. package/src/main/organisms/promo-card-cover.js +35 -0
  149. package/src/main/organisms/promo-card.js +33 -0
  150. package/src/main/organisms/promo-full.js +20 -0
  151. package/src/main/organisms/promo-image.js +22 -0
  152. package/src/main/organisms/promo-lure.js +22 -0
  153. package/src/main/organisms/promo-product-card.js +187 -0
  154. package/src/main/organisms/promo-product-full.js +293 -0
  155. package/src/main/organisms/promo-simple.js +23 -0
  156. package/src/main/organisms/quote.js +21 -0
  157. package/src/main/organisms/search-form.js +42 -0
  158. package/src/main/organisms/search-nav.js +66 -0
  159. package/src/main/organisms/search-result.js +53 -0
  160. package/src/main/organisms/slider.js +26 -0
  161. package/src/main/organisms/standalone-asset.js +22 -0
  162. package/src/main/organisms/tabs.js +277 -0
  163. package/src/main/organisms/topbar.js +83 -0
  164. package/src/main/organisms/web-component.js +53 -0
  165. package/src/main/routing/annotation.js +9 -0
  166. package/src/main/routing/component.js +138 -0
  167. package/src/main/routing/empty.js +5 -0
  168. package/src/main/routing/error-handler.js +64 -0
  169. package/src/main/routing/placeholder-image.svg +5 -0
  170. package/src/main/routing/router.js +219 -0
  171. package/src/main/shared/analytics.js +677 -0
  172. package/src/main/shared/bubble-event.js +11 -0
  173. package/src/main/shared/custom-element.js +21 -0
  174. package/src/main/shared/deep-selector.js +28 -0
  175. package/src/main/shared/disable-transparency.js +10 -0
  176. package/src/main/shared/format-time.js +8 -0
  177. package/src/main/shared/get-id.js +5 -0
  178. package/src/main/shared/get-meta.js +3 -0
  179. package/src/main/shared/get-size-class.js +3 -0
  180. package/src/main/shared/get-size.js +11 -0
  181. package/src/main/shared/h.js +88 -0
  182. package/src/main/shared/hash-jump.js +33 -0
  183. package/src/main/shared/icons/arrow-back.svg +1 -0
  184. package/src/main/shared/icons/arrow-down.svg +1 -0
  185. package/src/main/shared/icons/arrow-next.svg +1 -0
  186. package/src/main/shared/icons/arrow-tail-right.svg +1 -0
  187. package/src/main/shared/icons/arrow-tail-up.svg +1 -0
  188. package/src/main/shared/icons/arrow-up.svg +1 -0
  189. package/src/main/shared/icons/asset-download.svg +1 -0
  190. package/src/main/shared/icons/logo.svg +5 -0
  191. package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
  192. package/src/main/shared/icons/media-pause.svg +1 -0
  193. package/src/main/shared/icons/media-play.svg +1 -0
  194. package/src/main/shared/icons/navigation-burger.svg +1 -0
  195. package/src/main/shared/icons/navigation-close.svg +1 -0
  196. package/src/main/shared/icons/navigation-link.svg +1 -0
  197. package/src/main/shared/icons/navigation-refresh.svg +1 -0
  198. package/src/main/shared/icons/navigation-search.svg +1 -0
  199. package/src/main/shared/icons/navigation-share.svg +1 -0
  200. package/src/main/shared/icons/toggle-newwindow.svg +1 -0
  201. package/src/main/shared/icons.js +18 -0
  202. package/src/main/shared/id-from-string.js +5 -0
  203. package/src/main/shared/mark-selection.js +19 -0
  204. package/src/main/shared/register.js +26 -0
  205. package/src/main/shared/renderer.js +43 -0
  206. package/src/main/shared/simple-consent-api.js +70 -0
  207. package/src/main/shared/split-links.js +11 -0
  208. package/src/main/shared/t.js +60 -0
  209. package/src/main/shared/twind.js +837 -0
  210. package/src/main/shared/update-head.js +34 -0
  211. package/src/main/shared/update-scrollbar-width.js +30 -0
  212. package/src/main/shared/use-link.js +151 -0
  213. package/src/main/shared/use-persistent-state.js +42 -0
  214. package/src/main/shared/wait-for-dom-ready.js +6 -0
  215. package/src/main/shared/wcm-mode.js +4 -0
  216. package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
  217. package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
  218. package/src/wcs/components/admin-login/admin-login.js +91 -0
  219. package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
  220. package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
  221. package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
  222. package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
  223. package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
  224. package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
  225. package/src/wcs/components/consent-banner/consent-banner.js +248 -0
  226. package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
  227. package/src/wcs/components/consent-banner/icons/info.svg +1 -0
  228. package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
  229. package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
  230. package/src/wcs/components/decision-tree/badges.js +37 -0
  231. package/src/wcs/components/decision-tree/decision-tree.js +162 -0
  232. package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
  233. package/src/wcs/components/example-accordion/example-accordion.js +10 -0
  234. package/src/wcs/components/example-asset/example-asset.js +12 -0
  235. package/src/wcs/components/example-form/example-form.js +59 -0
  236. package/src/wcs/components/example-nested/example-nested.js +10 -0
  237. package/src/wcs/components/example-routing/example-routing.js +51 -0
  238. package/src/wcs/components/example-rtl/example-rtl.js +28 -0
  239. package/src/wcs/components/example-tabs/example-tabs.js +12 -0
  240. package/src/wcs/components/example-web-component/example-web-component.js +34 -0
  241. package/src/wcs/components/floating-button/floating-button.js +17 -0
  242. package/src/wcs/components/formstack-form/fields/address.js +38 -0
  243. package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
  244. package/src/wcs/components/formstack-form/fields/date.js +22 -0
  245. package/src/wcs/components/formstack-form/fields/description.js +8 -0
  246. package/src/wcs/components/formstack-form/fields/input.js +8 -0
  247. package/src/wcs/components/formstack-form/fields/name.js +39 -0
  248. package/src/wcs/components/formstack-form/fields/radio.js +24 -0
  249. package/src/wcs/components/formstack-form/fields/rating.js +53 -0
  250. package/src/wcs/components/formstack-form/fields/section.js +8 -0
  251. package/src/wcs/components/formstack-form/fields/select.js +10 -0
  252. package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
  253. package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
  254. package/src/wcs/components/formstack-form/formstack-form.js +280 -0
  255. package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
  256. package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
  257. package/src/wcs/components/gauge-value/gauge-value.js +65 -0
  258. package/src/wcs/components/help-centre/api.js +150 -0
  259. package/src/wcs/components/help-centre/help-centre.js +400 -0
  260. package/src/wcs/components/help-centre/icon-search.svg +1 -0
  261. package/src/wcs/components/image-gen/admin-panel.js +248 -0
  262. package/src/wcs/components/image-gen/image-gen.js +385 -0
  263. package/src/wcs/components/image-gen/labels.js +37 -0
  264. package/src/wcs/components/image-gen/use-api.js +392 -0
  265. package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
  266. package/src/wcs/components/launch-container/launch-container.js +95 -0
  267. package/src/wcs/components/launch-container/ledger.js +140 -0
  268. package/src/wcs/components/lng-map/lng-map.js +44 -0
  269. package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
  270. package/src/wcs/components/msds-search/msds-search.js +127 -0
  271. package/src/wcs/components/msds-search/navigation-search.svg +3 -0
  272. package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
  273. package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
  274. package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
  275. package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
  276. package/src/wcs/components/product-links/icon-cart.svg +3 -0
  277. package/src/wcs/components/product-links/product-links.js +43 -0
  278. package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
  279. package/src/wcs/components/salsify-products/filter-tools.js +60 -0
  280. package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
  281. package/src/wcs/components/salsify-products/process-products.js +53 -0
  282. package/src/wcs/components/salsify-products/route-tools.js +54 -0
  283. package/src/wcs/components/salsify-products/salsify-products.js +281 -0
  284. package/src/wcs/components/shout-out/shout-out.js +51 -0
  285. package/src/wcs/components/simple-chart/simple-chart.js +53 -0
  286. package/src/wcs/components/single-stat/single-stat.js +85 -0
  287. package/src/wcs/components/site-feedback/site-feedback.js +56 -0
  288. package/src/wcs/components/skds-search/navigation-search.svg +3 -0
  289. package/src/wcs/components/skds-search/skds-search.js +103 -0
  290. package/src/wcs/components/smart-banner/smart-banner.js +104 -0
  291. package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
  292. package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
  293. package/src/wcs/components/standalone-table/standalone-table.js +440 -0
  294. package/src/wcs/components/station-locator/station-locator.js +49 -0
  295. package/src/wcs/components/store-badges/badges.js +60 -0
  296. package/src/wcs/components/store-badges/store-badges.js +93 -0
  297. package/src/wcs/components/topbar-button/person.svg +1 -0
  298. package/src/wcs/components/topbar-button/topbar-button.js +22 -0
  299. package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
  300. package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
  301. package/src/wcs/shared/chat-bot/README.md +61 -0
  302. package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
  303. package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
  304. package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
  305. package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
  306. package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
  307. package/src/wcs/shared/filtered-data/README.md +52 -0
  308. package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
  309. package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
  310. package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
  311. package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
  312. package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
  313. package/src/wcs/shared/promo-with-popup/promo-with-popup.js +93 -0
@@ -0,0 +1,176 @@
1
+ import ChatBot from '#acinguiux-preact/wcs/shared/chat-bot/chat-bot.js'
2
+ import KoreaiTransport from './koreai-transport.js'
3
+ import { marked } from 'marked'
4
+
5
+ const { h, preactHooks, matter, register } = globalThis.ami
6
+ const { useState } = preactHooks
7
+ const { Button, RichText, Img } = matter
8
+
9
+ const THINKING_MESSAGE = { isBot: true, children: ['...'] }
10
+
11
+ function setupMarkdown () {
12
+ // Remove code parsing.
13
+ const tokenizer = {
14
+ code () {
15
+ return null
16
+ }
17
+ }
18
+
19
+ const renderer = {
20
+ // Open all links in new tab.
21
+ link (args) {
22
+ const processedLink = this.constructor.prototype.link.call(this, args)
23
+ return processedLink.replace('<a ', '<a target="_blank" ')
24
+ },
25
+ // Replace italics with bold.
26
+ em ({ tokens }) {
27
+ const text = this.parser.parseInline(tokens)
28
+ return `<strong>${text}</strong>`
29
+ }
30
+ }
31
+
32
+ marked.use({ tokenizer, renderer })
33
+ }
34
+
35
+ setupMarkdown()
36
+
37
+ function ChatbotKoreai ({
38
+ id,
39
+ url,
40
+ name,
41
+ icon,
42
+ 'button-text': buttonText,
43
+ 'header-text': headerText,
44
+ 'attach-text': attachText,
45
+ 'input-text': inputText,
46
+ 'send-text': sendText
47
+ }) {
48
+ if (!id || !url) {
49
+ console.error('Both id and url are required.')
50
+ return null
51
+ }
52
+
53
+ const [state, setState] = useState({
54
+ transport: new KoreaiTransport(id, name, url)
55
+ })
56
+
57
+ // Translate Kore.ai messages onto chat-bot.js messages.
58
+ const [messages, isBusy] = getMessagesFromHistory(state.transport.history, userMessage => {
59
+ // Additional button handling.
60
+ state.transport.sendMessage({ message: { body: userMessage } })
61
+ setState({ ...state })
62
+ })
63
+
64
+ return h(ChatBot, {
65
+ buttonText,
66
+ headerText,
67
+ attachText,
68
+ inputText,
69
+ sendText,
70
+ isBusy: isBusy || !state.transport.isOpen(),
71
+ icon,
72
+ messages,
73
+ onInit () {
74
+ state.transport.connect(() => setState({ ...state })) // Redraw on new message.
75
+ },
76
+ onSend (userMessage) {
77
+ state.transport.sendMessage({ message: { body: userMessage } })
78
+ setState({ ...state }) // Redraw on message sent.
79
+ },
80
+ onClose () {
81
+ state.transport.disconnect()
82
+ },
83
+ async onAttach (file) {
84
+ await state.transport.sendFile(file)
85
+ setState({ ...state }) // Redraw on file attached.
86
+ }
87
+ })
88
+ }
89
+
90
+ function getMessagesFromHistory (history, onSend) {
91
+ const messages = []
92
+
93
+ for (const data of history) {
94
+ if (data.type === 'bot_response') {
95
+ messages.push(...resolveBotMessages(data, onSend))
96
+ } else if (data.resourceid === '/bot.message' && data?.message?.body) {
97
+ messages.push({
98
+ children: data.message.body,
99
+ isBot: false,
100
+ timestamp: new Date(data.id)
101
+ })
102
+ }
103
+ }
104
+
105
+ const isBusy = history.length < 4
106
+ if (isBusy) {
107
+ messages.push(THINKING_MESSAGE)
108
+ }
109
+
110
+ return [messages, isBusy]
111
+ }
112
+
113
+ function resolveBotMessages (eventData, onSend) {
114
+ const messages = []
115
+
116
+ for (const message of eventData?.message ?? []) {
117
+ const outerPayload = message?.component?.payload
118
+ const innerPayload = outerPayload?.payload
119
+ const payload = innerPayload ?? outerPayload
120
+ const text = resolveText(payload)
121
+ const buttonTitles = resolveButtonTitles(payload)
122
+ const imageModel = outerPayload?.type === 'image' && { src: payload.url }
123
+
124
+ messages.push({
125
+ children: h('div', { className: 'space-y-2' },
126
+ // Image.
127
+ imageModel && h('div', { className: 'rounded-lg overflow-hidden' },
128
+ h(Img, { _model: imageModel })
129
+ ),
130
+
131
+ // Text message.
132
+ text && h(RichText, marked(text)),
133
+
134
+ // Buttons.
135
+ buttonTitles.length > 0 && h('div', { className: 'flex flex-col', style: 'gap: 8px' }, buttonTitles.map(buttonTitle =>
136
+ h('div',
137
+ h(Button, {
138
+ _model: { name: buttonTitle },
139
+ _size: 'xs',
140
+ async onClick () {
141
+ onSend(buttonTitle)
142
+ }
143
+ })
144
+ )
145
+ ))
146
+ ),
147
+ isBot: eventData.type === 'bot_response',
148
+ timestamp: new Date(eventData.timestamp)
149
+ })
150
+ }
151
+
152
+ return messages
153
+ }
154
+
155
+ function resolveButtonTitles (payload) {
156
+ const buttonTitles = []
157
+ for (const reply of payload?.quick_replies ?? []) {
158
+ buttonTitles.push(reply.title)
159
+ }
160
+
161
+ for (const feedback of payload?.thumpsUpDownArrays ?? []) {
162
+ buttonTitles.push(feedback.reviewText)
163
+ }
164
+
165
+ return buttonTitles
166
+ }
167
+
168
+ function resolveText (payload) {
169
+ if (typeof payload === 'string') {
170
+ return payload
171
+ }
172
+
173
+ return payload?.text
174
+ }
175
+
176
+ register(ChatbotKoreai, 'chatbot-koreai')
@@ -0,0 +1,217 @@
1
+ const CHUNK_SIZE = 1_048_576 // 1 MiB
2
+ const PING_INTERVAL = 30_000
3
+
4
+ export default class KoreaiTransport {
5
+ constructor (id, name, url) {
6
+ const [language, country] = document.documentElement.lang.toLowerCase().split('-')
7
+ this.socket = null
8
+ this.history = []
9
+ this.pingInterval = null
10
+ this.jwtUrl = new URL(url)
11
+ this.botInfo = {
12
+ chatBot: name,
13
+ taskBotId: id,
14
+ customData: { botlanguage: language, countryCode: country }
15
+ }
16
+ }
17
+
18
+ async connect (onStateChange, params = '') {
19
+ // Get the websocket URL.
20
+ const res = await fetch('https://de-bots.kore.ai/api/rtm/start', {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ authorization: `Bearer ${await this._getAccessToken()}`
25
+ },
26
+ body: JSON.stringify({
27
+ botInfo: this.botInfo,
28
+ language: this.botInfo.customData.botlanguage,
29
+ bot_title: this.botInfo.chatBot
30
+ })
31
+ })
32
+ const { url } = await res.json()
33
+
34
+ // Create web socket connection.
35
+ this.socket = new WebSocket(url + params)
36
+
37
+ // Reconnect on close.
38
+ this.socket.addEventListener('close', () => {
39
+ onStateChange()
40
+ clearInterval(this.pingInterval)
41
+ this.connect(onStateChange, '&isReconnect=true&ConnectionMode=Reconnect')
42
+ })
43
+
44
+ this.socket.addEventListener('error', event => {
45
+ console.error('WebSocket error:', event)
46
+ })
47
+
48
+ // Handle incoming messages.
49
+ this.socket.addEventListener('message', event => {
50
+ const data = JSON.parse(event.data)
51
+ this.history.push(data)
52
+ onStateChange()
53
+ })
54
+
55
+ // Handle ping.
56
+ clearInterval(this.pingInterval)
57
+ this.pingInterval ||= setInterval(() => this.sendMessage({ type: 'ping' }), PING_INTERVAL)
58
+ }
59
+
60
+ async disconnect () {
61
+ return this.sendMessage({
62
+ customEvent: { web_sdk: 'reconnect_button_event' },
63
+ resourceid: '/bot.clientEvent',
64
+ message: { body: 'reconnect_button_event', type: 'text' },
65
+ })
66
+ }
67
+
68
+ async sendMessage (message) {
69
+ if (!this.isOpen()) {
70
+ return
71
+ }
72
+
73
+ const date = new Date()
74
+
75
+ const data = {
76
+ clientMessageId: date.getTime(),
77
+ resourceid: '/bot.message',
78
+ botInfo: this.botInfo,
79
+ client: 'sdk',
80
+ meta: {
81
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
82
+ locale: document.documentElement.lang // Use site locale rather than browser locale.
83
+ },
84
+ id: date.getTime(),
85
+ ...message
86
+ }
87
+
88
+ this.history.push(data)
89
+ this.socket.send(JSON.stringify(data))
90
+ }
91
+
92
+ async sendFile (file) {
93
+ const chunks = await this._getBase64Chunks(file)
94
+ const fileToken = await this._getFileToken()
95
+ const messageToken = await this._getFileToken()
96
+ const boundary = `--------MultipartData${this._getRandomToken()}`
97
+
98
+ // Send chunk requests.
99
+ for (const [ii, chunk] of chunks.entries()) {
100
+ const formData = new FormData()
101
+ formData.append('chunkNo', ii)
102
+ formData.append('messageToken', messageToken)
103
+ formData.append('chunk', chunk)
104
+ await fetch(`https://de-bots.kore.ai/api/1.1/attachment/file/${fileToken}/chunk`, {
105
+ method: 'POST',
106
+ headers: {
107
+ authorization: `Bearer ${await this._getAccessToken()}`,
108
+ 'content-type': `multipart/form-data; boundary=${boundary}`
109
+ },
110
+ body: this._getCustomMultipartBody(boundary, { chunkNo: ii, messageToken, chunk })
111
+ })
112
+ }
113
+
114
+ // Send completion request.
115
+ const res = await fetch(`https://de-bots.kore.ai/api/1.1/attachment/file/${fileToken}`, {
116
+ method: 'PUT',
117
+ headers: {
118
+ accept: '*/*',
119
+ authorization: `Bearer ${await this._getAccessToken()}`,
120
+ 'content-type': `multipart/form-data; boundary=${boundary}`
121
+ },
122
+ body: this._getCustomMultipartBody(boundary, {
123
+ totalChunks: chunks.length,
124
+ messageToken,
125
+ fileExtension: file.name.split('.').pop(),
126
+ fileContext: 'workflows',
127
+ thumbnailUpload: false,
128
+ filename: file.name
129
+ })
130
+ })
131
+
132
+ const { fileId } = await res.json()
133
+
134
+ await this.sendMessage({
135
+ message: {
136
+ body: file.name,
137
+ attachments: [{ fileName: file.name, fileType: file.type, fileId }]
138
+ }
139
+ })
140
+ }
141
+
142
+ isOpen () {
143
+ return this?.socket?.readyState === WebSocket.OPEN
144
+ }
145
+
146
+ async _getBase64Chunks (file) {
147
+ const chunks = []
148
+ for (let ii = 0; ii < file.size; ii += CHUNK_SIZE) {
149
+ const blob = file.slice(ii, ii + CHUNK_SIZE)
150
+ const chunk = await new Promise(resolve => {
151
+ const reader = new FileReader()
152
+ reader.onloadend = event => {
153
+ resolve(event.target.result.split(',').pop())
154
+ }
155
+ reader.readAsDataURL(blob)
156
+ })
157
+ chunks.push(chunk)
158
+ }
159
+
160
+ return chunks
161
+ }
162
+
163
+ // Why the hell aren't Kore.ai following standards?
164
+ _getCustomMultipartBody (boundary, data) {
165
+ const lines = []
166
+ for (const key of Object.keys(data)) {
167
+ lines.push(`--${boundary}`)
168
+ if (key === 'chunk') {
169
+ lines.push(`Content-Disposition: form-data; name="${key}"; filename="${data[key].name}"`, 'Content-Transfer-Encoding: base64')
170
+ } else {
171
+ lines.push(`Content-Disposition: form-data; name="${key}";`)
172
+ }
173
+ lines.push('', data[key])
174
+ }
175
+ lines.push(`--${boundary}--`)
176
+ return lines.join('\r\n')
177
+ }
178
+
179
+ async _getJwt () {
180
+ const res = await fetch(`${this.jwtUrl.origin}/acinguiux-botkit/generate-token?taskBotId=${this.botInfo.taskBotId}`)
181
+ const json = await res.json()
182
+ return json.jwt
183
+ }
184
+
185
+ async _getAccessToken () {
186
+ if (!this.accessToken) {
187
+ const res = await fetch('https://de-bots.kore.ai/api/oAuth/token/jwtgrant', {
188
+ method: 'POST',
189
+ headers: { 'Content-Type': 'application/json' },
190
+ body: JSON.stringify({ assertion: await this._getJwt(), botInfo: this.botInfo, token: {} })
191
+ })
192
+ const json = await res.json()
193
+ this.accessToken = json.authorization.accessToken
194
+ }
195
+
196
+ return this.accessToken
197
+ }
198
+
199
+ async _getFileToken () {
200
+ const res = await fetch('https://de-bots.kore.ai/api/1.1/attachment/file/token', {
201
+ method: 'POST',
202
+ headers: {
203
+ accept: 'application/json',
204
+ authorization: `Bearer ${await this._getAccessToken()}`
205
+ }
206
+ })
207
+
208
+ const json = await res.json()
209
+ return json.fileToken
210
+ }
211
+
212
+ _getRandomToken () {
213
+ const buffer = new Uint8Array(16)
214
+ crypto.getRandomValues(buffer)
215
+ return Array.from(buffer, byte => byte.toString(16).padStart(2, '0')).join('')
216
+ }
217
+ }
@@ -0,0 +1,210 @@
1
+ import ChatBot from '#acinguiux-preact/wcs/shared/chat-bot/chat-bot.js'
2
+ import { marked } from 'marked'
3
+
4
+ const { h, preactHooks, matter, register } = globalThis.ami
5
+ const { useState } = preactHooks
6
+ const { Heading, Link, RichText } = matter
7
+
8
+ // API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#activity-object
9
+ const ACTIVITY_MAP = {
10
+ message: MessageActivity,
11
+ typing: TypingActivity
12
+ }
13
+
14
+ const API_URL = 'https://directline.botframework.com/v3/directline/conversations'
15
+ const USER_ID = globalThis.crypto.randomUUID()
16
+
17
+ // -----------------------------------------------------------------------------
18
+ // Main chat bot component
19
+ // -----------------------------------------------------------------------------
20
+
21
+ function ChatbotMS ({ 'instance-id': instanceId, 'button-text': buttonText, 'header-text': headerText, icon, state }) {
22
+ const [data, setData] = useState({
23
+ user: { id: USER_ID, name: USER_ID },
24
+ conv: {},
25
+ messages: []
26
+ })
27
+
28
+ if (!instanceId) {
29
+ return null
30
+ }
31
+
32
+ const start = async () => {
33
+ // Start a conversation.
34
+ const res = await fetch(API_URL, {
35
+ method: 'post',
36
+ headers: { authorization: `Bearer ${instanceId}`, 'content-type': 'application/json' },
37
+ body: JSON.stringify({ user: data.user })
38
+ })
39
+
40
+ data.conv = await res.json()
41
+ setData({ ...data })
42
+
43
+ // Connect to a web socket.
44
+ const socket = new globalThis.WebSocket(data.conv.streamUrl)
45
+ socket.addEventListener('message', event => {
46
+ if (!event.data) {
47
+ return
48
+ }
49
+
50
+ // Get activities.
51
+ const activities = JSON.parse(event.data)?.activities || []
52
+
53
+ // Limit activities to supported only.
54
+ const supportedActivities = activities.filter(activity => Object.keys(ACTIVITY_MAP).includes(activity.type))
55
+
56
+ // Convert activities to messages.
57
+ const messages = supportedActivities.map(activity => ({
58
+ isTyping: activity.type === 'typing',
59
+ isBot: activity.from.id !== data.user.id,
60
+ children: h(ACTIVITY_MAP[activity.type], { activity, sendFromUser })
61
+ }))
62
+
63
+ // Remove all existing typing messages.
64
+ data.messages = data.messages.filter(m => !m.isTyping)
65
+
66
+ // Update messages.
67
+ data.messages.push(...messages)
68
+ setData({ ...data })
69
+ })
70
+ }
71
+
72
+ const sendFromUser = async text => fetch(`${API_URL}/${data.conv.conversationId}/activities`, {
73
+ method: 'post',
74
+ headers: { authorization: `Bearer ${data.conv.token}`, 'content-type': 'application/json' },
75
+ body: JSON.stringify({ locale: getLocale(), from: data.user, type: 'message', text })
76
+ })
77
+
78
+ return h(ChatBot, {
79
+ buttonText,
80
+ headerText,
81
+ icon,
82
+ forceOpen: state === 'open',
83
+ messages: data.messages,
84
+ onInit () {
85
+ start()
86
+ },
87
+ onSend (userMessage) {
88
+ sendFromUser(userMessage)
89
+ }
90
+ })
91
+ }
92
+
93
+ register(ChatbotMS, 'chatbot-ms')
94
+
95
+ // -----------------------------------------------------------------------------
96
+ // Activities
97
+ // -----------------------------------------------------------------------------
98
+
99
+ function MessageActivity ({ activity, sendFromUser }) {
100
+ const actions = activity?.suggestedActions?.actions
101
+ return [
102
+ activity.text && h(Markdown, { text: activity.text }),
103
+ activity.attachments && h(Attachments, { attachments: activity.attachments, sendFromUser }),
104
+ actions?.length && h(SuggestedActions, { actions, sendFromUser })
105
+ ]
106
+ }
107
+
108
+ function SuggestedActions ({ actions, sendFromUser }) {
109
+ return h('div', { className: 'space-y-2' }, actions.map(action =>
110
+ h('div',
111
+ h(CardAction, { content: action, sendFromUser })
112
+ )
113
+ ))
114
+ }
115
+
116
+ function TypingActivity () {
117
+ return h('span', '...')
118
+ }
119
+
120
+ // -----------------------------------------------------------------------------
121
+ // Attachments
122
+ // -----------------------------------------------------------------------------
123
+
124
+ // API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#attachment-object
125
+ function Attachments ({ attachments, sendFromUser }) {
126
+ const attachmentMap = {
127
+ 'application/vnd.microsoft.card.hero': HeroCard,
128
+ 'application/vnd.microsoft.card.adaptive': AdaptiveCard
129
+ }
130
+
131
+ return attachments.map(a => h('div',
132
+ attachmentMap[a.contentType] && h(attachmentMap[a.contentType], { content: a.content, sendFromUser })
133
+ ))
134
+ }
135
+
136
+ // Attachment type: application/vnd.microsoft.card.hero
137
+ function HeroCard ({ content, sendFromUser }) {
138
+ return h('div', { className: 'space-y-2' },
139
+ content.title && h(Heading, { _level: 3 }, content.title),
140
+ content.subtitle && h('p', content.subtitle),
141
+ content.text && h(Markdown, { text: content.text }),
142
+ content.images?.map(im => h('img', { className: 'block w-full', src: im.url })),
143
+ content.buttons && h('div', { className: 'space-y-2' }, content.buttons.map(b => // type: imBack, openUrl
144
+ h('div',
145
+ h(CardAction, { content: b, sendFromUser })
146
+ ))
147
+ )
148
+ )
149
+ }
150
+
151
+ // Rabbit hole: https://adaptivecards.io
152
+ function AdaptiveCard ({ content, sendFromUser }) {
153
+ return h('div', { className: 'space-y-2' },
154
+ content.body?.map(b => // type: imBack, openUrl
155
+ b.type === 'TextBlock' && h(Markdown, { text: b.text })
156
+ ),
157
+ (content?.actions ?? []).map(a =>
158
+ h(CardAction, { content: { ...a, value: a.data }, sendFromUser })
159
+ )
160
+ )
161
+ }
162
+
163
+ // API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#cardaction-object
164
+ // Types: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-add-rich-cards?view=azure-bot-service-4.0#process-events-within-rich-cards
165
+ function CardAction ({ content, sendFromUser }) {
166
+ const baseProps = {
167
+ _variant: 'simple',
168
+ className: 'rounded-md pt-1 pb-1 pl-2 pr-2 border border-txa/20 inline-block'
169
+ }
170
+
171
+ switch (content.type) {
172
+ case 'call':
173
+ case 'downloadFile':
174
+ case 'openUrl':
175
+ case 'playAudio':
176
+ case 'playVideo':
177
+ case 'showImage':
178
+ case 'signin':
179
+ return h(Link, {
180
+ ...baseProps,
181
+ _model: { name: content.title, value: content.value },
182
+ target: '_blank',
183
+ rel: 'noopener',
184
+ })
185
+ default:
186
+ return content.url
187
+ ? h(Link, { ...baseProps, _model: { name: content.title, value: content.url }, target: '_blank' })
188
+ : h(Link, {
189
+ _model: { name: content.title },
190
+ ...baseProps,
191
+ onClick () {
192
+ sendFromUser(content.value)
193
+ }
194
+ })
195
+ }
196
+ }
197
+
198
+ // -----------------------------------------------------------------------------
199
+ // Helpers
200
+ // -----------------------------------------------------------------------------
201
+
202
+ function getLocale () {
203
+ return document.documentElement.lang ?? navigator.language ?? 'en'
204
+ }
205
+
206
+ function Markdown ({ text }) {
207
+ // Make sure links open in a new window.
208
+ const markedText = marked(text).replaceAll(/( href=")/gm, ' rel="noopener" target="_blank" $1')
209
+ return h(RichText, markedText)
210
+ }
@@ -0,0 +1,44 @@
1
+ import ChatBot from '#acinguiux-preact/wcs/shared/chat-bot/chat-bot.js'
2
+
3
+ const { h, preactHooks, register } = globalThis.ami
4
+ const { useState } = preactHooks
5
+
6
+ const DELAY = 1000 // Simulate lag.
7
+
8
+ function ChatbotTest () {
9
+ const [state, setState] = useState({ messages: [], isBusy: true })
10
+
11
+ return h(ChatBot, {
12
+ buttonText: 'Ask us',
13
+ headerText: 'Test chatbot',
14
+ closeText: 'Close window',
15
+ attachText: 'Attach file',
16
+ inputText: 'Type your message here',
17
+ sendText: 'Send message',
18
+ icon: null,
19
+ isBusy: state.isBusy,
20
+ forceOpen: false,
21
+ messages: state.messages,
22
+ onInit () {
23
+ setTimeout(() => {
24
+ setState(prevState => ({ ...prevState, messages: [...prevState.messages, { isBot: true, children: ['Hello!'], timestamp: new Date() }], isBusy: false }))
25
+ }, DELAY)
26
+ },
27
+ onSend (userMessage) {
28
+ setState(prevState => ({ ...prevState, messages: [...prevState.messages, { children: [userMessage], timestamp: new Date() }], isBusy: true }))
29
+
30
+ setTimeout(() => {
31
+ setState(prevState => ({ ...prevState, messages: [...prevState.messages, { isBot: true, children: [`You sent: ${userMessage}`], timestamp: new Date() }], isBusy: false }))
32
+ }, DELAY)
33
+ },
34
+ onAttach (file) {
35
+ setState(prevState => ({ ...prevState, messages: [...prevState.messages, { children: [`Attaching ${file.name}`], timestamp: new Date() }], isBusy: true }))
36
+
37
+ setTimeout(() => {
38
+ setState(prevState => ({ ...prevState, messages: [...prevState.messages, { isBot: true, children: [`You attached: ${file.name}`], timestamp: new Date() }], isBusy: false }))
39
+ }, DELAY)
40
+ }
41
+ })
42
+ }
43
+
44
+ register(ChatbotTest, 'chatbot-test')