@hachther/akilio-widget 0.6.6 → 1.0.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.
Files changed (42) hide show
  1. package/README.md +359 -16
  2. package/dist/_headers +16 -0
  3. package/dist/akilio-widget.min.js +44 -44
  4. package/dist/assets/EmailIcon.d.ts +2 -0
  5. package/dist/assets/FormIcon.d.ts +2 -0
  6. package/dist/assets/PhoneIcon.d.ts +2 -0
  7. package/dist/components/BrandLogo.d.ts +7 -1
  8. package/dist/components/CartPanel.d.ts +13 -0
  9. package/dist/components/CommerceActions.d.ts +11 -0
  10. package/dist/components/Composer.d.ts +7 -1
  11. package/dist/components/ContactActions.d.ts +8 -0
  12. package/dist/components/ContextualPrompt.d.ts +7 -0
  13. package/dist/components/EmptyState.d.ts +3 -1
  14. package/dist/components/Icons.d.ts +1 -0
  15. package/dist/components/InlineDecisionCard.d.ts +2 -0
  16. package/dist/components/Launcher.d.ts +8 -1
  17. package/dist/components/MessageBubble.d.ts +10 -1
  18. package/dist/components/MessageList.d.ts +1 -1
  19. package/dist/components/ProductCard.d.ts +4 -2
  20. package/dist/components/ProductCards.d.ts +6 -1
  21. package/dist/components/ProductDetail.d.ts +12 -0
  22. package/dist/components/SectionPrompt.d.ts +2 -0
  23. package/dist/components/TextSelectionPrompt.d.ts +2 -0
  24. package/dist/hooks/page-end.d.ts +1 -0
  25. package/dist/hooks/proactive-summary.d.ts +1 -0
  26. package/dist/hooks/response-sound.d.ts +9 -0
  27. package/dist/hooks/storage.d.ts +2 -0
  28. package/dist/index.d.ts +5 -1
  29. package/dist/index.min.js +9343 -6663
  30. package/dist/index.umd.min.cjs +36 -36
  31. package/dist/lib/client.d.ts +23 -2
  32. package/dist/lib/copy.d.ts +8 -2
  33. package/dist/loader.js +46 -0
  34. package/dist/locales/en.d.ts +127 -0
  35. package/dist/locales/fr.d.ts +127 -0
  36. package/dist/locales/index.d.ts +3 -0
  37. package/dist/releases/1.0.0/akilio-widget.min.js +45 -0
  38. package/dist/style.css +2334 -151
  39. package/dist/types.d.ts +554 -20
  40. package/dist/vanilla.d.ts +1 -0
  41. package/dist/widget-manifest.json +8 -0
  42. package/package.json +5 -3
package/README.md CHANGED
@@ -37,6 +37,87 @@ citations, products, and suggestions).
37
37
  Use `streaming={false}` or `data-streaming="false"` to use `POST /api/v1/chat/`
38
38
  for compatibility with an older backend or proxy.
39
39
 
40
+ ## Response notification sound
41
+
42
+ An optional dependency-free chime can notify visitors when the final response
43
+ arrives, including while the browser tab is in the background:
44
+
45
+ ```tsx
46
+ <AkilioWidget
47
+ projectKey='your-project-key'
48
+ enableResponseSound
49
+ responseSoundVolume={0.22}
50
+ />
51
+ ```
52
+
53
+ Standalone scripts use `data-enable-response-sound="true"` and optionally
54
+ `data-response-sound-volume="0.22"`. The sound is disabled by default. It is
55
+ prepared when the visitor sends a question to comply with browser autoplay
56
+ rules, plays only for successful final responses, downloads no audio file, and
57
+ never blocks message delivery if audio is unavailable or denied.
58
+
59
+ ## Launcher position
60
+
61
+ The built-in launcher defaults to the bottom-right corner. It can also be
62
+ placed at the bottom-left or displayed as a vertical tab in the middle of the
63
+ right viewport edge:
64
+
65
+ ```tsx
66
+ <AkilioWidget projectKey='your-project-key' launcherPosition='right-middle' />
67
+ ```
68
+
69
+ Supported values are `bottom-right`, `bottom-left`, and `right-middle`. The
70
+ `right-middle` position automatically opens a full-height right drawer on
71
+ desktop, even when the configured mode is `floating`. On mobile it returns to
72
+ the compact bottom-right launcher and the normal mobile presentation.
73
+
74
+ The standalone equivalent is `data-launcher-position="right-middle"`.
75
+
76
+ ## Adaptive launcher
77
+
78
+ The built-in launcher defaults to Akilio’s compact adaptive experience: it
79
+ appears as an icon, expands once with a page-aware invitation, then stays quiet.
80
+ It can show a single answer-ready signal when a response completes while chat is
81
+ closed. Discovery and dismissal are remembered per project, and motion is
82
+ automatically reduced for visitors who request it.
83
+
84
+ Use the original persistent label while validating the new experience:
85
+
86
+ ```tsx
87
+ <AkilioWidget launcherExperience='static' />
88
+ ```
89
+
90
+ Standalone scripts use `data-launcher-experience="static"`.
91
+
92
+ ## Page-end contextual prompt
93
+
94
+ Documentation pages can show a compact “Ask about this page” input when the
95
+ reader approaches the end of the document:
96
+
97
+ ```tsx
98
+ <AkilioWidget
99
+ projectKey='your-project-key'
100
+ contextualPrompt={{
101
+ enabled: true,
102
+ trigger: 'near-page-end',
103
+ threshold: 320,
104
+ placeholder: 'Ask a question about this page...',
105
+ dismissible: true,
106
+ }}
107
+ />
108
+ ```
109
+
110
+ Submitting opens the main chat and sends the question through its normal
111
+ authenticated streaming flow with the current page URL and title. The prompt
112
+ does not steal focus, stays hidden while chat is open, supports Escape and a
113
+ dismiss button, and does not appear before required privacy consent is granted.
114
+ It uses an `IntersectionObserver` end marker with a scroll fallback.
115
+
116
+ Standalone attributes are `data-contextual-prompt="true"`,
117
+ `data-contextual-prompt-threshold="320"`,
118
+ `data-contextual-prompt-placeholder="Ask about this page..."`, and
119
+ `data-contextual-prompt-dismissible="true"`.
120
+
40
121
  ## Server widget policy
41
122
 
42
123
  The public project configuration may include an authoritative `widget_policy`.
@@ -113,11 +194,7 @@ and is dismissed when the visitor closes it, opens the widget, or presses
113
194
  Escape. Dismissal is stored per project and tour version.
114
195
 
115
196
  ```tsx
116
- <AkilioWidget
117
- enableTour
118
- tourDelayMs={3000}
119
- tourVersion="v1"
120
- />
197
+ <AkilioWidget enableTour tourDelayMs={3000} tourVersion='v1' />
121
198
  ```
122
199
 
123
200
  Standalone attributes are `data-enable-tour`, `data-tour-delay-ms`, and
@@ -132,6 +209,28 @@ The coachmark stays inside the widget’s style boundary, does not darken or blo
132
209
  the host page, does not steal keyboard focus, and respects reduced-motion
133
210
  preferences.
134
211
 
212
+ ## Capability-driven chat actions
213
+
214
+ The widget reads `capabilities.chat` from the public project configuration. An
215
+ enabled `summarize_page` intent that supports the `widget` surface is shown in
216
+ an action tray immediately above the composer when its `page.url` requirement
217
+ is available. Selecting it sends `intent: "summarize_page"` and the current
218
+ page context to the chat endpoint. Unsupported, disabled, wrong-surface, or
219
+ unmet-requirement actions are not displayed. The action tray remains available
220
+ throughout the conversation and is temporarily hidden while a response streams.
221
+ Older API responses without the capability catalog retain the previous
222
+ suggestion behavior.
223
+
224
+ An enabled `contact` intent adds a “Contact the team” action. Contact responses
225
+ can display validated `email`, `phone`, and `form` links from `contact_options`.
226
+ When the response includes a `start_contact_request` action and public
227
+ configuration enables `capabilities.chat.contact_request`, the widget renders
228
+ the advertised fields as an inline contact form. It applies the server-provided
229
+ consent text, submits through the authenticated `/contact-requests/` endpoint,
230
+ prevents submission without required consent, and displays success, progress,
231
+ failure, dark-mode, and mobile states. Repeating the source message remains
232
+ idempotent on the API.
233
+
135
234
  Production React and standalone JavaScript widget for the Akilio backend.
136
235
 
137
236
  ## Included modes
@@ -157,7 +256,11 @@ dist/index.min.js
157
256
  dist/index.umd.min.cjs
158
257
  dist/index.d.ts
159
258
  dist/style.css
259
+ dist/loader.js
260
+ dist/widget-manifest.json
261
+ dist/releases/0.7.0/akilio-widget.min.js
160
262
  dist/akilio-widget.min.js
263
+ dist/_headers
161
264
  ```
162
265
 
163
266
  ## React
@@ -227,10 +330,13 @@ The token resolver is evaluated for every request, so a host application can rot
227
330
 
228
331
  ## Standalone script
229
332
 
333
+ For production websites, use the stable loader URL:
334
+
230
335
  ```html
231
336
  <script
232
337
  data-akilio-widget
233
- src="/assets/akilio-widget.min.js"
338
+ data-cfasync="false"
339
+ src="https://cdn.example.com/widget/loader.js"
234
340
  data-api-base-url="https://api.akilio-ai.com"
235
341
  data-project-key="ask_pk_xxx"
236
342
  data-mode="floating"
@@ -246,19 +352,76 @@ For an inline widget:
246
352
 
247
353
  ```html
248
354
  <div id="support-assistant" style="height: 680px"></div>
249
- <script src="/assets/akilio-widget.min.js" data-auto-init="false"></script>
355
+ <script
356
+ src="https://cdn.example.com/widget/loader.js"
357
+ data-akilio-widget
358
+ data-auto-init="false"
359
+ ></script>
250
360
  <script>
251
- window.Akilio.mount({
252
- target: '#support-assistant',
253
- apiBaseUrl: 'https://api.akilio-ai.com',
254
- projectKey: 'ask_pk_xxx',
255
- mode: 'inline',
256
- colorMode: 'system',
257
- accessToken: 'short-lived-widget-token',
361
+ window.addEventListener('akilio:loader-ready', () => {
362
+ window.Akilio.mount({
363
+ target: '#support-assistant',
364
+ apiBaseUrl: 'https://api.akilio-ai.com',
365
+ projectKey: 'ask_pk_xxx',
366
+ mode: 'inline',
367
+ colorMode: 'system',
368
+ accessToken: 'short-lived-widget-token',
369
+ });
258
370
  });
259
371
  </script>
260
372
  ```
261
373
 
374
+ `loader.js` is revalidated by the browser and loads the immutable release file
375
+ for the current package version. Deploy the complete `dist` directory without
376
+ deleting earlier `dist/releases/*` versions. Each build also keeps
377
+ `akilio-widget.min.js` as a non-immutable compatibility file.
378
+
379
+ For optional Cloudflare Pages deployments, the generated `dist/_headers` requires browser and edge
380
+ revalidation for `loader.js` and `widget-manifest.json`. Files under
381
+ `releases/*` receive a one-year immutable browser and edge cache. Configure
382
+ Cloudflare Browser Cache TTL to **Respect Existing Headers** for this path so a
383
+ cache rule does not keep the loader stale.
384
+
385
+ Cloudflare R2 does not use `_headers`; the R2 deployment command writes the
386
+ equivalent `Cache-Control` metadata on each uploaded object.
387
+
388
+ Publishing a new package version updates only the loader and manifest pointers;
389
+ existing embed snippets do not change and a manual cache purge is not normally
390
+ required. The manifest includes the active version, release path, byte size,
391
+ and SHA-256 digest for deployment verification.
392
+
393
+ ### Deploy to Cloudflare R2
394
+
395
+ Set the R2 bucket name in your local `.env` or shell environment:
396
+
397
+ ```bash
398
+ CLOUDFLARE_R2_BUCKET=your-r2-bucket
399
+ # Optional folder within the bucket:
400
+ CLOUDFLARE_R2_PREFIX=widget
401
+ ```
402
+
403
+ Then validate, build, and publish the CDN assets with:
404
+
405
+ ```bash
406
+ npm run deploy:r2
407
+ ```
408
+
409
+ You can also provide the target without storing it in `.env`:
410
+
411
+ ```bash
412
+ npm run deploy:r2 -- --bucket your-r2-bucket --prefix widget
413
+ ```
414
+
415
+ Wrangler uses an existing local Cloudflare login. In CI, provide
416
+ `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` as protected environment
417
+ variables. Never commit a Cloudflare API token.
418
+
419
+ The deploy command uploads the immutable versioned bundle first, followed by
420
+ the compatibility bundle, manifest, and finally `loader.js`. The versioned file
421
+ receives a one-year immutable cache policy. Stable files receive
422
+ `max-age=0, must-revalidate`, so the CDN entry point can discover each release.
423
+ Add `--dry-run` to build and inspect the target object paths without uploading.
424
+
262
425
  The standalone API can hide the launcher and control the chat imperatively:
263
426
 
264
427
  ```js
@@ -388,7 +551,10 @@ The package itself is now safe to import in an SSR module. The component must st
388
551
 
389
552
  ## Ecommerce responses
390
553
 
391
- When the chat API returns a `commerce` capability, the widget automatically displays its products below the assistant answer. No additional configuration is required.
554
+ When the chat API returns a `commerce` capability, the widget renders a compact
555
+ commerce surface inside the assistant message. Direct matches, recommendations,
556
+ comparisons, compatible products, bundles, upsells, and cross-sells receive
557
+ capability-aware headings and layouts.
392
558
 
393
559
  ```tsx
394
560
  <AkilioWidget
@@ -400,7 +566,42 @@ When the chat API returns a `commerce` capability, the widget automatically disp
400
566
  />
401
567
  ```
402
568
 
403
- Product links open in a new tab. The widget also sends an authenticated commerce event to `/api/v1/commerce/events/` using the response capability (`search` or `recommendation`).
569
+ Product links open through the existing product callback. Trusted
570
+ `commerce.actions` are matched only to products returned in the same response.
571
+ `add_to_cart` proposals require a second explicit confirmation, then use the
572
+ backend's one-time capability-confirmation flow and an idempotency key before a
573
+ cart tool is executed. The provider cart ID is retained in `sessionStorage` for
574
+ subsequent additions during the same browser session. The widget also records
575
+ authenticated commerce events.
576
+
577
+ The widget refreshes `GET /commerce/cart/current/` whenever its chat panel is
578
+ opened. The header cart control shows the returned line items and only exposes a
579
+ checkout link supplied by the backend. A chat response with `cart_state` causes
580
+ an immediate refresh, ensuring the local cart display follows server-side cart
581
+ changes without treating the compact summary as the source of truth.
582
+
583
+ ### `/chat/` response contract
584
+
585
+ `AkilioChatResponse` follows the public OpenAPI `ChatResponse` schema. It
586
+ supports documented conversation context, answer content, citations, commerce,
587
+ suggestions, warnings, market resolution, grounding/refusal flags, escalation,
588
+ contact options, customer actions, agent tools, and intent decisions. Commerce
589
+ products and actions are runtime-validated before they are rendered because the
590
+ OpenAPI schema deliberately leaves those objects extensible.
591
+
592
+ The documented response does not include top-level `images`, `rich_actions`,
593
+ `forms`, or `cart` fields. The widget does not treat those fields as part of its
594
+ public chat contract. Add them to the backend OpenAPI schema before introducing
595
+ matching widget rendering.
596
+
597
+ ## Human handoff
598
+
599
+ When a configured contact action is submitted, the widget sends its consented
600
+ request to `POST /chat/contact-requests/` with the conversation and assistant
601
+ message that offered the action. `POST /chat/handoffs/` is an equivalent backend
602
+ alias. The backend derives the conversation summary, intent, customer context,
603
+ and action context from the validated conversation. The widget renders the
604
+ browser-safe handoff delivery status returned by the API.
404
605
 
405
606
  ## Assistant mode
406
607
 
@@ -431,3 +632,145 @@ For the standalone build:
431
632
  ## Follow-up suggestions
432
633
 
433
634
  When `/api/v1/chat/` returns a `suggestions` array, the widget displays accessible suggestion chips under that assistant response. Selecting a chip sends it as the next user message. Suggestions are retained in local history and restored from server conversation message metadata or structured content when supplied by the API.
635
+
636
+ ## Widget Core (AKI-WIDGET-001)
637
+
638
+ Built-in interface translations cover English and French (including regional
639
+ locales such as `fr-CA`). Supply `locale` and `copy` for other languages; missing
640
+ translations fall back to English. The widget sets its language and direction
641
+ for common right-to-left locales. The standalone script reads the page language.
642
+ The backend receives the requested locale for generated responses.
643
+
644
+ White-label policy hides Akilio marks in the launcher, discovery tip, header,
645
+ messages, and confirmation cards. Default text becomes neutral; explicitly
646
+ configured project titles and copy remain under the integrator's control.
647
+
648
+ History, preferences, configuration, and anonymous continuation credentials use
649
+ browser storage with an in-memory fallback when storage is blocked or full.
650
+ Persistent storage is required to resume after a full page reload; memory-only
651
+ sessions remain usable during the current visit. History is scoped by project
652
+ and locale and retains the latest 60 messages.
653
+
654
+ Mobile forms use 16px inputs and main controls have 44px touch targets, alongside
655
+ viewport sizing and safe-area padding. Physical-device keyboard testing and live
656
+ backend acceptance testing are still required before production sign-off.
657
+
658
+ ## Adding languages
659
+
660
+ Translations live in `src/locales/en.ts` and `src/locales/fr.ts`. To ship a new
661
+ built-in language:
662
+
663
+ 1. Copy `en.ts` to a new file such as `es.ts` and rename the export to `es`.
664
+ 2. Translate every message, the starter questions, and the `whiteLabel` wording.
665
+ Preserve placeholders such as `{seconds}`. Set `direction` to `ltr` or `rtl`.
666
+ 3. Import the pack in `src/locales/index.ts` and add it to `builtInLanguages`.
667
+ 4. Run `npm run typecheck`, `npm test`, and `npm run build`.
668
+
669
+ Each complete pack uses `satisfies AkilioLanguagePack`, so missing translation
670
+ keys are reported by TypeScript when the interface grows.
671
+
672
+ You can also add languages without rebuilding the widget using the `translations`
673
+ option. Partial packs are supported; untranslated text falls back to English:
674
+
675
+ ```tsx
676
+ import type { AkilioTranslations } from '@hachther/akilio-widget';
677
+
678
+ const translations = {
679
+ es: {
680
+ direction: 'ltr',
681
+ messages: {
682
+ title: 'Asistente',
683
+ launcher: 'Hacer una pregunta',
684
+ submit: 'Enviar',
685
+ placeholder: 'Escribe tu pregunta...',
686
+ suggestions: ['¿Cómo puedo contactar con el equipo?'],
687
+ },
688
+ whiteLabel: {
689
+ title: 'Asistente',
690
+ launcher: 'Hacer una pregunta',
691
+ configFailed: 'No se puede cargar la configuración.',
692
+ },
693
+ },
694
+ } satisfies AkilioTranslations;
695
+
696
+ <AkilioWidget
697
+ projectKey='your-project-key'
698
+ locale='es-MX'
699
+ translations={translations}
700
+ />;
701
+ ```
702
+
703
+ The same dictionary works with `window.Akilio.mount({ projectKey: 'your-project-key',
704
+ locale: 'es-MX', translations })`. For script integrations, use programmatic mounting
705
+ with auto-init disabled when supplying dictionaries.
706
+
707
+ Locale matching ignores case and accepts underscores. The fallback order is the
708
+ most specific locale, then its parent language/script, then English. For example,
709
+ `zh-Hant-TW` inherits from `zh-Hant`, `zh`, and `en`. Custom dictionaries override
710
+ built-ins at the same locale. Existing project title, placeholder, and starter
711
+ questions still take precedence; the `copy` prop remains the final text override.
712
+ White-label wording is stored separately from branded defaults. Dictionaries are
713
+ scoped to each widget, so one integration cannot change another's translations.
714
+
715
+ Changing `locale` or `translations` props updates React UI copy. The requested
716
+ locale is still sent to the backend; adding UI translations does not configure
717
+ backend response languages. This framework uses plain text messages and existing
718
+ named placeholders, not ICU plural rules.
719
+
720
+ ## Business branding (AKI-WIDGET-002)
721
+
722
+ ```tsx
723
+ <AkilioWidget
724
+ projectKey='your-project-key'
725
+ agentName='Alex'
726
+ welcomeMessage='Welcome to Acme. How can I help?'
727
+ starterPrompts={['Explore our services', 'Contact the team']}
728
+ launcherPosition='bottom-left'
729
+ theme={{
730
+ logo_url: 'https://example.com/logo.png',
731
+ brand_name: 'Acme',
732
+ primary_color: '#123456',
733
+ primary_dark_color: '#102030',
734
+ primary_foreground_color: '#ffffff',
735
+ }}
736
+ />
737
+ ```
738
+
739
+ Business logos appear in the launcher, discovery tip, header, empty state,
740
+ assistant answers, and human-help confirmation cards. HTTP(S) and relative image
741
+ URLs are accepted. Failed or invalid images fall back to the Akilio mark when
742
+ branding is enabled, or a neutral icon when branding is hidden. Required
743
+ “Powered by Akilio” attribution remains visible with a business logo.
744
+
745
+ Name priority: `copy.title`, `agentName`, host `theme.brand_name`, server
746
+ `theme_config.brand_name`, server `title`, then language defaults. Welcome text
747
+ uses `copy.emptyState`, `welcomeMessage`, server `welcome_message`, then language
748
+ defaults. The welcome message appears before the first conversation message.
749
+
750
+ Before configuration arrives, starter prompts use `starterPrompts` and then
751
+ language suggestions. Once available, `starter_questions` from the public
752
+ configuration endpoint is authoritative, including an explicit empty array that
753
+ hides prompts. Up to four are shown; server-advertised capability actions remain
754
+ independent of starter prompts.
755
+
756
+ Standalone embeds accept the same options via `Akilio.mount()` or these attributes:
757
+
758
+ ```html
759
+ <script
760
+ src="https://your-cdn.example/widget/loader.js"
761
+ data-akilio-widget
762
+ data-project-key="your-project-key"
763
+ data-agent-name="Alex"
764
+ data-brand-name="Acme"
765
+ data-logo-url="https://example.com/logo.png"
766
+ data-welcome-message="Welcome to Acme. How can I help?"
767
+ data-starter-prompts='["Explore our services", "Contact the team"]'
768
+ data-theme='{"primary_color":"#123456"}'
769
+ data-launcher-position="bottom-left"
770
+ ></script>
771
+ ```
772
+
773
+ Use `data-starter-prompts='[]'` to hide prompts. Dedicated logo/name attributes
774
+ override corresponding values in `data-theme`. Malformed prompt JSON falls back
775
+ to configuration defaults. Custom React launchers and external HTML buttons using
776
+ `launcher: 'hidden'` with `open()`, `close()`, or `toggle()` remain supported.
package/dist/_headers ADDED
@@ -0,0 +1,16 @@
1
+ # Cloudflare Pages response headers
2
+ /loader.js
3
+ Cache-Control: public, max-age=0, must-revalidate
4
+ Cloudflare-CDN-Cache-Control: no-cache
5
+
6
+ /widget-manifest.json
7
+ Cache-Control: public, max-age=0, must-revalidate
8
+ Cloudflare-CDN-Cache-Control: no-cache
9
+
10
+ /akilio-widget.min.js
11
+ Cache-Control: public, max-age=0, must-revalidate
12
+ Cloudflare-CDN-Cache-Control: no-cache
13
+
14
+ /releases/*
15
+ Cache-Control: public, max-age=31536000, immutable
16
+ Cloudflare-CDN-Cache-Control: public, max-age=31536000, immutable