@salla.sa/ui-ai-kit-core 1.0.0 → 1.1.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 (148) hide show
  1. package/dist/cjs/ai-card.cjs.entry.js +2 -2
  2. package/dist/cjs/ai-chat-container.cjs.entry.js +84 -57
  3. package/dist/cjs/ai-chat-header.cjs.entry.js +27 -17
  4. package/dist/cjs/ai-chat-message.cjs.entry.js +1456 -21
  5. package/dist/cjs/ai-conversation-list.cjs.entry.js +80 -0
  6. package/dist/cjs/ai-conversation-summary.cjs.entry.js +33 -0
  7. package/dist/cjs/ai-icon.cjs.entry.js +2 -2
  8. package/dist/cjs/ai-link.cjs.entry.js +4 -4
  9. package/dist/cjs/ai-loading.cjs.entry.js +35 -22
  10. package/dist/cjs/ai-message-input.cjs.entry.js +48 -15
  11. package/dist/cjs/ai-rating.cjs.entry.js +2 -2
  12. package/dist/cjs/ai-route-decision.cjs.entry.js +48 -0
  13. package/dist/cjs/ai-suggestion.cjs.entry.js +4 -4
  14. package/dist/cjs/ai-voice-input.cjs.entry.js +62 -14
  15. package/dist/cjs/{icon-registry-dmfLA-Dj.js → icon-registry-BKb9-2Nt.js} +24 -0
  16. package/dist/cjs/{index-DLJcLHFH.js → index-BkNg07SW.js} +1 -1
  17. package/dist/cjs/loader.cjs.js +2 -2
  18. package/dist/cjs/ui-ai-kit.cjs.js +2 -2
  19. package/dist/collection/collection-manifest.json +3 -0
  20. package/dist/collection/components/ai-card/ai-card.css +5 -8
  21. package/dist/collection/components/ai-chat-container/ai-chat-container.css +17 -14
  22. package/dist/collection/components/ai-chat-container/ai-chat-container.js +125 -53
  23. package/dist/collection/components/ai-chat-header/ai-chat-header.css +50 -17
  24. package/dist/collection/components/ai-chat-header/ai-chat-header.js +50 -15
  25. package/dist/collection/components/ai-chat-message/ai-chat-message.css +47 -38
  26. package/dist/collection/components/ai-chat-message/ai-chat-message.js +68 -18
  27. package/dist/collection/components/ai-conversation-list/ai-conversation-list.css +196 -0
  28. package/dist/collection/components/ai-conversation-list/ai-conversation-list.js +176 -0
  29. package/dist/collection/components/ai-conversation-summary/ai-conversation-summary.css +103 -0
  30. package/dist/collection/components/ai-conversation-summary/ai-conversation-summary.js +118 -0
  31. package/dist/collection/components/ai-icon/ai-icon.js +1 -1
  32. package/dist/collection/components/ai-link/ai-link.css +3 -7
  33. package/dist/collection/components/ai-link/ai-link.js +1 -1
  34. package/dist/collection/components/ai-loading/ai-loading.css +149 -20
  35. package/dist/collection/components/ai-loading/ai-loading.js +100 -23
  36. package/dist/collection/components/ai-message-input/ai-message-input.css +41 -37
  37. package/dist/collection/components/ai-message-input/ai-message-input.js +100 -19
  38. package/dist/collection/components/ai-rating/ai-rating.css +8 -14
  39. package/dist/collection/components/ai-route-decision/ai-route-decision.css +132 -0
  40. package/dist/collection/components/ai-route-decision/ai-route-decision.js +195 -0
  41. package/dist/collection/components/ai-suggestion/ai-suggestion.css +5 -11
  42. package/dist/collection/components/ai-suggestion/ai-suggestion.js +2 -2
  43. package/dist/collection/components/ai-voice-input/ai-voice-input.css +26 -21
  44. package/dist/collection/components/ai-voice-input/ai-voice-input.js +103 -13
  45. package/dist/collection/utils/icon-registry.js +24 -0
  46. package/dist/components/ai-card.js +1 -1
  47. package/dist/components/ai-chat-container.js +1 -1
  48. package/dist/components/ai-chat-header.js +1 -1
  49. package/dist/components/ai-chat-message.js +2 -1
  50. package/dist/components/ai-conversation-list.d.ts +11 -0
  51. package/dist/components/ai-conversation-list.js +1 -0
  52. package/dist/components/ai-conversation-summary.d.ts +11 -0
  53. package/dist/components/ai-conversation-summary.js +1 -0
  54. package/dist/components/ai-icon.js +1 -1
  55. package/dist/components/ai-link.js +1 -1
  56. package/dist/components/ai-loading.js +1 -1
  57. package/dist/components/ai-message-input.js +1 -1
  58. package/dist/components/ai-rating.js +1 -1
  59. package/dist/components/ai-route-decision.d.ts +11 -0
  60. package/dist/components/ai-route-decision.js +1 -0
  61. package/dist/components/ai-suggestion.js +1 -1
  62. package/dist/components/ai-voice-input.js +1 -1
  63. package/dist/components/index.js +1 -1
  64. package/dist/components/p-DCr8F_XV.js +1 -0
  65. package/dist/components/p-DnO4dikr.js +1 -0
  66. package/dist/components/{p-CY6emva2.js → p-Dr2tAPV7.js} +1 -1
  67. package/dist/{ui-ai-kit/p-DYv5ef4M.js → components/p-SJZ6Ujn9.js} +1 -1
  68. package/dist/esm/ai-card.entry.js +2 -2
  69. package/dist/esm/ai-chat-container.entry.js +84 -57
  70. package/dist/esm/ai-chat-header.entry.js +27 -17
  71. package/dist/esm/ai-chat-message.entry.js +1456 -21
  72. package/dist/esm/ai-conversation-list.entry.js +78 -0
  73. package/dist/esm/ai-conversation-summary.entry.js +31 -0
  74. package/dist/esm/ai-icon.entry.js +2 -2
  75. package/dist/esm/ai-link.entry.js +4 -4
  76. package/dist/esm/ai-loading.entry.js +35 -22
  77. package/dist/esm/ai-message-input.entry.js +48 -15
  78. package/dist/esm/ai-rating.entry.js +2 -2
  79. package/dist/esm/ai-route-decision.entry.js +46 -0
  80. package/dist/esm/ai-suggestion.entry.js +4 -4
  81. package/dist/esm/ai-voice-input.entry.js +62 -14
  82. package/dist/esm/{icon-registry-DYv5ef4M.js → icon-registry-SJZ6Ujn9.js} +24 -0
  83. package/dist/esm/{index-7hrZ8FOQ.js → index-B0yIzgh4.js} +1 -1
  84. package/dist/esm/loader.js +3 -3
  85. package/dist/esm/ui-ai-kit.js +3 -3
  86. package/dist/types/components/ai-chat-container/ai-chat-container.d.ts +11 -1
  87. package/dist/types/components/ai-chat-header/ai-chat-header.d.ts +6 -1
  88. package/dist/types/components/ai-conversation-list/ai-conversation-list.d.ts +24 -0
  89. package/dist/types/components/ai-conversation-summary/ai-conversation-summary.d.ts +12 -0
  90. package/dist/types/components/ai-loading/ai-loading.d.ts +12 -6
  91. package/dist/types/components/ai-message-input/ai-message-input.d.ts +17 -3
  92. package/dist/types/components/ai-route-decision/ai-route-decision.d.ts +21 -0
  93. package/dist/types/components/ai-voice-input/ai-voice-input.d.ts +7 -0
  94. package/dist/types/components.d.ts +333 -9
  95. package/dist/types/index.d.ts +2 -0
  96. package/dist/types/utils/icon-registry.d.ts +1 -1
  97. package/dist/ui-ai-kit/p-21c4fc1f.entry.js +1 -0
  98. package/dist/ui-ai-kit/p-2955439f.entry.js +1 -0
  99. package/dist/ui-ai-kit/p-5c9e9822.entry.js +1 -0
  100. package/dist/ui-ai-kit/p-5caf1c38.entry.js +1 -0
  101. package/dist/ui-ai-kit/p-6d3505e9.entry.js +1 -0
  102. package/dist/ui-ai-kit/p-74c5c83f.entry.js +1 -0
  103. package/dist/ui-ai-kit/p-87e9739b.entry.js +1 -0
  104. package/dist/ui-ai-kit/p-B0yIzgh4.js +2 -0
  105. package/dist/{components/p-DYv5ef4M.js → ui-ai-kit/p-SJZ6Ujn9.js} +1 -1
  106. package/dist/ui-ai-kit/p-a099fcfb.entry.js +1 -0
  107. package/dist/ui-ai-kit/p-a9e4eaef.entry.js +1 -0
  108. package/dist/ui-ai-kit/p-b28af13a.entry.js +1 -0
  109. package/dist/ui-ai-kit/p-d1bb1ad0.entry.js +1 -0
  110. package/dist/ui-ai-kit/p-eb0c7e7a.entry.js +1 -0
  111. package/dist/ui-ai-kit/{p-455daa17.entry.js → p-eec6f083.entry.js} +1 -1
  112. package/dist/ui-ai-kit/p-ef07638f.entry.js +2 -0
  113. package/dist/ui-ai-kit/ui-ai-kit.css +1 -1
  114. package/dist/ui-ai-kit/ui-ai-kit.esm.js +1 -1
  115. package/package.json +5 -14
  116. package/dist/collection/components/ai-card/ai-card.stories.js +0 -52
  117. package/dist/collection/components/ai-chat-container/ai-chat-container.stories.js +0 -160
  118. package/dist/collection/components/ai-chat-header/ai-chat-header.stories.js +0 -138
  119. package/dist/collection/components/ai-chat-message/ai-chat-message.stories.js +0 -164
  120. package/dist/collection/components/ai-link/ai-link.stories.js +0 -79
  121. package/dist/collection/components/ai-loading/ai-loading.stories.js +0 -145
  122. package/dist/collection/components/ai-message-input/ai-message-input.stories.js +0 -125
  123. package/dist/collection/components/ai-rating/ai-rating.stories.js +0 -78
  124. package/dist/collection/components/ai-suggestion/ai-suggestion.stories.js +0 -62
  125. package/dist/collection/components/ai-voice-input/ai-voice-input.stories.js +0 -118
  126. package/dist/components/p-CWjXxYJI.js +0 -1
  127. package/dist/types/components/ai-card/ai-card.stories.d.ts +0 -7
  128. package/dist/types/components/ai-chat-container/ai-chat-container.stories.d.ts +0 -7
  129. package/dist/types/components/ai-chat-header/ai-chat-header.stories.d.ts +0 -8
  130. package/dist/types/components/ai-chat-message/ai-chat-message.stories.d.ts +0 -10
  131. package/dist/types/components/ai-link/ai-link.stories.d.ts +0 -8
  132. package/dist/types/components/ai-loading/ai-loading.stories.d.ts +0 -10
  133. package/dist/types/components/ai-message-input/ai-message-input.stories.d.ts +0 -13
  134. package/dist/types/components/ai-rating/ai-rating.stories.d.ts +0 -8
  135. package/dist/types/components/ai-suggestion/ai-suggestion.stories.d.ts +0 -8
  136. package/dist/types/components/ai-voice-input/ai-voice-input.stories.d.ts +0 -9
  137. package/dist/ui-ai-kit/p-11facfad.entry.js +0 -1
  138. package/dist/ui-ai-kit/p-128a2ed4.entry.js +0 -1
  139. package/dist/ui-ai-kit/p-227bdb8f.entry.js +0 -1
  140. package/dist/ui-ai-kit/p-56163e8c.entry.js +0 -1
  141. package/dist/ui-ai-kit/p-6d21d0fd.entry.js +0 -1
  142. package/dist/ui-ai-kit/p-6ddcd77b.entry.js +0 -1
  143. package/dist/ui-ai-kit/p-7hrZ8FOQ.js +0 -2
  144. package/dist/ui-ai-kit/p-8e90143e.entry.js +0 -1
  145. package/dist/ui-ai-kit/p-9938c277.entry.js +0 -1
  146. package/dist/ui-ai-kit/p-dc5b4a7f.entry.js +0 -1
  147. package/dist/ui-ai-kit/p-fb1702de.entry.js +0 -1
  148. package/readme.md +0 -111
@@ -13,32 +13,89 @@ export class ChatContainer {
13
13
  showWatermark = false;
14
14
  /** Height of the panel when position='float' */
15
15
  floatHeight = '600px';
16
+ /** Color theme for the panel */
17
+ theme = 'light';
16
18
  isMobile = false;
19
+ floatLeft = '24px';
20
+ floatTop = '24px';
17
21
  containerRef;
22
+ messagesAreaRef;
18
23
  dragState = null;
19
- resizeObserver;
24
+ // Named arrow so we can reliably add/remove it without leaking
25
+ dragListener = (e) => {
26
+ const container = this.containerRef;
27
+ if (!container)
28
+ return;
29
+ const { clientX, clientY } = e.detail;
30
+ const rect = container.getBoundingClientRect();
31
+ this.dragState = {
32
+ startX: clientX,
33
+ startY: clientY,
34
+ initLeft: rect.left,
35
+ initTop: rect.top,
36
+ };
37
+ // Capture current rendered position into state so subsequent re-renders don't jump
38
+ this.floatLeft = `${rect.left}px`;
39
+ this.floatTop = `${rect.top}px`;
40
+ const onMove = (me) => {
41
+ if (!this.dragState)
42
+ return;
43
+ const dx = me.clientX - this.dragState.startX;
44
+ const dy = me.clientY - this.dragState.startY;
45
+ let newLeft = this.dragState.initLeft + dx;
46
+ let newTop = this.dragState.initTop + dy;
47
+ const vw = window.innerWidth;
48
+ const vh = window.innerHeight;
49
+ const w = container.offsetWidth;
50
+ const h = container.offsetHeight;
51
+ newLeft = Math.max(0, Math.min(newLeft, vw - w));
52
+ newTop = Math.max(0, Math.min(newTop, vh - h));
53
+ this.floatLeft = `${newLeft}px`;
54
+ this.floatTop = `${newTop}px`;
55
+ };
56
+ const onUp = () => {
57
+ this.dragState = null;
58
+ document.removeEventListener('pointermove', onMove);
59
+ document.removeEventListener('pointerup', onUp);
60
+ };
61
+ document.addEventListener('pointermove', onMove);
62
+ document.addEventListener('pointerup', onUp);
63
+ };
64
+ constructor() {
65
+ // Bind so window.removeEventListener can match the reference
66
+ this.checkMobile = this.checkMobile.bind(this);
67
+ }
20
68
  componentWillLoad() {
21
69
  this.checkMobile();
22
70
  }
23
71
  componentDidLoad() {
24
72
  if (typeof window !== 'undefined') {
25
- this.resizeObserver = new ResizeObserver(() => {
26
- this.checkMobile();
27
- });
28
- this.resizeObserver.observe(document.body);
73
+ window.addEventListener('resize', this.checkMobile);
29
74
  if (this.position === 'float') {
30
75
  this.setupDrag();
31
76
  }
32
77
  }
33
78
  }
79
+ isOpenChanged(newVal) {
80
+ if (newVal && this.autoScroll && this.messagesAreaRef) {
81
+ requestAnimationFrame(() => {
82
+ this.messagesAreaRef.scrollTop = this.messagesAreaRef.scrollHeight;
83
+ });
84
+ }
85
+ }
34
86
  positionChanged(newVal) {
35
87
  if (newVal === 'float') {
36
88
  this.setupDrag();
37
89
  }
38
90
  }
39
91
  disconnectedCallback() {
40
- if (this.resizeObserver) {
41
- this.resizeObserver.disconnect();
92
+ window.removeEventListener('resize', this.checkMobile);
93
+ this.el.removeEventListener('headerDragStart', this.dragListener);
94
+ }
95
+ /** Programmatically scroll the messages area to the bottom */
96
+ async scrollToBottom() {
97
+ if (this.messagesAreaRef) {
98
+ this.messagesAreaRef.scrollTop = this.messagesAreaRef.scrollHeight;
42
99
  }
43
100
  }
44
101
  checkMobile() {
@@ -47,50 +104,9 @@ export class ChatContainer {
47
104
  }
48
105
  }
49
106
  setupDrag() {
50
- const container = this.containerRef;
51
- if (!container)
52
- return;
53
- // Listen on the host element so composed events from ai-chat-header's shadow DOM
54
- // are received. (composedPath() hides cross-shadow-root elements from shadow listeners.)
55
- this.el.addEventListener('headerDragStart', (e) => {
56
- const { clientX, clientY } = e.detail;
57
- const rect = container.getBoundingClientRect();
58
- this.dragState = {
59
- startX: clientX,
60
- startY: clientY,
61
- initLeft: rect.left,
62
- initTop: rect.top,
63
- };
64
- // Switch from bottom/right anchoring to top/left so we can move freely
65
- container.style.right = 'auto';
66
- container.style.bottom = 'auto';
67
- container.style.left = `${rect.left}px`;
68
- container.style.top = `${rect.top}px`;
69
- const onMove = (me) => {
70
- if (!this.dragState)
71
- return;
72
- const dx = me.clientX - this.dragState.startX;
73
- const dy = me.clientY - this.dragState.startY;
74
- let newLeft = this.dragState.initLeft + dx;
75
- let newTop = this.dragState.initTop + dy;
76
- // Clamp to viewport edges
77
- const vw = window.innerWidth;
78
- const vh = window.innerHeight;
79
- const w = container.offsetWidth;
80
- const h = container.offsetHeight;
81
- newLeft = Math.max(0, Math.min(newLeft, vw - w));
82
- newTop = Math.max(0, Math.min(newTop, vh - h));
83
- container.style.left = `${newLeft}px`;
84
- container.style.top = `${newTop}px`;
85
- };
86
- const onUp = () => {
87
- this.dragState = null;
88
- document.removeEventListener('pointermove', onMove);
89
- document.removeEventListener('pointerup', onUp);
90
- };
91
- document.addEventListener('pointermove', onMove);
92
- document.addEventListener('pointerup', onUp);
93
- });
107
+ // Remove any existing listener before adding to prevent accumulation
108
+ this.el.removeEventListener('headerDragStart', this.dragListener);
109
+ this.el.addEventListener('headerDragStart', this.dragListener);
94
110
  }
95
111
  getContainerClasses() {
96
112
  const classes = ['chat-container', this.position];
@@ -110,14 +126,24 @@ export class ChatContainer {
110
126
  if (this.position === 'float') {
111
127
  style.width = this.width;
112
128
  style.height = this.floatHeight;
129
+ style.left = this.floatLeft;
130
+ style.top = this.floatTop;
113
131
  }
114
132
  else {
115
133
  style.width = this.width;
116
134
  }
117
135
  return style;
118
136
  }
137
+ isDark() {
138
+ if (this.theme === 'dark')
139
+ return true;
140
+ if (this.theme === 'auto' && typeof window !== 'undefined') {
141
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
142
+ }
143
+ return false;
144
+ }
119
145
  render() {
120
- return (h(Host, { key: '5048010bf45a26985c92bfb9a6caf01d8aa938c0' }, h("div", { key: '08491cff2361071bc0635914cc665281b80321ac', class: this.getContainerClasses(), style: this.getContainerStyle(), ref: el => (this.containerRef = el) }, this.showWatermark && (h("div", { key: '366140a61929689fc522dddfb1485bbd4d2cbef9', class: "watermark" }, h("ai-icon", { key: '5e210929feeb519889344385fa2fb75373ff8fed', name: "watermark", size: 133 }))), h("slot", { key: '57eef4d0c273009feeeb4f03400e05352acdf6cd', name: "header" }), h("div", { key: '15b14f49819f886ce1d56de87c770bb7241dcd77', class: "messages-area" }, h("slot", { key: '050cd0fde5bc3de75df0747031dc84214c125d5d' })), h("slot", { key: '195ab33291da775299e0d7c0c53df92fcf46a4ca', name: "footer" }))));
146
+ return (h(Host, { key: 'dd656127f6974b6eef12f64c78c072f2c1f92a6a', class: { dark: this.isDark() } }, h("div", { key: '0eb43bc5c8b80e5576146f01998e5279eb860162', class: this.getContainerClasses(), style: this.getContainerStyle(), ref: el => (this.containerRef = el) }, this.showWatermark && (h("div", { key: '363046f0e73ab7aa0209d5f41562031fb34d6c2d', class: "watermark" }, h("ai-icon", { key: '1f3c26ba6a145f8532f6e1a40ea344249e8e5b43', name: "watermark", size: 133 }))), h("slot", { key: '3ec10548660ff721c094be36a15a152fdf6dac78', name: "header" }), h("div", { key: '9babb87041287ac400322b910aa64f7a7b1743a5', class: "messages-area", ref: el => (this.messagesAreaRef = el) }, h("slot", { key: '937c0c0bd675e5a619d46883e7c1f890754aae4b' })), h("slot", { key: '0982b8bc96537c6cecca757926d5849ba386bd69', name: "footer" }))));
121
147
  }
122
148
  static get is() { return "ai-chat-container"; }
123
149
  static get encapsulation() { return "shadow"; }
@@ -252,17 +278,63 @@ export class ChatContainer {
252
278
  "reflect": false,
253
279
  "attribute": "float-height",
254
280
  "defaultValue": "'600px'"
281
+ },
282
+ "theme": {
283
+ "type": "string",
284
+ "mutable": false,
285
+ "complexType": {
286
+ "original": "'light' | 'dark' | 'auto'",
287
+ "resolved": "\"auto\" | \"dark\" | \"light\"",
288
+ "references": {}
289
+ },
290
+ "required": false,
291
+ "optional": false,
292
+ "docs": {
293
+ "tags": [],
294
+ "text": "Color theme for the panel"
295
+ },
296
+ "getter": false,
297
+ "setter": false,
298
+ "reflect": false,
299
+ "attribute": "theme",
300
+ "defaultValue": "'light'"
255
301
  }
256
302
  };
257
303
  }
258
304
  static get states() {
259
305
  return {
260
- "isMobile": {}
306
+ "isMobile": {},
307
+ "floatLeft": {},
308
+ "floatTop": {}
309
+ };
310
+ }
311
+ static get methods() {
312
+ return {
313
+ "scrollToBottom": {
314
+ "complexType": {
315
+ "signature": "() => Promise<void>",
316
+ "parameters": [],
317
+ "references": {
318
+ "Promise": {
319
+ "location": "global",
320
+ "id": "global::Promise"
321
+ }
322
+ },
323
+ "return": "Promise<void>"
324
+ },
325
+ "docs": {
326
+ "text": "Programmatically scroll the messages area to the bottom",
327
+ "tags": []
328
+ }
329
+ }
261
330
  };
262
331
  }
263
332
  static get elementRef() { return "el"; }
264
333
  static get watchers() {
265
334
  return [{
335
+ "propName": "isOpen",
336
+ "methodName": "isOpenChanged"
337
+ }, {
266
338
  "propName": "position",
267
339
  "methodName": "positionChanged"
268
340
  }];
@@ -1,6 +1,5 @@
1
1
  :host {
2
2
  display: block;
3
- font-family: var(--ai-font-family, "PingARLT", sans-serif);
4
3
  }
5
4
 
6
5
  /* ── Container ──────────────────────────────────────────────────── */
@@ -9,16 +8,12 @@
9
8
  align-items: center;
10
9
  gap: 8px;
11
10
  padding: 16px;
12
- background: var(--ai-bg-card, white);
13
- border-bottom: 1px solid var(--ai-border-light, #f4f4f4);
11
+ background: var(--ai-bg-card);
12
+ border-bottom: 1px solid var(--ai-border-light);
14
13
  width: 100%;
15
14
  box-sizing: border-box;
16
15
  }
17
16
 
18
- .header-container {
19
- direction: rtl;
20
- }
21
-
22
17
  /* ── Action & Back Buttons ──────────────────────────────────────── */
23
18
  .drag-btn {
24
19
  cursor: grab;
@@ -37,7 +32,7 @@
37
32
  display: flex;
38
33
  align-items: center;
39
34
  justify-content: center;
40
- background: var(--ai-bg-card, white);
35
+ background: var(--ai-bg-card);
41
36
  border: none;
42
37
  border-radius: 8px;
43
38
  cursor: pointer;
@@ -48,7 +43,7 @@
48
43
 
49
44
  .action-btn:hover,
50
45
  .back-btn:hover {
51
- background: var(--ai-bg-surface, #f9fafb);
46
+ background: var(--ai-bg-surface);
52
47
  }
53
48
 
54
49
  .action-btn:focus-visible,
@@ -59,7 +54,7 @@
59
54
 
60
55
  .action-btn:active,
61
56
  .back-btn:active {
62
- background: var(--ai-bg-surface, #f3f4f6);
57
+ background: var(--ai-bg-surface);
63
58
  }
64
59
 
65
60
  /* ── Actions Group ──────────────────────────────────────────────── */
@@ -110,7 +105,7 @@
110
105
  .title {
111
106
  font-size: 16px;
112
107
  font-weight: 700;
113
- color: var(--ai-text-primary, #444444);
108
+ color: var(--ai-text-primary);
114
109
  white-space: nowrap;
115
110
  line-height: normal;
116
111
  min-width: 0;
@@ -130,15 +125,30 @@
130
125
  width: 40px;
131
126
  height: 40px;
132
127
  border-radius: 9999px;
133
- border: 1px solid var(--ai-border-default, #eeeeee);
128
+ border: 1px solid var(--ai-border-default);
134
129
  object-fit: cover;
135
130
  display: block;
136
131
  }
137
132
 
133
+ .avatar-fallback {
134
+ width: 40px;
135
+ height: 40px;
136
+ border-radius: 9999px;
137
+ border: 1px solid var(--ai-border-default);
138
+ background: var(--ai-bg-surface);
139
+ color: var(--ai-text-primary);
140
+ font-size: 16px;
141
+ font-weight: 600;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ flex-shrink: 0;
146
+ }
147
+
138
148
  .online-dot {
139
149
  position: absolute;
140
150
  bottom: 0;
141
- left: 0;
151
+ inset-inline-end: 0;
142
152
  width: 10px;
143
153
  height: 10px;
144
154
  display: inline-flex;
@@ -147,20 +157,36 @@
147
157
  color: var(--ai-bg-card); /* used as stroke to match card bg in both themes */
148
158
  }
149
159
 
160
+ /* Status indicator color variants */
161
+ .online-dot.status-online {
162
+ color: #22c55e; /* green */
163
+ }
164
+
165
+ .online-dot.status-offline {
166
+ color: #9ca3af; /* gray */
167
+ }
168
+
169
+ .online-dot.status-busy {
170
+ color: #ef4444; /* red */
171
+ }
172
+
173
+ .online-dot.status-away {
174
+ color: #f59e0b; /* amber */
175
+ }
176
+
150
177
  /* ── Text Block (human mode) ────────────────────────────────────── */
151
178
  .text-block {
152
179
  display: flex;
153
180
  flex-direction: column;
154
- align-items: flex-end;
181
+ align-items: flex-start;
155
182
  gap: 0;
156
183
  flex-shrink: 0;
157
184
  }
158
185
 
159
-
160
186
  .agent-name {
161
187
  font-size: 14px;
162
188
  font-weight: 500;
163
- color: var(--ai-text-primary, #333333);
189
+ color: var(--ai-text-primary);
164
190
  line-height: 20px;
165
191
  white-space: nowrap;
166
192
  }
@@ -168,7 +194,7 @@
168
194
  .agent-status {
169
195
  font-size: 14px;
170
196
  font-weight: 400;
171
- color: var(--ai-text-secondary, #737373);
197
+ color: var(--ai-text-secondary);
172
198
  line-height: 20px;
173
199
  white-space: nowrap;
174
200
  }
@@ -184,3 +210,10 @@
184
210
  .icon-wrap svg {
185
211
  display: block;
186
212
  }
213
+
214
+ /* ── Directional icon flip (back-btn arrow points right in RTL = "back";
215
+ in LTR it must flip to point left) ──────────────────────────────────── */
216
+ :host-context([dir='ltr']) .back-btn .icon-wrap,
217
+ :host([dir='ltr']) .back-btn .icon-wrap {
218
+ transform: scaleX(-1);
219
+ }
@@ -14,13 +14,16 @@ export class AiChatHeader {
14
14
  /** Human mode: show the back button */
15
15
  showBack = true;
16
16
  isDraggable = false;
17
+ /** Human mode: status indicator variant */
18
+ statusIndicator = 'online';
19
+ avatarError = false;
17
20
  /** Cancel / close button */
18
21
  closeClick;
19
22
  /** Pencil-edit button (agent mode) */
20
23
  editClick;
21
24
  /** Title / chevron click → open conversation list (agent mode) */
22
25
  dropdownClick;
23
- /** Chevron-down button (human mode) */
26
+ /** More options button */
24
27
  moreClick;
25
28
  /** Back-arrow button (human mode) */
26
29
  backClick;
@@ -36,28 +39,35 @@ export class AiChatHeader {
36
39
  const svg = `<svg width="${width}" height="${height}" viewBox="${icon.viewBox}" fill="none" xmlns="http://www.w3.org/2000/svg">${icon.content}</svg>`;
37
40
  return h("span", { class: "icon-wrap", innerHTML: svg });
38
41
  }
42
+ renderAvatar() {
43
+ const fallbackLetter = this.agentName ? this.agentName.charAt(0) : '?';
44
+ if (!this.agentAvatar || this.avatarError) {
45
+ return h("div", { class: "avatar-fallback" }, fallbackLetter);
46
+ }
47
+ return (h("img", { class: "avatar", src: this.agentAvatar, alt: this.agentName, onError: () => { this.avatarError = true; } }));
48
+ }
49
+ renderDragBtn() {
50
+ return this.isDraggable && (h("button", { class: "action-btn drag-btn", "aria-label": "\u0633\u062D\u0628 / Drag", onPointerDown: (e) => {
51
+ e.preventDefault();
52
+ this.headerDragStart.emit({ clientX: e.clientX, clientY: e.clientY });
53
+ } }, this.renderIcon('drag', 11, 15)));
54
+ }
39
55
  renderAgentMode() {
40
56
  return [
41
- this.isDraggable && (h("button", { class: "action-btn drag-btn", onPointerDown: (e) => {
42
- e.preventDefault();
43
- this.headerDragStart.emit({ clientX: e.clientX, clientY: e.clientY });
44
- } }, this.renderIcon('drag', 11, 15))),
45
- h("div", { class: "content agent" }, h("span", { class: "title" }, this.conversation), h("button", { class: "dropdown-trigger", onClick: () => this.dropdownClick.emit() }, this.renderIcon('chevron-down', 24, 24))),
46
- h("div", { class: "actions" }, h("button", { class: "action-btn", onClick: () => this.editClick.emit() }, this.renderIcon('pencil-edit', 22, 22)), h("button", { class: "action-btn", onClick: () => this.moreClick.emit() }, this.renderIcon('hand', 22, 22)), h("button", { class: "action-btn", onClick: () => this.closeClick.emit() }, this.renderIcon('cancel', 22, 22))),
57
+ this.renderDragBtn(),
58
+ h("div", { class: "content agent dropdown-trigger", role: "button", onClick: () => this.dropdownClick.emit(), "aria-haspopup": "listbox", "aria-expanded": false, "aria-label": `${this.conversation}, افتح قائمة المحادثات` }, h("span", { class: "title" }, this.conversation), h("span", { class: "dropdown-chevron" }, this.renderIcon('chevron-down', 24, 24))),
59
+ h("div", { class: "actions" }, h("button", { class: "action-btn", "aria-label": "\u062A\u0639\u062F\u064A\u0644 / Edit", onClick: () => this.editClick.emit() }, this.renderIcon('pencil-edit', 22, 22)), h("button", { class: "action-btn", "aria-label": "\u0627\u0644\u0645\u0632\u064A\u062F / More", onClick: () => this.moreClick.emit() }, this.renderIcon('ellipsis', 22, 22)), h("button", { class: "action-btn", "aria-label": "\u0625\u063A\u0644\u0627\u0642 / Close", onClick: () => this.closeClick.emit() }, this.renderIcon('cancel', 22, 22))),
47
60
  ];
48
61
  }
49
62
  renderHumanMode() {
50
63
  return [
51
- this.isDraggable && (h("button", { class: "action-btn drag-btn", onPointerDown: (e) => {
52
- e.preventDefault();
53
- this.headerDragStart.emit({ clientX: e.clientX, clientY: e.clientY });
54
- } }, this.renderIcon('drag', 11, 15))),
55
- h("div", { class: "content human" }, this.showBack && (h("button", { class: "back-btn", onClick: () => this.backClick.emit() }, this.renderIcon('arrow-right', 24, 24))), h("div", { class: "avatar-wrapper" }, h("img", { class: "avatar", src: this.agentAvatar, alt: this.agentName }), h("span", { class: "online-dot" }, this.renderIcon('online-dot', 10, 10))), h("div", { class: "text-block" }, h("span", { class: "agent-name" }, this.agentName), h("span", { class: "agent-status" }, this.agentStatus))),
56
- h("div", { class: "actions" }, h("button", { class: "action-btn", onClick: () => this.moreClick.emit() }, this.renderIcon('hand', 22, 22)), h("button", { class: "action-btn", onClick: () => this.closeClick.emit() }, this.renderIcon('cancel', 22, 22))),
64
+ this.renderDragBtn(),
65
+ h("div", { class: "content human" }, this.showBack && (h("button", { class: "back-btn", "aria-label": "\u0631\u062C\u0648\u0639 / Back", onClick: () => this.backClick.emit() }, this.renderIcon('arrow-right', 24, 24))), h("div", { class: "avatar-wrapper" }, this.renderAvatar(), h("span", { class: `online-dot status-${this.statusIndicator}` }, this.renderIcon('online-dot', 10, 10))), h("div", { class: "text-block" }, h("span", { class: "agent-name" }, this.agentName), this.agentStatus && h("span", { class: "agent-status" }, this.agentStatus))),
66
+ h("div", { class: "actions" }, h("button", { class: "action-btn", "aria-label": "\u0627\u0644\u0645\u0632\u064A\u062F / More", onClick: () => this.moreClick.emit() }, this.renderIcon('ellipsis', 22, 22)), h("button", { class: "action-btn", "aria-label": "\u0625\u063A\u0644\u0627\u0642 / Close", onClick: () => this.closeClick.emit() }, this.renderIcon('cancel', 22, 22))),
57
67
  ];
58
68
  }
59
69
  render() {
60
- return (h(Host, { key: '93fe332abe1339f7235aa18717d63aa65d9aac13' }, h("div", { key: 'b496695911f7d1f959c4f98506b0d230e77a86c3', class: "header-container" }, this.mode === 'agent' ? this.renderAgentMode() : this.renderHumanMode())));
70
+ return (h(Host, { key: 'f0b6caccf925bc1388b2680b49e4aec3a44fa353' }, h("div", { key: 'fd144266fb338cbb5d70a3fb1ae9c1b39710ffc5', class: "header-container" }, this.mode === 'agent' ? this.renderAgentMode() : this.renderHumanMode())));
61
71
  }
62
72
  static get is() { return "ai-chat-header"; }
63
73
  static get encapsulation() { return "shadow"; }
@@ -212,9 +222,34 @@ export class AiChatHeader {
212
222
  "reflect": false,
213
223
  "attribute": "is-draggable",
214
224
  "defaultValue": "false"
225
+ },
226
+ "statusIndicator": {
227
+ "type": "string",
228
+ "mutable": false,
229
+ "complexType": {
230
+ "original": "'online' | 'offline' | 'busy' | 'away'",
231
+ "resolved": "\"away\" | \"busy\" | \"offline\" | \"online\"",
232
+ "references": {}
233
+ },
234
+ "required": false,
235
+ "optional": false,
236
+ "docs": {
237
+ "tags": [],
238
+ "text": "Human mode: status indicator variant"
239
+ },
240
+ "getter": false,
241
+ "setter": false,
242
+ "reflect": false,
243
+ "attribute": "status-indicator",
244
+ "defaultValue": "'online'"
215
245
  }
216
246
  };
217
247
  }
248
+ static get states() {
249
+ return {
250
+ "avatarError": {}
251
+ };
252
+ }
218
253
  static get events() {
219
254
  return [{
220
255
  "method": "closeClick",
@@ -269,7 +304,7 @@ export class AiChatHeader {
269
304
  "composed": true,
270
305
  "docs": {
271
306
  "tags": [],
272
- "text": "Chevron-down button (human mode)"
307
+ "text": "More options button"
273
308
  },
274
309
  "complexType": {
275
310
  "original": "void",