@aiden-voice/widget 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -96,10 +96,15 @@ across renewals.
96
96
  })
97
97
  ```
98
98
 
99
- Without it, or when it fails or returns an unusable token, the visitor sees
100
- "This chat session has expired. Reload the page to continue." and their
101
- message is put back in the message box. A page served with an already
102
- expired `sessionToken` also tries `getSessionToken` before giving up.
99
+ Without it, or when it returns an empty or already expired token, the
100
+ visitor sees "This chat session has expired. Reload the page to continue."
101
+ When it throws or takes longer than 15 seconds, the failure may pass, so the
102
+ visitor sees "Something went wrong, please try again." and can send again,
103
+ which asks `getSessionToken` once more. Either way their message is put back
104
+ in the message box. A page served with an already
105
+ expired `sessionToken` also tries `getSessionToken` before giving up, and
106
+ shows no chat button until it answers (at most 15 seconds: a renewal that
107
+ takes longer counts as failed).
103
108
 
104
109
  Branding (`color`, `inkColor`, `accentColor`, `name`) follows one rule in
105
110
  both modes: **a value you pass to `init()` always wins**, anything you leave
@@ -129,6 +134,7 @@ throws synchronously, at call time, if you pass both or neither.
129
134
  | `getSessionToken` | `sessionToken` mode only: a function returning a fresh session token (or a promise of one) from your backend, called when the current one has run out or is refused. Without it an expired session ends the chat until the page is reloaded. Ignored, with a warning, with `publishableKey` or when not a function. | none |
130
135
  | `language` | Language of the widget's own text, as a BCP 47 tag (`'fr'`, `'pt-BR'`, `'zh-CN'`). Without it the widget follows the page's `<html lang>`, then English. See [Language](#language). | the page's language, else `'en'` |
131
136
  | `messages` | Replaces individual built-in strings in the active language, e.g. `{ newConversation: 'Start over' }`. See [Language](#language). | none |
137
+ | `contentLanguage` | The language of the conversation's content (replies, the visitor's messages, and your `greeting`, `title`, `name` and image text), as a BCP 47 tag, so screen readers read it with the right voice. Separate from `language`, which only sets the widget's own text; `language` counts only for a page with no `<html lang>`. See [Language](#language). | the page's `<html lang>`, else `language`, else unknown |
132
138
  | `position` | Launcher/panel corner: `'bottom-right'` or `'bottom-left'`. | `'bottom-right'` |
133
139
  | `color` | Primary accent color (any valid CSS color). Overrides the color configured in whoo-aiden; omit it to use that one. | whoo-aiden's, else `'#4f46e5'` |
134
140
  | `inkColor` | Body text color (chat title and replies). Darkened automatically if needed to stay readable. Overrides the color configured in whoo-aiden; omit it to use that one. | whoo-aiden's, else `'#111111'` |
@@ -142,12 +148,12 @@ throws synchronously, at call time, if you pass both or neither.
142
148
  | `emptyStateImageUrl` | Image (e.g. a brand lockup) shown above the greeting while the conversation is empty. `https:` or `data:image/` only. Removed if it fails to load. | none |
143
149
  | `emptyStateImageAlt` | Alt text for `emptyStateImageUrl`. Set it when the image carries meaning (e.g. your brand name); omit it for a purely decorative image. | none (decorative) |
144
150
  | `showFeedback` | Show thumbs up/down controls under each reply so visitors can rate answers (a thumbs-down opens an optional correction form). | `false` (hidden) |
145
- | `zIndex` | Stacking level of the launcher and panel over your page. The default sits above carousels, sticky headers and most overlays, while leaving room above it for a cookie or consent modal. Lower it if one of your own layers should cover the chat (see the accessibility note below). While the panel fills the screen (a viewport 400px wide or less, or 420px tall or less), it is a modal dialog drawn above everything, whatever `zIndex` says. An invalid value logs a warning and uses the default. | `2147483000` |
151
+ | `zIndex` | Stacking level of the launcher and panel over your page. The default sits above carousels, sticky headers and most overlays, while leaving room above it for a cookie or consent modal. Lower it if one of your own layers should cover the chat (see the accessibility note below). While the panel fills the screen (a viewport 400px wide or less, or 420px tall or less), it is a modal dialog in the browser's top layer: it sits above your page's layers whatever `zIndex` says, and the rest of the page, including page-level accessibility toolbars, can't be reached until it closes. A modal dialog your own page opens afterwards sits above it. An invalid value logs a warning and uses the default. | `2147483000` |
146
152
  | `apiBaseUrl` | Override the whoo-aiden API host the widget talks to. | `'https://api.aidenvoice.com'` |
147
153
 
148
154
  ### When something else already owns the corner
149
155
 
150
- Some third-party overlays (accessibility toolbars, chat or cookie widgets) sit at the maximum possible z-index, `2147483647`, in the same corner. No `zIndex` value can beat that: at an equal level, whichever element comes later in the page wins, and those overlays are usually injected after the widget. Move the widget to the other corner with `position: 'bottom-left'` instead.
156
+ Some third-party overlays (accessibility toolbars, chat or cookie widgets) sit at the maximum possible z-index, `2147483647`, in the same corner. No `zIndex` value can beat that: at an equal level, whichever element comes later in the page wins, and those overlays are usually injected after the widget. Move the widget to the other corner with `position: 'bottom-left'` instead. (None of this applies while the panel fills the screen; see `zIndex`.)
151
157
 
152
158
  ## Language
153
159
 
@@ -157,11 +163,12 @@ The widget's own text (buttons, labels, status and error messages, and what scre
157
163
  - **Regional tags** match their language: `fr-CA` is French, any `pt-*` gets Brazilian Portuguese, any `es-*` gets Latin American Spanish. Simplified Chinese matches `zh`, `zh-CN`, `zh-SG` and `zh-Hans`. Traditional Chinese (`zh-TW`, `zh-HK`, `zh-Hant`) is not supported and falls back to English.
158
164
  - **Decided once, at `init()`.** If your single-page app changes `<html lang>` later, pass `language` (unmount and `init()` again to switch).
159
165
  - **Arabic is right to left:** the panel's layout mirrors (the visitor's messages on the left). The corner set by `position` doesn't change.
160
- - **Not translated:** the agent's replies (the model answers in whatever language it does), and `greeting`, `title` and `name`, which you write in the language you want. Each message's text takes its direction from its own content, so an English reply in an Arabic widget, or the reverse, still reads correctly.
166
+ - **Not translated:** the agent's replies (the model answers in whatever language it does), and `greeting`, `title` and `name`, which you write in the language you want. These are marked with the **content language**, not the widget's: `contentLanguage` if you pass it, else your page's `<html lang>` (any language, not just the eleven), else `language` if the page has no `lang`, else unknown. So on a Dutch page the widget's buttons are English, while replies and your Dutch greeting are read with a Dutch voice.
167
+ - **Direction:** each paragraph and list item of a message takes its direction from its own first letter, so an English reply in an Arabic widget, or the reverse, still reads correctly. A paragraph that starts with a Latin name ("Aiden …") in Arabic text is laid out left to right, as the bidi standard decides.
161
168
  - **The widget marks its own language** (`lang` and `dir` on its root, naming the variant it ships: `pt-BR`, `es-419`, `zh-Hans`), so screen readers pronounce it correctly even on a page in another language.
162
- - **Upgrading from a version before 0.8.0:** a page whose `<html lang>` is one of these languages now gets the widget in that language, where it was English before. Pass `language: 'en'` to keep it in English.
169
+ - **Upgrading from a version before 0.8.0:** a page whose `<html lang>` is one of these languages now gets the widget in that language, where it was English before. Pass `language: 'en'` to keep it in English; replies and your own text keep your page's language for screen readers either way.
163
170
 
164
- **Changing the wording.** `messages` replaces any built-in string in the active language. Write overrides in that language, or set `language` to the language of your overrides. `chatWith` takes `{name}`, the agent's name. Unknown keys, and values that aren't non-blank strings, are ignored with a console warning.
171
+ **Changing the wording.** `messages` replaces any built-in string in the active language. Write overrides in that language, or set `language` to the language of your overrides. `chatWith` takes `{name}`, the agent's name. Unknown keys, and values that aren't non-blank strings (invisible characters such as a zero-width space count as blank), are ignored with a console warning. Overrides are marked with the widget's language: an override in another language (an English brand phrase in a French widget) is read with the widget's voice.
165
172
 
166
173
  ```js
167
174
  window.whooAidenWidget.init({
@@ -562,9 +569,18 @@ The widget distinguishes two deliberately different error tiers:
562
569
  response from the server) show a short, non-technical inline message in the
563
570
  chat panel itself (e.g. "Something went wrong, please try again"), leaving
564
571
  any partial response already rendered in place above it. A session that
565
- runs out while the page is open is renewed (see Usage); only when that is
566
- impossible does the panel say "This chat session has expired. Reload the
567
- page to continue." instead.
572
+ runs out while the page is open is renewed (see Usage); when the session is
573
+ refused the panel says "This chat session has expired. Reload the page to
574
+ continue." instead. When renewing fails for a reason that may pass (offline,
575
+ the key's daily limit, `getSessionToken` throwing or taking over 15
576
+ seconds), it says "Something went wrong, please try again", because a reload
577
+ wouldn't help. Either way the visitor's message goes back into the message
578
+ box, and leaves the transcript (so a retry doesn't show it twice). Closing
579
+ the panel cancels a pending reply silently; the message is handed back only
580
+ if it never reached the server (the widget was still renewing the session).
581
+ A session refused after the widget is already showing (the start-up check
582
+ outlived its 3-second wait) no longer removes it: the panel says the session
583
+ expired instead.
568
584
 
569
585
  How a bad `sessionToken` is caught at start-up, without the widget ever
570
586
  holding a key to check its signature:
@@ -1,10 +1,12 @@
1
1
  import { RefObject } from 'preact';
2
2
  import { WidgetMessages } from '../i18n/messages';
3
- export declare function Composer({ value, onChange, onSend, disabled, inputRef, text, }: {
3
+ export declare function isComposing(event: KeyboardEvent): boolean;
4
+ export declare function Composer({ value, onChange, onSend, disabled, inputRef, text, contentLanguage, }: {
4
5
  value: string;
5
6
  onChange: (value: string) => void;
6
7
  onSend: () => void;
7
8
  disabled: boolean;
8
9
  inputRef?: RefObject<HTMLInputElement | null>;
9
10
  text?: WidgetMessages;
11
+ contentLanguage?: string;
10
12
  }): import('preact').JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { RefObject } from 'preact';
2
2
  import { FeedbackCategory, MessageFeedbackState } from '../controller';
3
3
  import { WidgetMessages } from '../i18n/messages';
4
- export declare function MessageFeedback({ state, containerRef, onThumbsUp, onThumbsDown, onDraftChange, onSubmitDetailed, onCancel, text, }: {
4
+ export declare function MessageFeedback({ state, containerRef, onThumbsUp, onThumbsDown, onDraftChange, onSubmitDetailed, onCancel, text, contentLanguage, }: {
5
5
  state: MessageFeedbackState;
6
6
  containerRef?: RefObject<HTMLElement>;
7
7
  onThumbsUp: () => void;
@@ -10,4 +10,5 @@ export declare function MessageFeedback({ state, containerRef, onThumbsUp, onThu
10
10
  onSubmitDetailed: (correction: string, category: '' | FeedbackCategory) => void;
11
11
  onCancel: () => void;
12
12
  text?: WidgetMessages;
13
+ contentLanguage?: string;
13
14
  }): import('preact').JSX.Element;
@@ -2,7 +2,7 @@ import { FeedbackCategory, Message, MessageFeedbackState } from '../controller';
2
2
  import { WidgetMessages } from '../i18n/messages';
3
3
  type DraftChangeHandler = (messageId: string, correction: string, category: '' | FeedbackCategory) => void;
4
4
  type SubmitHandler = (messageId: string, correction: string, category: '' | FeedbackCategory) => void;
5
- export declare function MessageList({ messages, messageFeedback, announcement, emptyStateImage, agentName, showFeedback, isStreaming, isSlowResponse, onThumbsUp, onThumbsDown, onDraftChange, onSubmitDetailed, onCancelFeedback, text, }: {
5
+ export declare function MessageList({ messages, messageFeedback, announcement, emptyStateImage, agentName, showFeedback, isStreaming, isSlowResponse, onThumbsUp, onThumbsDown, onDraftChange, onSubmitDetailed, onCancelFeedback, text, contentLanguage, languageTag, announcementIsContent, }: {
6
6
  messages: Message[];
7
7
  messageFeedback: Record<string, MessageFeedbackState>;
8
8
  announcement: string | null;
@@ -20,5 +20,8 @@ export declare function MessageList({ messages, messageFeedback, announcement, e
20
20
  onSubmitDetailed: SubmitHandler;
21
21
  onCancelFeedback: (messageId: string) => void;
22
22
  text?: WidgetMessages;
23
+ contentLanguage?: string;
24
+ languageTag?: string;
25
+ announcementIsContent?: boolean;
23
26
  }): import('preact').JSX.Element;
24
27
  export {};
@@ -5,6 +5,7 @@ import { WidgetConfig } from './types';
5
5
  export declare const SLOW_RESPONSE_DELAY_MS = 8000;
6
6
  export declare const BRANDING_LOOKUP_WAIT_MS = 3000;
7
7
  export declare const TOKEN_RENEWAL_MARGIN_MS = 60000;
8
+ export declare const RENEWAL_TIMEOUT_MS = 15000;
8
9
  export interface Message {
9
10
  role: 'user' | 'assistant';
10
11
  content: string;
@@ -30,6 +31,7 @@ export interface WidgetState {
30
31
  accentColor: string | null;
31
32
  inkColor: string | null;
32
33
  announcement: string | null;
34
+ announcementIsContent: boolean;
33
35
  isSlowResponse: boolean;
34
36
  draft: string;
35
37
  }
@@ -41,6 +43,8 @@ export declare class WidgetController {
41
43
  private sessionToken;
42
44
  private sessionTokenExpiresAt;
43
45
  private tokenRenewal;
46
+ private renewalGeneration;
47
+ private sendEpoch;
44
48
  private readonly getSessionToken;
45
49
  private conversationId;
46
50
  private readonly storageIdentifier;
@@ -55,6 +59,7 @@ export declare class WidgetController {
55
59
  readonly text: WidgetMessages;
56
60
  readonly language: SupportedLanguage;
57
61
  readonly languageTag: string;
62
+ readonly contentLanguage: string;
58
63
  readonly dir: 'ltr' | 'rtl';
59
64
  constructor(config: WidgetConfig);
60
65
  getState(): WidgetState;
@@ -76,6 +81,7 @@ export declare class WidgetController {
76
81
  private currentToken;
77
82
  private renewToken;
78
83
  private fetchNewToken;
84
+ private obtainToken;
79
85
  private withSession;
80
86
  }
81
87
  export {};
@@ -20,6 +20,12 @@ export declare function resolveLanguage(option: unknown, pageLang: unknown): {
20
20
  tag: string;
21
21
  dir: 'ltr' | 'rtl';
22
22
  };
23
+ export declare function resolveContentLanguage(option: unknown, pageLang: unknown, languageOption?: unknown): string;
24
+ export declare function templateParts(template: string): {
25
+ before: string;
26
+ after: string;
27
+ hasName: boolean;
28
+ };
23
29
  export declare function format(template: string, values: {
24
30
  name: string;
25
31
  }): string;
@@ -25,6 +25,7 @@ export interface WidgetMessages {
25
25
  conversationCleared: string;
26
26
  genericError: string;
27
27
  sessionExpired: string;
28
+ opensInNewTab: string;
28
29
  }
29
30
  export declare const ENGLISH: WidgetMessages;
30
31
  export declare const MESSAGE_KEYS: (keyof WidgetMessages)[];
@@ -1,2 +1,6 @@
1
1
  import { ComponentChildren } from 'preact';
2
- export declare function renderMarkdown(source: string): ComponentChildren;
2
+ export interface RenderOptions {
3
+ linkCue?: string;
4
+ linkCueLang?: string;
5
+ }
6
+ export declare function renderMarkdown(source: string, options?: RenderOptions): ComponentChildren;
package/dist/types.d.ts CHANGED
@@ -94,6 +94,15 @@ export interface WidgetConfig {
94
94
  * Unknown keys and blank or non-string values are ignored with a warning.
95
95
  */
96
96
  messages?: Partial<WidgetMessages>;
97
+ /**
98
+ * The language of the conversation's content (replies, the visitor's
99
+ * messages, and your greeting, title, name and image text), as a BCP 47
100
+ * tag, so screen readers read it with the right voice. Defaults to the
101
+ * page's <html lang>, then to `language` when the page has none, else
102
+ * unknown. Separate from `language`, which only sets the widget's own
103
+ * buttons and messages.
104
+ */
105
+ contentLanguage?: string;
97
106
  position?: 'bottom-right' | 'bottom-left';
98
107
  /**
99
108
  * Primary accent color. Overrides the color configured in whoo-aiden, in
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const WIDGET_VERSION = "0.8.0";
1
+ export declare const WIDGET_VERSION = "0.9.0";