@aleph-alpha/chat-kit 1.0.0 → 1.1.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 (118) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-744ZHsQU.js → CkInput.vue_vue_type_script_setup_true_lang-DpYyyCgU.js} +23 -68
  2. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts +3 -4
  3. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -1
  4. package/dist/components/base/CkHistoryItem/types.d.ts +3 -2
  5. package/dist/components/base/CkHistoryItem/types.d.ts.map +1 -1
  6. package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts.map +1 -1
  7. package/dist/components/index.js +1 -1
  8. package/dist/index.js +1 -1
  9. package/package.json +5 -5
  10. package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +91 -0
  11. package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +215 -0
  12. package/src/components/base/CkAgentMessage/CkAgentMessage.vue +25 -0
  13. package/src/components/base/CkAgentMessage/index.ts +2 -0
  14. package/src/components/base/CkAgentMessage/types.ts +11 -0
  15. package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +129 -0
  16. package/src/components/base/CkConversationLayout/CkConversationLayout.stories.ts +251 -0
  17. package/src/components/base/CkConversationLayout/CkConversationLayout.vue +206 -0
  18. package/src/components/base/CkConversationLayout/index.ts +2 -0
  19. package/src/components/base/CkConversationLayout/types.ts +18 -0
  20. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts +89 -0
  21. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts +70 -0
  22. package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue +110 -0
  23. package/src/components/base/CkCopyMessageActionButton/index.ts +2 -0
  24. package/src/components/base/CkCopyMessageActionButton/types.ts +43 -0
  25. package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +42 -0
  26. package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +230 -0
  27. package/src/components/base/CkHistoryItem/CkHistoryItem.vue +285 -0
  28. package/src/components/base/CkHistoryItem/activeIndicator.ts +15 -0
  29. package/src/components/base/CkHistoryItem/index.ts +2 -0
  30. package/src/components/base/CkHistoryItem/types.ts +111 -0
  31. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.spec.ts +26 -0
  32. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +149 -0
  33. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +36 -0
  34. package/src/components/base/CkHistoryWrapper/index.ts +2 -0
  35. package/src/components/base/CkHistoryWrapper/types.ts +16 -0
  36. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +348 -0
  37. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +382 -0
  38. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +89 -0
  39. package/src/components/base/CkMarkdownRenderer/defaultMarkdownClasses.ts +19 -0
  40. package/src/components/base/CkMarkdownRenderer/index.ts +5 -0
  41. package/src/components/base/CkMarkdownRenderer/types.ts +53 -0
  42. package/src/components/base/CkMessageActionButton/CkMessageActionButton.spec.ts +40 -0
  43. package/src/components/base/CkMessageActionButton/CkMessageActionButton.stories.ts +56 -0
  44. package/src/components/base/CkMessageActionButton/CkMessageActionButton.vue +23 -0
  45. package/src/components/base/CkMessageActionButton/index.ts +2 -0
  46. package/src/components/base/CkMessageActionButton/types.ts +21 -0
  47. package/src/components/base/CkPromptActionButton/CkPromptActionButton.spec.ts +52 -0
  48. package/src/components/base/CkPromptActionButton/CkPromptActionButton.stories.ts +126 -0
  49. package/src/components/base/CkPromptActionButton/CkPromptActionButton.vue +25 -0
  50. package/src/components/base/CkPromptActionButton/index.ts +2 -0
  51. package/src/components/base/CkPromptActionButton/types.ts +32 -0
  52. package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +181 -0
  53. package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +224 -0
  54. package/src/components/base/CkPromptInput/CkPromptInput.vue +104 -0
  55. package/src/components/base/CkPromptInput/index.ts +2 -0
  56. package/src/components/base/CkPromptInput/types.ts +39 -0
  57. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +40 -0
  58. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.ts +103 -0
  59. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +16 -0
  60. package/src/components/base/CkPromptInputDisclaimer/index.ts +2 -0
  61. package/src/components/base/CkPromptInputDisclaimer/types.ts +17 -0
  62. package/src/components/base/CkPromptStopButton/CkPromptStopButton.spec.ts +64 -0
  63. package/src/components/base/CkPromptStopButton/CkPromptStopButton.stories.ts +81 -0
  64. package/src/components/base/CkPromptStopButton/CkPromptStopButton.vue +27 -0
  65. package/src/components/base/CkPromptStopButton/index.ts +2 -0
  66. package/src/components/base/CkPromptStopButton/types.ts +28 -0
  67. package/src/components/base/CkUserMessage/CkUserMessage.spec.ts +20 -0
  68. package/src/components/base/CkUserMessage/CkUserMessage.stories.ts +112 -0
  69. package/src/components/base/CkUserMessage/CkUserMessage.vue +19 -0
  70. package/src/components/base/CkUserMessage/index.ts +2 -0
  71. package/src/components/base/CkUserMessage/types.ts +14 -0
  72. package/src/components/base/icons/CkSendIcon.vue +22 -0
  73. package/src/components/base/icons/CkStopIcon.vue +18 -0
  74. package/src/components/base/icons/index.ts +2 -0
  75. package/src/components/base/index.ts +21 -0
  76. package/src/components/composed/CkConversation/CkConversation.spec.ts +439 -0
  77. package/src/components/composed/CkConversation/CkConversation.stories.ts +642 -0
  78. package/src/components/composed/CkConversation/CkConversation.vue +146 -0
  79. package/src/components/composed/CkConversation/index.ts +2 -0
  80. package/src/components/composed/CkConversation/types.ts +37 -0
  81. package/src/components/composed/CkHistory/CkHistory.spec.ts +327 -0
  82. package/src/components/composed/CkHistory/CkHistory.stories.ts +373 -0
  83. package/src/components/composed/CkHistory/CkHistory.vue +126 -0
  84. package/src/components/composed/CkHistory/index.ts +2 -0
  85. package/src/components/composed/CkHistory/types.ts +72 -0
  86. package/src/components/composed/CkInput/CkInput.spec.ts +191 -0
  87. package/src/components/composed/CkInput/CkInput.stories.ts +438 -0
  88. package/src/components/composed/CkInput/CkInput.vue +104 -0
  89. package/src/components/composed/CkInput/index.ts +2 -0
  90. package/src/components/composed/CkInput/types.ts +60 -0
  91. package/src/components/composed/index.ts +5 -0
  92. package/src/components/index.ts +34 -0
  93. package/src/composables/index.ts +9 -0
  94. package/src/composables/types.ts +36 -0
  95. package/src/composables/useChat.ts +18 -0
  96. package/src/composables/useConversations.spec.ts +557 -0
  97. package/src/composables/useConversations.ts +321 -0
  98. package/src/handlers/index.ts +9 -0
  99. package/src/handlers/useChatKitLabels.spec.ts +290 -0
  100. package/src/handlers/useChatKitLabels.ts +96 -0
  101. package/src/handlers/useMarkdownParser.spec.ts +112 -0
  102. package/src/handlers/useMarkdownParser.ts +47 -0
  103. package/src/handlers/useMessageHandler.spec.ts +535 -0
  104. package/src/handlers/useMessageHandler.ts +138 -0
  105. package/src/helpers/conversationTree.spec.ts +423 -0
  106. package/src/helpers/conversationTree.ts +117 -0
  107. package/src/helpers/generateId.ts +5 -0
  108. package/src/helpers/index.ts +4 -0
  109. package/src/helpers/secondsToDate.ts +7 -0
  110. package/src/helpers/toError.ts +8 -0
  111. package/src/index.ts +79 -0
  112. package/src/services/ChatService.ts +60 -0
  113. package/src/services/MockChatService.ts +47 -0
  114. package/src/services/index.ts +12 -0
  115. package/src/services/types.ts +92 -0
  116. package/src/test-setup.ts +12 -0
  117. package/src/test-utils/createTextPlugin.ts +9 -0
  118. package/src/types.ts +47 -0
@@ -0,0 +1,348 @@
1
+ import { render, screen } from '@testing-library/vue';
2
+ import { h } from 'vue';
3
+ import CkMarkdownRenderer from './CkMarkdownRenderer.vue';
4
+ import { defaultMarkdownClasses } from './defaultMarkdownClasses';
5
+ import { createTextPlugin } from '../../../test-utils/createTextPlugin';
6
+
7
+ describe('CkMarkdownRenderer', () => {
8
+ it('renders a plain text paragraph', () => {
9
+ render(CkMarkdownRenderer, { props: { content: 'Hello world' } });
10
+
11
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
12
+ expect(screen.getByText('Hello world').tagName).toBe('P');
13
+ });
14
+
15
+ it('renders headings at the correct level', () => {
16
+ const { container } = render(CkMarkdownRenderer, {
17
+ props: { content: '# Title\n\n## Subtitle' },
18
+ });
19
+
20
+ const h1 = container.querySelector('h1');
21
+ const h2 = container.querySelector('h2');
22
+ expect(h1?.textContent).toBe('Title');
23
+ expect(h2?.textContent).toBe('Subtitle');
24
+ });
25
+
26
+ it('renders bold and italic text', () => {
27
+ const { container } = render(CkMarkdownRenderer, {
28
+ props: { content: '**bold** and *italic*' },
29
+ });
30
+
31
+ expect(container.querySelector('strong')?.textContent).toBe('bold');
32
+ expect(container.querySelector('em')?.textContent).toBe('italic');
33
+ });
34
+
35
+ it('renders unordered lists', () => {
36
+ const { container } = render(CkMarkdownRenderer, {
37
+ props: { content: '- item one\n- item two\n- item three' },
38
+ });
39
+
40
+ const items = container.querySelectorAll('li');
41
+ expect(items).toHaveLength(3);
42
+ expect(items[0].textContent).toBe('item one');
43
+ });
44
+
45
+ it('renders inline code without triggering the code-block slot', () => {
46
+ const { container } = render(CkMarkdownRenderer, {
47
+ props: { content: 'Use `console.log` for debugging' },
48
+ slots: {
49
+ 'code-block': () =>
50
+ h('div', { class: 'custom-block' }, 'SHOULD NOT APPEAR'),
51
+ },
52
+ });
53
+
54
+ expect(container.querySelector('.custom-block')).toBeNull();
55
+ const inlineCode = container.querySelector('p > code');
56
+ expect(inlineCode?.textContent).toBe('console.log');
57
+ });
58
+
59
+ it('renders a code block with default pre/code when no slot is provided', () => {
60
+ const markdown = '```javascript\nconsole.log("hi");\n```';
61
+
62
+ const { container } = render(CkMarkdownRenderer, {
63
+ props: { content: markdown },
64
+ });
65
+
66
+ const codeEl = container.querySelector('pre > code');
67
+ expect(codeEl).not.toBeNull();
68
+ expect(codeEl?.textContent).toContain('console.log("hi");');
69
+ expect(codeEl?.classList.contains('language-javascript')).toBe(true);
70
+ });
71
+
72
+ it('renders a code block without a language class when none is specified', () => {
73
+ const markdown = '```\nplain code\n```';
74
+
75
+ const { container } = render(CkMarkdownRenderer, {
76
+ props: { content: markdown },
77
+ });
78
+
79
+ const codeEl = container.querySelector('pre > code');
80
+ expect(codeEl).not.toBeNull();
81
+ expect(codeEl?.textContent).toContain('plain code');
82
+ });
83
+
84
+ it('delegates code blocks to the code-block scoped slot', () => {
85
+ const markdown = '```python\nprint("hello")\n```';
86
+
87
+ const { container } = render(CkMarkdownRenderer, {
88
+ props: { content: markdown },
89
+ slots: {
90
+ 'code-block': (props: { code: string; language: string }) =>
91
+ h(
92
+ 'div',
93
+ { class: 'custom-code', 'data-lang': props.language },
94
+ props.code,
95
+ ),
96
+ },
97
+ });
98
+
99
+ const custom = container.querySelector('.custom-code');
100
+ expect(custom).not.toBeNull();
101
+ expect(custom?.textContent).toContain('print("hello")');
102
+ expect(custom?.getAttribute('data-lang')).toBe('python');
103
+ expect(container.querySelector('pre > code')).toBeNull();
104
+ });
105
+
106
+ it('preserves order of mixed prose and code blocks', () => {
107
+ const markdown = 'Before\n\n```ts\nconst x = 1;\n```\n\nAfter';
108
+
109
+ const { container } = render(CkMarkdownRenderer, {
110
+ props: { content: markdown },
111
+ });
112
+
113
+ const root = container.querySelector('.markdown-renderer');
114
+ expect(root).not.toBeNull();
115
+
116
+ const text = root!.textContent!;
117
+ expect(text.indexOf('Before')).toBeLessThan(text.indexOf('const x = 1;'));
118
+ expect(text.indexOf('const x = 1;')).toBeLessThan(text.indexOf('After'));
119
+ });
120
+
121
+ it('renders nothing for empty content', () => {
122
+ const { container } = render(CkMarkdownRenderer, {
123
+ props: { content: '' },
124
+ });
125
+
126
+ const root = container.querySelector('.markdown-renderer');
127
+ expect(root).not.toBeNull();
128
+ expect(root?.children.length).toBe(0);
129
+ });
130
+
131
+ it('renders a table from GFM syntax', () => {
132
+ const markdown =
133
+ '| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |';
134
+
135
+ const { container } = render(CkMarkdownRenderer, {
136
+ props: { content: markdown },
137
+ });
138
+
139
+ const table = container.querySelector('table');
140
+ expect(table).not.toBeNull();
141
+ const headerCells = table!.querySelectorAll('thead th');
142
+ expect(headerCells).toHaveLength(2);
143
+ expect(headerCells[0].textContent).toBe('Name');
144
+ expect(headerCells[1].textContent).toBe('Age');
145
+ const rows = table!.querySelectorAll('tbody tr');
146
+ expect(rows).toHaveLength(2);
147
+ expect(rows[0].querySelectorAll('td')[0].textContent).toBe('Alice');
148
+ });
149
+
150
+ it('renders strikethrough text', () => {
151
+ const { container } = render(CkMarkdownRenderer, {
152
+ props: { content: '~~deleted~~' },
153
+ });
154
+
155
+ const del = container.querySelector('del');
156
+ expect(del).not.toBeNull();
157
+ expect(del?.textContent).toBe('deleted');
158
+ });
159
+
160
+ it('renders task list checkboxes', () => {
161
+ const markdown = '- [x] done\n- [ ] pending';
162
+
163
+ const { container } = render(CkMarkdownRenderer, {
164
+ props: { content: markdown },
165
+ });
166
+
167
+ const checkboxes = container.querySelectorAll('input[type="checkbox"]');
168
+ expect(checkboxes).toHaveLength(2);
169
+ expect((checkboxes[0] as HTMLInputElement).checked).toBe(true);
170
+ expect((checkboxes[1] as HTMLInputElement).checked).toBe(false);
171
+ });
172
+
173
+ it('auto-links bare URLs', () => {
174
+ const { container } = render(CkMarkdownRenderer, {
175
+ props: { content: 'Visit https://example.com for details' },
176
+ });
177
+
178
+ const link = container.querySelector('a');
179
+ expect(link).not.toBeNull();
180
+ expect(link?.getAttribute('href')).toBe('https://example.com');
181
+ expect(link?.textContent).toBe('https://example.com');
182
+ });
183
+
184
+ describe('classes prop', () => {
185
+ it('applies custom classes to matching elements', () => {
186
+ const { container } = render(CkMarkdownRenderer, {
187
+ props: {
188
+ content:
189
+ '# Hello\n\nA **bold** paragraph with a [link](https://example.com)',
190
+ classes: {
191
+ h1: 'my-heading',
192
+ p: 'my-paragraph',
193
+ strong: 'my-bold',
194
+ a: 'my-link',
195
+ },
196
+ },
197
+ });
198
+
199
+ expect(
200
+ container.querySelector('h1')?.classList.contains('my-heading'),
201
+ ).toBe(true);
202
+ expect(
203
+ container.querySelector('p')?.classList.contains('my-paragraph'),
204
+ ).toBe(true);
205
+ expect(
206
+ container.querySelector('strong')?.classList.contains('my-bold'),
207
+ ).toBe(true);
208
+ expect(container.querySelector('a')?.classList.contains('my-link')).toBe(
209
+ true,
210
+ );
211
+ });
212
+
213
+ it('applies custom classes to table elements', () => {
214
+ const markdown = '| A | B |\n| --- | --- |\n| 1 | 2 |';
215
+
216
+ const { container } = render(CkMarkdownRenderer, {
217
+ props: {
218
+ content: markdown,
219
+ classes: {
220
+ table: 'tbl',
221
+ th: 'tbl-header',
222
+ td: 'tbl-cell',
223
+ tr: 'tbl-row',
224
+ },
225
+ },
226
+ });
227
+
228
+ expect(container.querySelector('table')?.classList.contains('tbl')).toBe(
229
+ true,
230
+ );
231
+ expect(
232
+ container.querySelector('th')?.classList.contains('tbl-header'),
233
+ ).toBe(true);
234
+ expect(
235
+ container.querySelector('td')?.classList.contains('tbl-cell'),
236
+ ).toBe(true);
237
+ expect(container.querySelector('tr')?.classList.contains('tbl-row')).toBe(
238
+ true,
239
+ );
240
+ });
241
+
242
+ it('merges custom classes with existing attributes', () => {
243
+ const markdown = '```javascript\ncode\n```';
244
+
245
+ const { container } = render(CkMarkdownRenderer, {
246
+ props: { content: markdown, classes: { code: 'my-code' } },
247
+ });
248
+
249
+ const codeEl = container.querySelector('pre > code');
250
+ expect(codeEl?.classList.contains('language-javascript')).toBe(true);
251
+ expect(codeEl?.classList.contains('my-code')).toBe(true);
252
+ });
253
+
254
+ it('applies default classes to elements not overridden in the map', () => {
255
+ const { container } = render(CkMarkdownRenderer, {
256
+ props: {
257
+ content: '# Title\n\nParagraph',
258
+ classes: { h1: 'styled' },
259
+ },
260
+ });
261
+
262
+ expect(container.querySelector('h1')?.classList.contains('styled')).toBe(
263
+ true,
264
+ );
265
+ expect(container.querySelector('p')?.className).toContain(
266
+ defaultMarkdownClasses.p,
267
+ );
268
+ });
269
+
270
+ it('applies default classes when classes prop is not provided', () => {
271
+ const { container } = render(CkMarkdownRenderer, {
272
+ props: { content: '# Title' },
273
+ });
274
+
275
+ expect(container.querySelector('h1')?.className).toContain(
276
+ defaultMarkdownClasses.h1,
277
+ );
278
+ });
279
+
280
+ it('applies no classes for an element when classes prop explicitly sets it to empty string', () => {
281
+ const { container } = render(CkMarkdownRenderer, {
282
+ props: { content: '# Title', classes: { h1: '' } },
283
+ });
284
+
285
+ expect(container.querySelector('h1')?.hasAttribute('class')).toBe(false);
286
+ });
287
+ });
288
+
289
+ it('sanitizes dangerous HTML in markdown', () => {
290
+ const markdown =
291
+ 'Safe text\n\n<script>alert("xss")</script>\n\n<img src=x onerror=alert(1)>';
292
+
293
+ const { container } = render(CkMarkdownRenderer, {
294
+ props: { content: markdown },
295
+ });
296
+
297
+ expect(container.querySelector('script')).toBeNull();
298
+ const img = container.querySelector('img');
299
+ if (img) {
300
+ expect(img.hasAttribute('onerror')).toBe(false);
301
+ }
302
+ });
303
+
304
+ describe('plugins prop', () => {
305
+ it('applies a custom remark plugin that transforms text', () => {
306
+ const uppercasePlugin = createTextPlugin((t) => t.toUpperCase());
307
+
308
+ const { container } = render(CkMarkdownRenderer, {
309
+ props: {
310
+ content: 'hello world',
311
+ plugins: [uppercasePlugin],
312
+ },
313
+ });
314
+
315
+ expect(container.querySelector('p')?.textContent).toBe('HELLO WORLD');
316
+ });
317
+
318
+ it('renders default output when plugins is undefined', () => {
319
+ render(CkMarkdownRenderer, {
320
+ props: { content: 'Hello world', plugins: undefined },
321
+ });
322
+
323
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
324
+ });
325
+
326
+ it('renders default output when plugins is an empty array', () => {
327
+ render(CkMarkdownRenderer, {
328
+ props: { content: 'Hello world', plugins: [] },
329
+ });
330
+
331
+ expect(screen.getByText('Hello world')).toBeInTheDocument();
332
+ });
333
+
334
+ it('supports multiple plugins applied in order', () => {
335
+ const addPrefix = createTextPlugin((t) => `prefix:${t}`);
336
+ const uppercasePlugin = createTextPlugin((t) => t.toUpperCase());
337
+
338
+ const { container } = render(CkMarkdownRenderer, {
339
+ props: {
340
+ content: 'hello',
341
+ plugins: [addPrefix, uppercasePlugin],
342
+ },
343
+ });
344
+
345
+ expect(container.querySelector('p')?.textContent).toBe('PREFIX:HELLO');
346
+ });
347
+ });
348
+ });