@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
@@ -1,22 +1,17 @@
1
1
  /* ─── Custom Properties ──────────────────────────────────────────────────── */
2
2
  :host {
3
- --ai-msg-user-bg: var(--ai-user-bubble-bg, #F4F4F4);
4
- --ai-msg-user-color: var(--ai-text-primary, #333333);
5
- --ai-msg-user-border: 1px solid var(--ai-border-default, #eee);
6
- --ai-msg-agent-bg: var(--ai-agent-bubble-bg, #ffffff);
7
- --ai-msg-agent-color: var(--ai-text-primary, #333333);
8
- --ai-msg-agent-border: 1px solid var(--ai-border-default, #eee);
3
+ --ai-msg-user-bg: var(--ai-user-bubble-bg);
4
+ --ai-msg-user-border: 1px solid var(--ai-border-default);
5
+ --ai-msg-agent-bg: var(--ai-agent-bubble-bg);
6
+ --ai-msg-agent-border: 1px solid var(--ai-border-default);
9
7
  --ai-msg-border-radius: 16px;
10
8
  --ai-msg-padding: 16px;
11
9
  --ai-msg-font-size: 14px;
12
- --ai-msg-action-active-bg: var(--ai-accent, #a4ffe5);
13
- --ai-msg-timestamp-color: var(--ai-text-secondary, #737373);
10
+ --ai-msg-action-active-bg: var(--ai-accent);
14
11
  --ai-user-msg-max-width: 80%;
15
12
  --ai-agent-msg-max-width: 80%;
16
13
 
17
14
  display: block;
18
- direction: rtl;
19
- font-family: var(--ai-font-family, "PingARLT", sans-serif);
20
15
  }
21
16
 
22
17
  /* ─── Message Row ────────────────────────────────────────────────────────── */
@@ -33,7 +28,7 @@
33
28
 
34
29
  .user-bubble {
35
30
  background: var(--ai-msg-user-bg);
36
- color: var(--ai-msg-user-color);
31
+ color: var(--ai-text-primary);
37
32
  border: var(--ai-msg-user-border);
38
33
  border-radius: var(--ai-msg-border-radius);
39
34
  padding: var(--ai-msg-padding);
@@ -41,12 +36,12 @@
41
36
  max-width: var(--ai-user-msg-max-width);
42
37
  line-height: 1.5;
43
38
  word-break: break-word;
44
- box-shadow: 0px 1px 2px 0px rgba(18, 18, 23, 0.05);
39
+ box-shadow: var(--ai-shadow-sm);
45
40
  }
46
41
 
47
42
  .user-row .timestamp {
48
43
  font-size: 12px;
49
- color: var(--ai-msg-timestamp-color);
44
+ color: var(--ai-text-secondary);
50
45
  margin-top: 4px;
51
46
  }
52
47
 
@@ -68,14 +63,14 @@
68
63
 
69
64
  .agent-bubble {
70
65
  background: var(--ai-msg-agent-bg);
71
- color: var(--ai-msg-agent-color);
66
+ color: var(--ai-text-primary);
72
67
  border: var(--ai-msg-agent-border);
73
68
  border-radius: var(--ai-msg-border-radius);
74
69
  padding: var(--ai-msg-padding);
75
70
  font-size: var(--ai-msg-font-size);
76
71
  line-height: 1.6;
77
72
  word-break: break-word;
78
- box-shadow: 0px 1px 2px 0px rgba(18, 18, 23, 0.05);
73
+ box-shadow: var(--ai-shadow-sm);
79
74
  display: flex;
80
75
  flex-direction: column;
81
76
  gap: 12px;
@@ -115,16 +110,16 @@
115
110
  }
116
111
 
117
112
  .message-content code {
118
- background: var(--ai-bg-surface, #f3f4f6);
113
+ background: var(--ai-bg-surface);
119
114
  border-radius: 4px;
120
115
  padding: 1px 5px;
121
- font-family: monospace;
116
+ font-family: var(--ai-font-family-mono);
122
117
  font-size: 13px;
123
- color: var(--ai-text-primary, #374151);
118
+ color: var(--ai-text-primary);
124
119
  }
125
120
 
126
121
  .message-content pre {
127
- background: var(--ai-bg-surface, #f3f4f6);
122
+ background: var(--ai-bg-surface);
128
123
  border-radius: 8px;
129
124
  padding: 10px 12px;
130
125
  overflow-x: auto;
@@ -174,17 +169,17 @@
174
169
  width: 32px;
175
170
  height: 32px;
176
171
  border-radius: 8px;
177
- background: var(--ai-bg-card, #ffffff);
178
- border: 1px solid var(--ai-border-default, #eee);
172
+ background: var(--ai-bg-card);
173
+ border: 1px solid var(--ai-border-default);
179
174
  cursor: pointer;
180
- color: var(--ai-text-primary, #333333);
175
+ color: var(--ai-text-primary);
181
176
  transition: background 0.15s, color 0.15s;
182
177
  padding: 0;
183
178
  flex-shrink: 0;
184
179
  }
185
180
 
186
181
  .action-btn:hover {
187
- background: var(--ai-bg-surface, #f3f4f6);
182
+ background: var(--ai-bg-surface);
188
183
  }
189
184
 
190
185
  .action-btn.copy-success {
@@ -193,14 +188,13 @@
193
188
  border-color: var(--ai-accent);
194
189
  }
195
190
 
196
-
197
191
  .feedback-group {
198
192
  display: flex;
199
193
  align-items: center;
200
- border: 1px solid var(--ai-border-default, #eee);
194
+ border: 1px solid var(--ai-border-default);
201
195
  border-radius: 8px;
202
196
  overflow: hidden;
203
- background: var(--ai-bg-card, #ffffff);
197
+ background: var(--ai-bg-card);
204
198
  }
205
199
 
206
200
  .feedback-btn {
@@ -209,11 +203,11 @@
209
203
  justify-content: center;
210
204
  height: 32px;
211
205
  padding: 0 8px;
212
- background: var(--ai-bg-card, #ffffff);
206
+ background: var(--ai-bg-card);
213
207
  border: none;
214
- border-inline-start: 1px solid var(--ai-border-default, #eee);
208
+ border-inline-start: 1px solid var(--ai-border-default);
215
209
  cursor: pointer;
216
- color: var(--ai-text-primary, #333333);
210
+ color: var(--ai-text-primary);
217
211
  transition: background 0.15s, color 0.15s;
218
212
  }
219
213
 
@@ -222,7 +216,7 @@
222
216
  }
223
217
 
224
218
  .feedback-btn:hover {
225
- background: var(--ai-bg-surface, #f3f4f6);
219
+ background: var(--ai-bg-surface);
226
220
  }
227
221
 
228
222
  .feedback-btn.active {
@@ -244,7 +238,7 @@
244
238
  align-items: center;
245
239
  justify-content: flex-start;
246
240
  gap: 6px;
247
- color: var(--ai-text-muted, #9ca3af);
241
+ color: var(--ai-text-muted);
248
242
  }
249
243
 
250
244
  .agent-info-name,
@@ -275,16 +269,26 @@
275
269
  width: 8px;
276
270
  height: 8px;
277
271
  border-radius: 50%;
278
- background: var(--ai-text-muted, #9ca3af);
272
+ background: var(--ai-text-muted);
279
273
  animation: typingBounce 1.2s ease-in-out infinite;
280
274
  }
281
275
 
282
- .typing-dot:nth-child(2) { animation-delay: 0.2s; }
283
- .typing-dot:nth-child(3) { animation-delay: 0.4s; }
276
+ .typing-dot:nth-child(2) {
277
+ animation-delay: 0.2s;
278
+ }
279
+ .typing-dot:nth-child(3) {
280
+ animation-delay: 0.4s;
281
+ }
284
282
 
285
283
  @keyframes typingBounce {
286
- 0%, 60%, 100% { transform: translateY(0); }
287
- 30% { transform: translateY(-6px); }
284
+ 0%,
285
+ 60%,
286
+ 100% {
287
+ transform: translateY(0);
288
+ }
289
+ 30% {
290
+ transform: translateY(-6px);
291
+ }
288
292
  }
289
293
 
290
294
  /* ─── Streaming Cursor ───────────────────────────────────────────────────── */
@@ -299,6 +303,11 @@
299
303
  }
300
304
 
301
305
  @keyframes cursorBlink {
302
- 0%, 100% { opacity: 1; }
303
- 50% { opacity: 0; }
306
+ 0%,
307
+ 100% {
308
+ opacity: 1;
309
+ }
310
+ 50% {
311
+ opacity: 0;
312
+ }
304
313
  }
@@ -1,5 +1,10 @@
1
1
  import { Host, h } from "@stencil/core";
2
2
  import { iconRegistry } from "../../utils/icon-registry";
3
+ import DOMPurify from "dompurify";
4
+ const SANITIZE_CONFIG = {
5
+ ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'del', 'code', 'pre', 'ul', 'ol', 'li', 'h2', 'h3', 'a', 'blockquote', 'table', 'thead', 'tbody', 'tr', 'th', 'td'],
6
+ ALLOWED_ATTR: ['href', 'target', 'rel'],
7
+ };
3
8
  export class AiChatMessage {
4
9
  role = 'user';
5
10
  content = '';
@@ -28,16 +33,59 @@ export class AiChatMessage {
28
33
  parseMarkdown(text) {
29
34
  let html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
30
35
  const blocks = [];
36
+ // Fenced code blocks
31
37
  html = html.replace(/```(?:\w+)?\n?([\s\S]*?)```/g, (_, code) => {
32
38
  const idx = blocks.length;
33
39
  blocks.push(`<pre><code>${code}</code></pre>`);
34
40
  return `\x00BLOCK${idx}\x00`;
35
41
  });
42
+ // Pipe tables (must run before paragraph wrapping)
43
+ html = html.replace(/((?:^[ \t]*\|.+\|\n?)+)/gm, match => {
44
+ const rows = match.trim().split('\n').filter(l => l.trim());
45
+ if (rows.length < 2)
46
+ return match;
47
+ const isSep = (r) => /^[\s|:-]+$/.test(r);
48
+ let tableHtml = '<table>';
49
+ let inBody = false;
50
+ rows.forEach((row, i) => {
51
+ if (isSep(row)) {
52
+ inBody = true;
53
+ tableHtml += '<tbody>';
54
+ return;
55
+ }
56
+ const cells = row.split('|').filter((_, j, a) => j > 0 && j < a.length - 1);
57
+ if (!inBody && i === 0) {
58
+ tableHtml += `<thead><tr>${cells.map(c => `<th>${c.trim()}</th>`).join('')}</tr></thead>`;
59
+ }
60
+ else {
61
+ tableHtml += `<tr>${cells.map(c => `<td>${c.trim()}</td>`).join('')}</tr>`;
62
+ }
63
+ });
64
+ if (inBody)
65
+ tableHtml += '</tbody>';
66
+ tableHtml += '</table>';
67
+ const idx = blocks.length;
68
+ blocks.push(tableHtml);
69
+ return `\x00BLOCK${idx}\x00`;
70
+ });
71
+ // Blockquotes — &gt; because > was already escaped
72
+ html = html.replace(/((?:^&gt; .+\n?)+)/gm, match => {
73
+ const content = match.replace(/^&gt; /gm, '').trim();
74
+ const idx = blocks.length;
75
+ blocks.push(`<blockquote>${content}</blockquote>`);
76
+ return `\x00BLOCK${idx}\x00`;
77
+ });
78
+ // Inline code
36
79
  html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
80
+ // Headings
37
81
  html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
38
82
  html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
83
+ // Bold, italic, strikethrough, links
39
84
  html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
40
85
  html = html.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
86
+ html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');
87
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
88
+ // Unordered lists
41
89
  html = html.replace(/((?:^[ \t]*[-*] .+\n?)+)/gm, match => {
42
90
  const items = match
43
91
  .trim()
@@ -45,11 +93,11 @@ export class AiChatMessage {
45
93
  .filter(l => l.trim())
46
94
  .map(l => `<li>${l.replace(/^[ \t]*[-*] /, '')}</li>`)
47
95
  .join('');
48
- return `\x00BLOCK${blocks.length}\x00${(() => {
49
- blocks.push(`<ul>${items}</ul>`);
50
- return '';
51
- })()}`;
96
+ const idx = blocks.length;
97
+ blocks.push(`<ul>${items}</ul>`);
98
+ return `\x00BLOCK${idx}\x00`;
52
99
  });
100
+ // Ordered lists
53
101
  html = html.replace(/((?:^[ \t]*\d+\. .+\n?)+)/gm, match => {
54
102
  const items = match
55
103
  .trim()
@@ -57,10 +105,9 @@ export class AiChatMessage {
57
105
  .filter(l => l.trim())
58
106
  .map(l => `<li>${l.replace(/^[ \t]*\d+\. /, '')}</li>`)
59
107
  .join('');
60
- return `\x00BLOCK${blocks.length}\x00${(() => {
61
- blocks.push(`<ol>${items}</ol>`);
62
- return '';
63
- })()}`;
108
+ const idx = blocks.length;
109
+ blocks.push(`<ol>${items}</ol>`);
110
+ return `\x00BLOCK${idx}\x00`;
64
111
  });
65
112
  html = html
66
113
  .split(/\n{2,}/)
@@ -79,8 +126,7 @@ export class AiChatMessage {
79
126
  getRenderedContent() {
80
127
  if (!this.content)
81
128
  return '';
82
- let html = this.parseMarkdown(this.content);
83
- return html;
129
+ return DOMPurify.sanitize(this.parseMarkdown(this.content), SANITIZE_CONFIG);
84
130
  }
85
131
  getRelativeTime() {
86
132
  if (!this.timestamp)
@@ -89,15 +135,19 @@ export class AiChatMessage {
89
135
  const date = new Date(this.timestamp);
90
136
  if (isNaN(date.getTime()))
91
137
  return this.timestamp;
92
- const diffMin = Math.floor((Date.now() - date.getTime()) / 60000);
138
+ const diffMs = Date.now() - date.getTime();
139
+ const diffMin = Math.floor(diffMs / 60000);
140
+ const diffHour = Math.floor(diffMin / 60);
141
+ const diffDay = Math.floor(diffHour / 24);
142
+ const lang = (typeof document !== 'undefined' && document.documentElement.lang) || 'ar';
143
+ const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
93
144
  if (diffMin < 1)
94
- return 'الآن';
145
+ return rtf.format(0, 'second');
95
146
  if (diffMin < 60)
96
- return `منذ ${diffMin} دقيقة`;
97
- const hours = Math.floor(diffMin / 60);
98
- if (hours < 24)
99
- return `منذ ${hours} ساعة`;
100
- return `منذ ${Math.floor(hours / 24)} يوم`;
147
+ return rtf.format(-diffMin, 'minute');
148
+ if (diffHour < 24)
149
+ return rtf.format(-diffHour, 'hour');
150
+ return rtf.format(-diffDay, 'day');
101
151
  }
102
152
  catch {
103
153
  return this.timestamp;
@@ -145,7 +195,7 @@ export class AiChatMessage {
145
195
  } })), h("slot", null), showActions && this.renderActionsBar()), (this.agentName || this.timestamp) && (h("div", { class: "agent-info" }, this.agentName && h("span", { class: "agent-info-name" }, this.agentName), this.agentName && this.timestamp && this.renderIcon('eclipse', 10), this.timestamp && h("span", { class: "agent-info-time" }, this.getRelativeTime()))))));
146
196
  }
147
197
  render() {
148
- return h(Host, { key: '77dd0c46a676c211fdfba283d51bd0e0d2594b24' }, this.role === 'user' ? this.renderUserMessage() : this.renderAgentMessage());
198
+ return h(Host, { key: 'a3da55a7f023469f2538b363ff691674c241227e' }, this.role === 'user' ? this.renderUserMessage() : this.renderAgentMessage());
149
199
  }
150
200
  static get is() { return "ai-chat-message"; }
151
201
  static get encapsulation() { return "shadow"; }
@@ -0,0 +1,196 @@
1
+ :host {
2
+ display: block;
3
+ height: 100%;
4
+ }
5
+
6
+ .conversation-list {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 8px;
10
+ height: 100%;
11
+ background: var(--ai-bg-surface);
12
+ }
13
+
14
+ /* ── Scrollable list ───────────────────────────────────── */
15
+
16
+ .list-scroll {
17
+ flex: 1;
18
+ overflow-y: auto;
19
+ padding: 0 8px 12px;
20
+ scrollbar-width: thin;
21
+ scrollbar-color: var(--ai-scrollbar-thumb) var(--ai-scrollbar-track);
22
+ }
23
+
24
+ .list-scroll::-webkit-scrollbar {
25
+ width: 4px;
26
+ }
27
+
28
+ .list-scroll::-webkit-scrollbar-track {
29
+ background: var(--ai-scrollbar-track);
30
+ }
31
+
32
+ .list-scroll::-webkit-scrollbar-thumb {
33
+ background: var(--ai-scrollbar-thumb);
34
+ border-radius: 4px;
35
+ }
36
+
37
+ /* ── Conversation item ─────────────────────────────────── */
38
+
39
+ .conv-item {
40
+ display: flex;
41
+ align-items: flex-start;
42
+ gap: 6px;
43
+ margin-top: 6px;
44
+ padding: 10px 10px 10px 6px;
45
+ border-radius: 10px;
46
+ cursor: pointer;
47
+ transition: background 0.15s;
48
+ position: relative;
49
+ }
50
+
51
+ .conv-item:hover {
52
+ background: var(--ai-bg-card);
53
+ }
54
+
55
+ .conv-item--active {
56
+ background: var(--ai-bg-card);
57
+ box-shadow: var(--ai-shadow-sm);
58
+ }
59
+
60
+ .conv-item__body {
61
+ flex: 1;
62
+ min-width: 0;
63
+ }
64
+
65
+ .conv-item__title {
66
+ margin: 0 0 3px;
67
+ font-size: 13px;
68
+ font-weight: 600;
69
+ color: var(--ai-text-primary);
70
+ white-space: nowrap;
71
+ overflow: hidden;
72
+ text-overflow: ellipsis;
73
+ }
74
+
75
+ .conv-item__preview {
76
+ margin: 0 0 5px;
77
+ font-size: 12px;
78
+ color: var(--ai-text-muted);
79
+ line-height: 1.45;
80
+ display: -webkit-box;
81
+ -webkit-line-clamp: 2;
82
+ line-clamp: 2;
83
+ -webkit-box-orient: vertical;
84
+ overflow: hidden;
85
+ }
86
+
87
+ .conv-item__meta {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 8px;
91
+ }
92
+
93
+ .conv-item__time {
94
+ font-size: 11px;
95
+ color: var(--ai-text-muted);
96
+ flex-shrink: 0;
97
+ }
98
+
99
+ /* ── Rating dots ───────────────────────────────────────── */
100
+
101
+ .rating-dots {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 3px;
105
+ }
106
+
107
+ .rating-dot {
108
+ width: 6px;
109
+ height: 6px;
110
+ border-radius: 50%;
111
+ background: var(--ai-border-default);
112
+ transition: background 0.15s;
113
+ }
114
+
115
+ .rating-dot--filled {
116
+ background: var(--ai-accent-warning, #ffaf44);
117
+ }
118
+
119
+ /* ── Delete button ─────────────────────────────────────── */
120
+
121
+ .delete-btn {
122
+ flex-shrink: 0;
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ width: 24px;
127
+ height: 24px;
128
+ padding: 0;
129
+ background: transparent;
130
+ border: none;
131
+ border-radius: 6px;
132
+ cursor: pointer;
133
+ color: var(--ai-text-muted);
134
+ opacity: 0;
135
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
136
+ margin-block-start: 2px;
137
+ }
138
+
139
+ .conv-item:hover .delete-btn,
140
+ .conv-item--active .delete-btn {
141
+ opacity: 1;
142
+ }
143
+
144
+ .delete-btn:hover {
145
+ background: var(--ai-status-danger-bg, rgba(239, 68, 68, 0.1));
146
+ color: var(--ai-status-danger, #ef4444);
147
+ }
148
+
149
+ .delete-btn .icon-wrap {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ line-height: 0;
153
+ }
154
+
155
+ /* ── Skeleton loading ──────────────────────────────────── */
156
+
157
+ .skeleton-list {
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 4px;
161
+ }
162
+
163
+ .skeleton-item {
164
+ padding: 10px;
165
+ border-radius: 10px;
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 6px;
169
+ }
170
+
171
+ .skeleton-line {
172
+ border-radius: 6px;
173
+ background: var(--ai-shimmer-gradient);
174
+ background-size: 200% 100%;
175
+ animation: shimmer 2s linear infinite;
176
+ height: 12px;
177
+ }
178
+
179
+ .skeleton-line--title {
180
+ width: 65%;
181
+ height: 13px;
182
+ }
183
+
184
+ .skeleton-line--preview {
185
+ width: 100%;
186
+ }
187
+
188
+ .skeleton-line--meta {
189
+ width: 35%;
190
+ height: 10px;
191
+ }
192
+
193
+ @keyframes shimmer {
194
+ 0% { background-position: 200% 0; }
195
+ 100% { background-position: -200% 0; }
196
+ }