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,248 @@
1
+ import formatTime from '#acinguiux-preact/main/shared/format-time.js'
2
+ import LABELS from './labels.js'
3
+ import { ARROW_NEXT } from '#acinguiux-preact/main/shared/icons.js'
4
+
5
+ const { h, matter, preactHooks } = globalThis.ami
6
+ const { Box, Button, Heading, Input, Link, List, Table, Tabs } = matter
7
+ const { useEffect, useRef, useState } = preactHooks
8
+
9
+ const TEXT_CENTER = 'text-center'
10
+
11
+ function FormInput ({ label, title, ...props }) {
12
+ return h('label',
13
+ h('div', { className: 'block font-bold', title }, label),
14
+ h(Input, {
15
+ type: 'select',
16
+ ...props
17
+ })
18
+ )
19
+ }
20
+
21
+ function AdminUserForm ({ adminTools }) {
22
+ const adminFormRef = useRef(null)
23
+ const [state, setState] = useState({ isInProgress: false, hasSucceeded: false })
24
+
25
+ const onSubmit = event => {
26
+ event.preventDefault()
27
+
28
+ const form = adminFormRef.current
29
+ const formData = Object.fromEntries(new FormData(form))
30
+
31
+ const toggleForm = setDisabled => {
32
+ for (const element of form.elements) {
33
+ element.disabled = setDisabled
34
+ }
35
+ }
36
+
37
+ toggleForm(true)
38
+ setState({ isInProgress: true, hasSucceeded: false })
39
+
40
+ adminTools.request('user', formData).then(result => {
41
+ setState({ isInProgress: false, hasSucceeded: Boolean(result) })
42
+ toggleForm(false)
43
+ if (result) {
44
+ // Clear email value as it will be typically used only once.
45
+ form.elements['email'].value = ''
46
+ }
47
+ }).catch(error => {
48
+ toggleForm(false)
49
+ console.error(error)
50
+ })
51
+ }
52
+
53
+ return [
54
+ h('form', {
55
+ ref: adminFormRef,
56
+ onSubmit
57
+ },
58
+ h('div', { className: 'flex sm:flex-col md:flex-col lg:items-end gap-5' },
59
+ h('div', { className: 'grow min-w-1/3' },
60
+ h(FormInput, {
61
+ label: 'Email',
62
+ name: 'email',
63
+ required: true,
64
+ type: 'email'
65
+ })
66
+ ),
67
+ h('div', { className: 'grow' },
68
+ h(FormInput, {
69
+ label: 'Action',
70
+ name: 'action',
71
+ children: [
72
+ h('option', { value: 'update' }, 'Update'),
73
+ h('option', { value: 'add' }, 'Add/reactivate'),
74
+ h('option', { value: 'remove' }, 'Remove/deactivate')
75
+ ]
76
+ })
77
+ ),
78
+ h('div', { className: 'grow' },
79
+ h(FormInput, {
80
+ label: 'Quota',
81
+ name: 'quota',
82
+ type: 'number',
83
+ min: '0'
84
+ })
85
+ ),
86
+ h('div', { className: 'grow' },
87
+ h(FormInput, {
88
+ label: 'Admin status',
89
+ name: 'isAdmin',
90
+ children: [
91
+ h('option', { value: '' }, '(no change)'),
92
+ h('option', { value: '1' }, 'Yes'),
93
+ h('option', { value: '0' }, 'No')
94
+ ]
95
+ })
96
+ ),
97
+ h('div', { className: 'grow-0' },
98
+ h(Button, {
99
+ type: 'submit',
100
+ _textless: true,
101
+ disabled: state.isInProgress,
102
+ _model: { name: LABELS.GENERATE_BUTTON, icon: ARROW_NEXT }
103
+ })
104
+ )
105
+ )),
106
+ state.hasSucceeded && h('div', { className: TEXT_CENTER }, 'Action completed successfully.')
107
+ ]
108
+ }
109
+
110
+ function AdminData ({ adminTools, endpoint, dataPanelCache, headings, excludeFields, fieldHandler }) {
111
+ const [state, setState] = useState({ isLoading: true, data: dataPanelCache?.current[endpoint] || null })
112
+
113
+ useEffect(() => {
114
+ if (state.data) {
115
+ setState(prev => ({ ...prev, isLoading: false }))
116
+ return
117
+ }
118
+ adminTools.request(endpoint)
119
+ .then(data => {
120
+ setState(prev => ({ ...prev, isLoading: false, data }))
121
+ // Cache data if enabled.
122
+ if (dataPanelCache) {
123
+ dataPanelCache.current[endpoint] = data
124
+ }
125
+ })
126
+ .catch(console.error)
127
+ }, []) // Only a single call.
128
+
129
+ if (state.isLoading) {
130
+ return h('div', { className: TEXT_CENTER }, LABELS.LOADING)
131
+ }
132
+
133
+ return Boolean(state.data) && h(Table, {
134
+ children: [
135
+ h('tr', headings.map((heading, index) =>
136
+ h('th', { key: index }, heading)
137
+ )),
138
+ state.data?.items?.map((item, index) =>
139
+ h('tr', { key: index },
140
+ Object.entries(item).filter(([field]) => !excludeFields?.includes(field)).map(([field, value], cellIndex) =>
141
+ h('td', { key: cellIndex }, fieldHandler(value, field, item))
142
+ )
143
+ )
144
+ )
145
+ ]
146
+ })
147
+ }
148
+
149
+ function AdminReportLink ({ name, value }) {
150
+ return h(Link, {
151
+ className: 'clickable cursor-pointer hover:underline',
152
+ _model: {
153
+ name,
154
+ value
155
+ },
156
+ onClick: e => {
157
+ e.preventDefault()
158
+ // Bypass external disclaimer.
159
+ window.open(value, '_blank', 'noopener')
160
+ }
161
+ })
162
+ }
163
+
164
+ export default function AdminPanel ({ adminTools }) {
165
+ const [state, setState] = useState({ userList: false, models: false })
166
+ const dataPanelCache = useRef({})
167
+
168
+ useEffect(() => {
169
+ const onHashChange = () => {
170
+ const tabStateMap = {
171
+ 'tab-user-list': 'userList',
172
+ 'tab-models': 'models'
173
+ }
174
+
175
+ const stateProp = tabStateMap[globalThis.location.hash.substring(1)]
176
+ if (stateProp) {
177
+ setState(prev => ({
178
+ ...prev,
179
+ [stateProp]: true
180
+ }))
181
+ }
182
+ }
183
+ globalThis.addEventListener('hashchange', onHashChange)
184
+ globalThis.location.hash && onHashChange()
185
+
186
+ return () => globalThis.removeEventListener('hashchange', onHashChange)
187
+ }, [])
188
+
189
+ return h(Box, { className: 'rounded-2xl bg-bga text-txa' },
190
+ h(Heading, { _level: 2, _size: 4 }, 'Administrator panel'),
191
+ h(Tabs, {
192
+ _model: {
193
+ links: [{ name: 'User management' }, { name: 'User list' }, { name: 'Models' }, { name: 'Reports' }]
194
+ },
195
+ children: [
196
+ h('div', { className: 'space-y-5' }, h(AdminUserForm, { adminTools })),
197
+ h('div', {
198
+ children: state.userList &&
199
+ h(AdminData, {
200
+ adminTools,
201
+ endpoint: 'users',
202
+ dataPanelCache,
203
+ headings: ['Email', 'Quota', 'Usage', 'Prompts', 'Admin'],
204
+ excludeFields: ['isInactive'], // Handled by line-through.
205
+ fieldHandler: (value, field, item) => {
206
+ if (['isInactive', 'isAdmin'].includes(field)) {
207
+ return h('div', { className: TEXT_CENTER }, value ? '+' : '-')
208
+ }
209
+
210
+ if (field === 'email' && item['isInactive']) {
211
+ return h('span', { className: 'line-through' }, value)
212
+ }
213
+
214
+ return value
215
+ }
216
+ })
217
+ }),
218
+ h('div', {
219
+ children: state.models &&
220
+ h(AdminData, {
221
+ adminTools,
222
+ endpoint: 'models',
223
+ dataPanelCache,
224
+ headings: ['Name', 'ID', 'Created', 'Modified'],
225
+ fieldHandler: (value, field) => {
226
+ if (['createdDate', 'modifiedDate'].includes(field)) {
227
+ const date = new Date(value)
228
+ return date.getTime() ? formatTime(date, true) : '-'
229
+ }
230
+
231
+ return value
232
+ }
233
+ })
234
+ }),
235
+ h('div',
236
+ h(List,
237
+ h('li',
238
+ h('div', h(AdminReportLink, { name: 'Quota usage', value: adminTools.getUrl('reports?id=quota-usage') }))
239
+ ),
240
+ h('li',
241
+ h('div', h(AdminReportLink, { name: 'User prompts', value: adminTools.getUrl('reports?id=user-prompts') }))
242
+ )
243
+ )
244
+ )
245
+ ]
246
+ })
247
+ )
248
+ }
@@ -0,0 +1,385 @@
1
+ /* global process */
2
+ import { ARROW_DOWN, ARROW_NEXT, ASSET_DOWNLOAD } from '#acinguiux-preact/main/shared/icons.js'
3
+ import formatTime from '#acinguiux-preact/main/shared/format-time.js'
4
+ import useApi, { STATUS, LOAD_MORE_STATUS, PROMPT_STATUS } from './use-api.js'
5
+ import AdminPanel from './admin-panel.js'
6
+ import LABELS from './labels.js'
7
+ import { isEdit } from '#acinguiux-preact/main/shared/wcm-mode.js'
8
+
9
+ const { h, register, matter, preactHooks } = globalThis.ami
10
+ const { Box, Button, Icon, Input, Link } = matter
11
+ const { useEffect, useMemo, useRef, useState } = preactHooks
12
+
13
+ const IMAGE_GEN_API_URL = process.env.IMAGE_GEN_API_URL || ''
14
+ const LG_COL_SPAN_4 = 'lg:col-span-4'
15
+
16
+ // Form configuration.
17
+ const PROMPT_MIN_LENGTH = 5
18
+ const PROMPT_MAX_LENGTH = 1024
19
+ const NUM_VARIATIONS = 2
20
+ const VARIATIONS = Array.from({ length: NUM_VARIATIONS }, (_, i) => ({ label: i + 1, value: i + 1 }))
21
+ const SIZES = [
22
+ { label: '1:1 - 2048x2048 (Square)', value: '2048x2048' },
23
+ { label: '4:3 - 2304x1792 (Landscape)', value: '2304x1792' },
24
+ { label: '3:4 - 1792x2304 (Portrait)', value: '1792x2304' },
25
+ { label: '16:9 - 2688x1536 (Widescreen)', value: '2688x1536' },
26
+ { label: '9:16 - 1440x2560 (Tall portrait)', value: '1440x2560' },
27
+ ]
28
+
29
+ // Common Link-like button props for accessibility.
30
+ const accessibleLinkButtonProps = {
31
+ role: 'button',
32
+ tabindex: 0,
33
+ onKeyDown: e => {
34
+ if (e.key === 'Enter' || e.key === ' ') {
35
+ e.preventDefault()
36
+ e.stopPropagation()
37
+ e.currentTarget.click()
38
+ }
39
+ }
40
+ }
41
+
42
+ function PromptForm ({ formRef, submitPrompt, isInProgress, customModels, quotaUsage }) {
43
+ const [state, setState] = useState({ isExpanded: false })
44
+ const formOptionsRef = useRef(null)
45
+ const quotaData = useMemo(() => {
46
+ if (!quotaUsage?.total) {
47
+ return null
48
+ }
49
+ const imagesLeft = Math.max(quotaUsage.total - quotaUsage.usage, 0)
50
+ const percentageLeft = Math.min(Math.floor((imagesLeft / quotaUsage.total) * 100), 100)
51
+ return { ...quotaUsage, imagesLeft, percentageLeft }
52
+ }, [quotaUsage])
53
+
54
+ useEffect(() => {
55
+ // Set default values.
56
+ resetOptions()
57
+ }, [])
58
+
59
+ const onSubmit = event => {
60
+ event.preventDefault()
61
+
62
+ const form = formRef.current
63
+ const prompt = form.text.value.trim() // We internally use "text" to avoid "prompt.prompt" confusion.
64
+ if (!prompt?.length) {
65
+ form.text.value = ''
66
+ form.reportValidity()
67
+ return
68
+ }
69
+ const formData =
70
+ // Filter out empty values to skip these fields entirely, remove text as it will be renamed to prompt.
71
+ Object.fromEntries(
72
+ [...new FormData(form)].filter(([k, v]) => k !== 'text' && Boolean(v?.trim?.()))
73
+ )
74
+ const numVariations = Number(formData.numVariations)
75
+ const [width, height] = formData.size.split('x')
76
+
77
+ form.text.value = ''
78
+ submitPrompt({
79
+ ...formData,
80
+ prompt,
81
+ numVariations,
82
+ size: { width, height }
83
+ }, () => {
84
+ if (!form.text.value) {
85
+ // Restore prompt text on error.
86
+ form.text.value = prompt
87
+ }
88
+ }).catch(console.error)
89
+ }
90
+
91
+ const resetOptions = () => {
92
+ const inputs = formOptionsRef.current.querySelectorAll('select, input, textarea')
93
+ for (const input of inputs) {
94
+ if (input.dataset.default) {
95
+ input.value = input.dataset.default
96
+ } else if (input.options) {
97
+ input.selectedIndex = 0
98
+ } else {
99
+ input.value = ''
100
+ }
101
+ }
102
+ }
103
+
104
+ return h('form', {
105
+ ref: formRef,
106
+ onSubmit
107
+ },
108
+ h(Box, { className: 'bg-bga rounded-2xl' },
109
+ // Main input.
110
+ h('div', { className: 'flex gap-5' },
111
+ h('div', { className: 'grow' },
112
+ h(Input, {
113
+ type: 'textarea',
114
+ minlength: PROMPT_MIN_LENGTH,
115
+ maxlength: PROMPT_MAX_LENGTH,
116
+ required: true,
117
+ name: 'text',
118
+ placeholder: LABELS.PROMPT_PLACEHOLDER,
119
+ 'aria-label': LABELS.PROMPT_PLACEHOLDER,
120
+ onKeyDown: e => {
121
+ if (e.key === 'Enter' && e.ctrlKey) {
122
+ e.preventDefault()
123
+ formRef.current.requestSubmit()
124
+ }
125
+ }
126
+ })
127
+ ),
128
+ h('div', { className: 'grow-0' },
129
+ h(Button, {
130
+ type: 'submit',
131
+ _textless: true,
132
+ disabled: isInProgress,
133
+ _model: { name: LABELS.GENERATE_BUTTON, icon: ARROW_NEXT }
134
+ })
135
+ )
136
+ ),
137
+ h('div',
138
+ h('div', { className: 'flex gap-5' },
139
+ h('div', { className: 'w-1/2' },
140
+ // Settings toggle.
141
+ h(Link, {
142
+ _variant: 'underline',
143
+ 'aria-controls': 'image-gen-prompt-settings',
144
+ 'aria-expanded': state.isExpanded,
145
+ className: 'clickable inline-flex items-center gap-2 cursor-pointer',
146
+ onClick: () => setState(prev => ({ ...prev, isExpanded: !prev.isExpanded })),
147
+ ...accessibleLinkButtonProps
148
+ },
149
+ h('span', LABELS.NEW_PROMPT_SETTINGS),
150
+ h('span', { className: ` transition-all ${state.isExpanded ? 'rotate-180' : ''}` }, h(Icon, { _size: 'sm' }, ARROW_DOWN)))
151
+ ),
152
+ // Quota/usage meter.
153
+ Boolean(quotaData) && h('div', { className: 'w-1/2' },
154
+ h('div', { className: 'text-right', title: `${quotaData.percentageLeft}%` }, `${LABELS.QUOTA_USAGE}: ${quotaData.imagesLeft} / ${quotaData.total}`)
155
+ )
156
+ ),
157
+ h('div', { className: `grid lg:grid-cols-12 gap-5 mt-5 animate-expand ${state.isExpanded ? '' : 'hidden'}`, ref: formOptionsRef, id: 'image-gen-prompt-settings' },
158
+ // Settings.
159
+ h('div', { className: LG_COL_SPAN_4 },
160
+ h(PromptFormInput, {
161
+ label: LABELS.VARIATIONS_LABEL,
162
+ name: 'numVariations',
163
+ 'data-default': '1',
164
+ children: VARIATIONS.map((option, index) => h('option', { key: index, value: option.value }, option.label))
165
+ })
166
+ ),
167
+ h('div', { className: LG_COL_SPAN_4 },
168
+ h(PromptFormInput, {
169
+ label: LABELS.SIZE_LABEL,
170
+ name: 'size',
171
+ children: SIZES.map((option, index) => h('option', { key: index, value: option.value }, option.label))
172
+ })
173
+ ),
174
+ (customModels?.length > 0) && h('div', { className: LG_COL_SPAN_4 },
175
+ h(PromptFormInput, {
176
+ label: LABELS.CUSTOM_MODEL_LABEL,
177
+ name: 'customModelId',
178
+ children: customModels.map(({ name, id }, index) => h('option', { key: index, value: id }, name))
179
+ })
180
+ ),
181
+ h('div', { className: 'lg:row-start-2 lg:col-span-12' },
182
+ h(PromptFormInput, {
183
+ label: LABELS.NEGATIVE_PROMPT_LABEL,
184
+ type: 'textarea',
185
+ maxlength: PROMPT_MAX_LENGTH,
186
+ name: 'negativePrompt',
187
+ placeholder: LABELS.NEGATIVE_PROMPT_PLACEHOLDER
188
+ })
189
+ ),
190
+ h('div', { className: 'lg:row-start-3 lg:col-span-12' },
191
+ h(Link, {
192
+ _model: { name: LABELS.RESTORE_DEFAULTS, value: '#' },
193
+ _variant: 'underline',
194
+ onClick: e => {
195
+ e.preventDefault()
196
+ resetOptions()
197
+ },
198
+ ...accessibleLinkButtonProps
199
+ })
200
+ )
201
+ )
202
+ )
203
+ ))
204
+ }
205
+
206
+ function PromptFormInput ({ label, title, ...props }) {
207
+ return h('label',
208
+ h('div', { className: 'block font-bold', title }, label),
209
+ h(Input, {
210
+ type: 'select',
211
+ ...props
212
+ })
213
+ )
214
+ }
215
+
216
+ function PromptItem ({ prompt, formRef }) {
217
+ const layoutImageCount = prompt.images?.length || prompt.numVariations || 1
218
+ const getMessage = messages =>
219
+ h('div', { className: `${layoutImageCount < 2 ? LG_COL_SPAN_4 : 'lg:col-span-8'} text-center pt-5 pb-5 space-y-5 self-center` }, messages)
220
+ const text = prompt.text?.trim()
221
+
222
+ const handlers = {
223
+ [PROMPT_STATUS.PENDING]: () => getMessage(LABELS.STATUS_SENDING_REQUEST),
224
+ [PROMPT_STATUS.RUNNING]: () => getMessage(LABELS.STATUS_GENERATING_IMAGES),
225
+ [PROMPT_STATUS.SUCCEEDED]: () =>
226
+ prompt.images.map((image, imageIndex) =>
227
+ image.url && h('div', { className: LG_COL_SPAN_4, key: image.seed },
228
+ h('div', { className: 'relative' },
229
+ // Image.
230
+ h('a', {
231
+ title: LABELS.VIEW_IMAGE_TOOLTIP,
232
+ 'aria-label': LABELS.VIEW_IMAGE_TOOLTIP,
233
+ href: image.url,
234
+ target: '_blank',
235
+ rel: 'noopener noreferrer',
236
+ className: 'clickable cursor-pointer block aspect-square',
237
+ }, h('img', {
238
+ className: 'block cursor-pointer w-full h-full object-cover object-center rounded-2xl',
239
+ src: image.url,
240
+ alt: `${text} (${imageIndex + 1})`
241
+ })),
242
+ // Tools.
243
+ image.downloadUrl && h('div', { className: 'inline-flex absolute bottom-4 right-4' },
244
+ h(Button, {
245
+ _variant: 'plain',
246
+ _size: 'xs',
247
+ _textless: true,
248
+ _model: {
249
+ name: LABELS.DOWNLOAD_IMAGE_BUTTON,
250
+ value: image.downloadUrl,
251
+ icon: ASSET_DOWNLOAD
252
+ },
253
+ onClick: e => {
254
+ e.preventDefault()
255
+ if (image.downloadUrl) {
256
+ // Bypass external disclaimer.
257
+ window.open(image.downloadUrl, '_blank', 'noopener')
258
+ }
259
+ }
260
+ })
261
+ )
262
+ )
263
+ )
264
+ ),
265
+ [PROMPT_STATUS.CANCELLED]: () => getMessage(LABELS.STATUS_CANCELLED),
266
+ [PROMPT_STATUS.FAILED]: () => getMessage(LABELS.STATUS_FAILED)
267
+ }
268
+
269
+ // No images means failed (service download/upload issues).
270
+ const status = ((prompt.status === PROMPT_STATUS.SUCCEEDED) && !prompt.images?.length) ? PROMPT_STATUS.FAILED : prompt.status
271
+
272
+ return handlers[status] &&
273
+ h(Box, { key: prompt.id, className: 'bg-bga rounded-2xl' },
274
+ h('article', { className: 'grid lg:grid-cols-12 grid-flow-row gap-5', tabindex: 0, 'data-prompt-id': prompt.id },
275
+ handlers[status](),
276
+ h('div', { className: `${layoutImageCount < 2 ? 'lg:col-span-8 lg:col-start-5' : 'lg:col-span-4 lg:col-start-9'} break-words space-y-5` },
277
+ // Prompt text - break into lines.
278
+ h('div', `${text || LABELS.PROMPT_TEXT_NOT_PROVIDED}`
279
+ .split('\n')
280
+ .map((line, index) => h('div', { className: 'whitespace-pre-wrap mb-1', key: index }, line))),
281
+ // Timestamp (show invisible if not ready to prevent layout shift).
282
+ h('time', {
283
+ className: `block text-txa/80 ${prompt.initDateTime ? '' : 'invisible'}`,
284
+ datetime: prompt.initDateTime?.toISOString()
285
+ }, `${prompt.initDateTime ? formatTime(prompt.initDateTime, true) : '-'}`),
286
+ // Reuse the prompt.
287
+ (text?.length > 0) && h(Link, {
288
+ _size: 'xs',
289
+ _model: { name: LABELS.REUSE_BUTTON },
290
+ _variant: 'underline',
291
+ onClick: e => {
292
+ e.preventDefault()
293
+ formRef.current.text.value = text
294
+ formRef.current.text.focus()
295
+ },
296
+ ...accessibleLinkButtonProps
297
+ })
298
+ )
299
+ )
300
+ )
301
+ }
302
+
303
+ function ImageGen ({ 'api-url': apiUrl = IMAGE_GEN_API_URL, models, anonymous: isAnonymous }) {
304
+ // Get models from the attributes, skip invalid entries.
305
+ const customModels = useMemo(() =>
306
+ models?.split(';')
307
+ .map(model => model.split('|').map(s => s?.trim()))
308
+ .map(([name, id = '']) => ({ name, id }))
309
+ , [models])
310
+
311
+ // The main state.
312
+ const [state, setState] = useState({
313
+ status: isEdit ? STATUS.ERROR : STATUS.LOADING,
314
+ statusMessage: isEdit ? LABELS.ERROR_EDIT_MODE : null,
315
+ loadMore: LOAD_MORE_STATUS.NONE,
316
+ lastLoadPromptId: null,
317
+ isAdmin: false
318
+ })
319
+
320
+ // Get API-related handlers.
321
+ const [prompts, loadPrompts, statusPoll, submitPrompt, quotaUsage, adminTools] = useApi({
322
+ apiUrl,
323
+ isAnonymous,
324
+ setState
325
+ })
326
+
327
+ // Form element reference.
328
+ const formRef = useRef(null)
329
+
330
+ useEffect(() => {
331
+ // Load initial prompts.
332
+ state.status === STATUS.LOADING && loadPrompts().then(async () => {
333
+ // Determine if admin user.
334
+ const userStatus = await adminTools.request('user-status')
335
+ if (userStatus?.isAdmin) {
336
+ setState(prev => ({ ...prev, isAdmin: true }))
337
+ }
338
+ }).catch(console.error)
339
+ }, [state.status, apiUrl, adminTools])
340
+
341
+ useEffect(() => {
342
+ // Start status polling on prompt list update.
343
+ prompts?.length && statusPoll().catch(console.error)
344
+
345
+ // Focus on the newly loaded prompt after load more.
346
+ if (state.lastLoadPromptId) {
347
+ formRef.current.getRootNode().querySelectorAll(`[data-prompt-id="${state.lastLoadPromptId}"]`)[0]?.focus({ preventScroll: true })
348
+ setState(prev => ({ ...prev, lastLoadPromptId: null }))
349
+ }
350
+ }, [prompts])
351
+
352
+ // General component status.
353
+ if ([STATUS.LOADING, STATUS.ERROR].includes(state.status)) {
354
+ return h(Box, { className: 'rounded-2xl bg-bga text-txa text-center' },
355
+ state.status === STATUS.ERROR ? state.statusMessage : LABELS.LOADING
356
+ )
357
+ }
358
+
359
+ return h('div', { className: 'text-txa space-y-5' },
360
+ state.isAdmin && h(AdminPanel, { adminTools }),
361
+ // Prompt form.
362
+ h(PromptForm, { formRef, submitPrompt, isInProgress: state.status === STATUS.PROCESSING, customModels, quotaUsage }),
363
+
364
+ // Prompt list.
365
+ (prompts?.length > 0) && h('div', { className: 'space-y-5' },
366
+ prompts.map(
367
+ prompt => h(PromptItem, { key: prompt.id || `new-${prompt.newItemId}`, prompt, formRef })
368
+ ),
369
+ // Load more button.
370
+ ([LOAD_MORE_STATUS.ACTIVE, LOAD_MORE_STATUS.IN_PROGRESS].includes(state.loadMore)) &&
371
+ h('div', { className: 'flex items-center justify-center min-h-12' },
372
+ state.loadMore === LOAD_MORE_STATUS.IN_PROGRESS
373
+ ? h('span', LABELS.LOADING)
374
+ : h(Button, {
375
+ onClick: () => {
376
+ loadPrompts().catch(console.error)
377
+ },
378
+ _model: { name: LABELS.LOAD_MORE_BUTTON }
379
+ })
380
+ )
381
+ )
382
+ )
383
+ }
384
+
385
+ register(ImageGen, 'image-gen')
@@ -0,0 +1,37 @@
1
+ export default {
2
+ // Component general state.
3
+ LOADING: 'Loading...',
4
+ ERROR_EDIT_MODE: 'This web component is not available in Edit mode.',
5
+ ERROR_UNAUTHORISED: 'Access restricted: this tool is currently available to pilot users only. Want access? Request via ServiceNow with your business justification and line of business.',
6
+ ERROR_LOGGED_OFF: 'Authentication failed. Please reload the page, you have probably been logged off.',
7
+ ERROR: 'Error connecting to the service. Please try to reload the page.',
8
+
9
+ // Form validation, labels and placeholders.
10
+ PROMPT_PLACEHOLDER: 'Describe your image idea in detail - include all key elements for best results (max 1024 characters)',
11
+ GENERATE_BUTTON: 'Generate (Ctrl+Enter)',
12
+ NEW_PROMPT_SETTINGS: 'New prompt settings',
13
+ QUOTA_USAGE: 'Images left',
14
+
15
+ // Form field labels.
16
+ VARIATIONS_LABEL: 'Variations',
17
+ SIZE_LABEL: 'Size',
18
+ NEGATIVE_PROMPT_LABEL: 'Negative prompt',
19
+ NEGATIVE_PROMPT_PLACEHOLDER: 'Specify elements to exclude from the image (max 1024 characters)',
20
+ CUSTOM_MODEL_LABEL: 'Model',
21
+ CUSTOM_MODEL_DEFAULT: 'Standard',
22
+ RESTORE_DEFAULTS: 'Restore defaults',
23
+
24
+ // Prompt status messages.
25
+ STATUS_SENDING_REQUEST: 'Sending request...',
26
+ STATUS_GENERATING_IMAGES: 'Generating images (it may take up to ~1 minute)...',
27
+ STATUS_CANCELLED: 'Cancelled',
28
+ STATUS_FAILED: 'Failed',
29
+ PROMPT_TEXT_NOT_PROVIDED: '[ Error: failed to retrieve prompt text ]',
30
+
31
+ // Action labels.
32
+ DISMISS_BUTTON: 'Dismiss',
33
+ VIEW_IMAGE_TOOLTIP: 'View image in a new tab',
34
+ DOWNLOAD_IMAGE_BUTTON: 'Download the image',
35
+ REUSE_BUTTON: 'Reuse',
36
+ LOAD_MORE_BUTTON: 'Load more'
37
+ }