@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,373 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { ref } from 'vue';
3
+ import CkHistory from './CkHistory.vue';
4
+ import { CkHistoryItem } from '../../base/CkHistoryItem';
5
+ import type { Conversation } from '../../../types';
6
+
7
+ const NOW = Math.floor(Date.now() / 1000);
8
+ const ONE_DAY = 60 * 60 * 24;
9
+
10
+ const sampleConversations: Conversation[] = [
11
+ {
12
+ id: 'c-1',
13
+ title: 'Project Proposal Overview',
14
+ createdAt: NOW - ONE_DAY * 14,
15
+ updatedAt: NOW - 60 * 30,
16
+ },
17
+ {
18
+ id: 'c-2',
19
+ title: 'Customer onboarding flow',
20
+ createdAt: NOW - ONE_DAY * 10,
21
+ updatedAt: NOW - 60 * 60 * 5,
22
+ },
23
+ {
24
+ id: 'c-3',
25
+ title: 'Refactor notes',
26
+ createdAt: NOW - ONE_DAY * 7,
27
+ updatedAt: NOW - ONE_DAY,
28
+ },
29
+ {
30
+ id: 'c-4',
31
+ createdAt: NOW - ONE_DAY * 3,
32
+ updatedAt: NOW - ONE_DAY * 2,
33
+ },
34
+ {
35
+ id: 'c-5',
36
+ title: 'Quarterly review prep',
37
+ createdAt: NOW - ONE_DAY * 30,
38
+ updatedAt: NOW - ONE_DAY * 5,
39
+ },
40
+ {
41
+ id: 'c-6',
42
+ title: 'Travel plans 2026',
43
+ createdAt: NOW - ONE_DAY * 60,
44
+ updatedAt: NOW - ONE_DAY * 14,
45
+ },
46
+ ];
47
+
48
+ const defaultHrefResolver = (id: string) => `/chat/${id}`;
49
+
50
+ const meta: Meta<typeof CkHistory> = {
51
+ title: 'Composed/CkHistory',
52
+ component: CkHistory,
53
+ tags: ['autodocs'],
54
+ parameters: {
55
+ layout: 'padded',
56
+ docs: {
57
+ description: {
58
+ component:
59
+ 'Drop-in conversation history list. Pass a sorted `conversations` array and an `hrefResolver(id)` and you get a list of real-link rows with native middle-click / cmd-click / right-click affordances. Plain left-clicks emit `select` so consumers can `router.push(...)` themselves. Per-row Edit / Delete actions emit `edit` and `delete`.\n\nLabels are sourced from `useChatKitLabels` and can be translated via `provideChatKitLabels`.',
60
+ },
61
+ },
62
+ },
63
+ argTypes: {
64
+ conversations: { control: false },
65
+ hrefResolver: { control: false },
66
+ activeId: { control: 'text' },
67
+ showAction: { control: 'boolean' },
68
+ showActionOnHover: { control: 'boolean' },
69
+ },
70
+ };
71
+
72
+ export default meta;
73
+ type Story = StoryObj<typeof CkHistory>;
74
+
75
+ const defaultTemplateSource = `<script setup lang="ts">
76
+ import { CkHistory, type Conversation } from '@aleph-alpha/chat-kit';
77
+
78
+ const conversations: Conversation[] = [
79
+ {
80
+ id: 'c-1',
81
+ title: 'Project Proposal Overview',
82
+ createdAt: 1_714_000_000,
83
+ updatedAt: 1_714_900_000,
84
+ },
85
+ {
86
+ id: 'c-2',
87
+ title: 'Customer onboarding flow',
88
+ createdAt: 1_714_100_000,
89
+ updatedAt: 1_714_800_000,
90
+ },
91
+ ];
92
+
93
+ const hrefResolver = (id: string) => \`/chat/\${id}\`;
94
+
95
+ function onSelect(c: Conversation) {
96
+ // router.push({ name: 'chat', params: { id: c.id } })
97
+ }
98
+ function onEdit(c: Conversation) {
99
+ // open rename dialog
100
+ }
101
+ function onDelete(c: Conversation) {
102
+ // confirm + delete
103
+ }
104
+ </script>
105
+
106
+ <template>
107
+ <CkHistory
108
+ :conversations="conversations"
109
+ :href-resolver="hrefResolver"
110
+ active-id="c-2"
111
+ @select="onSelect"
112
+ @edit="onEdit"
113
+ @delete="onDelete"
114
+ />
115
+ </template>`;
116
+
117
+ const withoutActionsTemplateSource = `<script setup lang="ts">
118
+ import { CkHistory, type Conversation } from '@aleph-alpha/chat-kit';
119
+
120
+ const conversations: Conversation[] = [/* ... */];
121
+ const hrefResolver = (id: string) => \`/chat/\${id}\`;
122
+ </script>
123
+
124
+ <template>
125
+ <CkHistory
126
+ :conversations="conversations"
127
+ :href-resolver="hrefResolver"
128
+ active-id="c-2"
129
+ :show-action="false"
130
+ />
131
+ </template>`;
132
+
133
+ const routerLinkIntegrationTemplateSource = `<script setup lang="ts">
134
+ import { CkHistory, type Conversation } from '@aleph-alpha/chat-kit';
135
+ import { useRouter, useRoute } from 'vue-router';
136
+
137
+ const router = useRouter();
138
+ const route = useRoute();
139
+
140
+ const conversations = /* fetched + sorted by updatedAt desc */ [] as Conversation[];
141
+
142
+ const hrefResolver = (id: string) =>
143
+ router.resolve({ name: 'chat', params: { id } }).href;
144
+
145
+ function onSelect(c: Conversation) {
146
+ router.push({ name: 'chat', params: { id: c.id } });
147
+ }
148
+ function onEdit(c: Conversation) { /* open rename dialog */ }
149
+ function onDelete(c: Conversation) { /* confirm + delete */ }
150
+ </script>
151
+
152
+ <template>
153
+ <CkHistory
154
+ :conversations="conversations"
155
+ :href-resolver="hrefResolver"
156
+ :active-id="route.params.id as string"
157
+ @select="onSelect"
158
+ @edit="onEdit"
159
+ @delete="onDelete"
160
+ />
161
+ </template>`;
162
+
163
+ const withCustomItemTemplateSource = `<script setup lang="ts">
164
+ import {
165
+ CkHistory,
166
+ CkHistoryItem,
167
+ type Conversation,
168
+ } from '@aleph-alpha/chat-kit';
169
+ import { RouterLink, useRoute } from 'vue-router';
170
+
171
+ const route = useRoute();
172
+ const conversations: Conversation[] = [/* ... */];
173
+
174
+ function onEdit(c: Conversation) { /* open rename dialog */ }
175
+ function onDelete(c: Conversation) { /* confirm + delete */ }
176
+ </script>
177
+
178
+ <template>
179
+ <CkHistory
180
+ :conversations="conversations"
181
+ :href-resolver="(id) => \\\`/chat/\\\${id}\\\`"
182
+ :active-id="route.params.id as string"
183
+ @edit="onEdit"
184
+ @delete="onDelete"
185
+ >
186
+ <template
187
+ #item="{ conversation, isActive, title, edit, delete: del }"
188
+ >
189
+ <RouterLink
190
+ :to="{ name: 'chat', params: { id: conversation.id } }"
191
+ custom
192
+ v-slot="{ href, navigate }"
193
+ >
194
+ <CkHistoryItem
195
+ as="a"
196
+ :href="href"
197
+ :is-active="isActive"
198
+ :title="title"
199
+ action-popover
200
+ @click="navigate"
201
+ >
202
+ <template #action-popover>
203
+ <button type="button" @click="edit">Edit</button>
204
+ <button type="button" @click="del">Delete</button>
205
+ </template>
206
+ </CkHistoryItem>
207
+ </RouterLink>
208
+ </template>
209
+ </CkHistory>
210
+ </template>`;
211
+
212
+ const renderStory = (args: Story['args']) => ({
213
+ components: { CkHistory },
214
+ setup() {
215
+ const lastEvent = ref<string>('');
216
+
217
+ function onSelect(c: Conversation) {
218
+ lastEvent.value = `select(${c.id})`;
219
+ }
220
+ function onEdit(c: Conversation) {
221
+ lastEvent.value = `edit(${c.id})`;
222
+ }
223
+ function onDelete(c: Conversation) {
224
+ lastEvent.value = `delete(${c.id})`;
225
+ }
226
+
227
+ return { args, lastEvent, onSelect, onEdit, onDelete };
228
+ },
229
+ template: `
230
+ <div class="bg-background-surface-primary p-4 w-[24rem]">
231
+ <CkHistory
232
+ v-bind="args"
233
+ @select="onSelect"
234
+ @edit="onEdit"
235
+ @delete="onDelete"
236
+ />
237
+ <p class="label-12-regular mt-3 text-content-on-surface-muted">
238
+ Last event: {{ lastEvent || '\u2014' }}
239
+ </p>
240
+ </div>
241
+ `,
242
+ });
243
+
244
+ /**
245
+ * Default list with per-row Edit / Delete actions and an active row. Plain
246
+ * left-clicks emit `select` (call `router.push(...)` in the handler);
247
+ * middle-click, cmd/ctrl-click, and right-click keep native browser
248
+ * behavior via the resolved `href`.
249
+ */
250
+ export const Default: Story = {
251
+ args: {
252
+ conversations: sampleConversations,
253
+ hrefResolver: defaultHrefResolver,
254
+ activeId: 'c-2',
255
+ },
256
+ render: renderStory,
257
+ parameters: { docs: { source: { code: defaultTemplateSource } } },
258
+ };
259
+
260
+ /**
261
+ * Hides the per-row kebab action (`show-action="false"`). Use when
262
+ * conversations are read-only or actions live elsewhere in the UI.
263
+ */
264
+ export const WithoutActions: Story = {
265
+ args: {
266
+ conversations: sampleConversations,
267
+ hrefResolver: defaultHrefResolver,
268
+ activeId: 'c-2',
269
+ showAction: false,
270
+ },
271
+ render: renderStory,
272
+ parameters: { docs: { source: { code: withoutActionsTemplateSource } } },
273
+ };
274
+
275
+ /**
276
+ * Typical Vue Router integration. The `<a href>` produced by `hrefResolver`
277
+ * enables native middle-click / cmd-click "open in new tab" and right-click
278
+ * "Copy link address". Plain left-clicks are intercepted via the `select`
279
+ * event so you can call `router.push(...)`.
280
+ */
281
+ export const RouterLinkIntegration: Story = {
282
+ args: {
283
+ conversations: sampleConversations,
284
+ hrefResolver: defaultHrefResolver,
285
+ activeId: 'c-3',
286
+ },
287
+ render: renderStory,
288
+ parameters: {
289
+ docs: { source: { code: routerLinkIntegrationTemplateSource } },
290
+ },
291
+ };
292
+
293
+ /**
294
+ * Replaces each row via the `#item` scoped slot. Use this when you want
295
+ * to wrap rows in `<RouterLink>` for native vue-router integration: the
296
+ * slot scope exposes the resolved row data plus `select` / `edit` /
297
+ * `delete` helpers, and you reuse `CkHistoryItem` to keep the default
298
+ * look while delegating navigation to the router.
299
+ */
300
+ export const WithCustomItem: Story = {
301
+ args: {
302
+ conversations: sampleConversations,
303
+ hrefResolver: defaultHrefResolver,
304
+ activeId: 'c-2',
305
+ },
306
+ render: (args) => ({
307
+ components: { CkHistory, CkHistoryItem },
308
+ setup() {
309
+ const lastEvent = ref<string>('');
310
+
311
+ function onEdit(c: Conversation) {
312
+ lastEvent.value = `edit(${c.id})`;
313
+ }
314
+ function onDelete(c: Conversation) {
315
+ lastEvent.value = `delete(${c.id})`;
316
+ }
317
+ function onSelect(c: Conversation) {
318
+ lastEvent.value = `select(${c.id})`;
319
+ }
320
+
321
+ return { args, lastEvent, onEdit, onDelete, onSelect };
322
+ },
323
+ template: `
324
+ <div class="bg-background-surface-primary p-4 w-[24rem]">
325
+ <CkHistory
326
+ v-bind="args"
327
+ @edit="onEdit"
328
+ @delete="onDelete"
329
+ @select="onSelect"
330
+ >
331
+ <template
332
+ #item="{ href, isActive, title, select, edit, delete: del }"
333
+ >
334
+ <CkHistoryItem
335
+ as="a"
336
+ :href="href"
337
+ :is-active="isActive"
338
+ :title="title"
339
+ action-popover
340
+ action-aria-label="Conversation options"
341
+ @click="select($event)"
342
+ >
343
+ <template #action-popover>
344
+ <div class="flex flex-col p-1">
345
+ <button
346
+ type="button"
347
+ class="flex h-10 w-full items-center gap-3 rounded-md px-2 py-2 body-14-regular text-content-on-surface-primary outline-hidden transition-colors hover-layer hover-layer-default focus-visible:ring-[3px] focus-visible:ring-ring/50"
348
+ @click="edit"
349
+ >
350
+ <span class="min-w-0 flex-1 truncate text-left">Edit</span>
351
+ </button>
352
+ <button
353
+ type="button"
354
+ class="flex h-10 w-full items-center gap-3 rounded-md px-2 py-2 body-14-regular text-content-on-surface-primary outline-hidden transition-colors hover-layer hover-layer-default focus-visible:ring-[3px] focus-visible:ring-ring/50"
355
+ @click="del"
356
+ >
357
+ <span class="min-w-0 flex-1 truncate text-left">Delete</span>
358
+ </button>
359
+ </div>
360
+ </template>
361
+ </CkHistoryItem>
362
+ </template>
363
+ </CkHistory>
364
+ <p class="label-12-regular mt-3 text-content-on-surface-muted">
365
+ Last event: {{ lastEvent || '\u2014' }}
366
+ </p>
367
+ </div>
368
+ `,
369
+ }),
370
+ parameters: {
371
+ docs: { source: { code: withCustomItemTemplateSource } },
372
+ },
373
+ };
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ UiIcon,
4
+ UiSidebarPopoverMenu,
5
+ UiSidebarPopoverMenuItem,
6
+ } from '@aleph-alpha/ui-library';
7
+ import CkHistoryItem from '../../base/CkHistoryItem/CkHistoryItem.vue';
8
+ import CkHistoryWrapper from '../../base/CkHistoryWrapper/CkHistoryWrapper.vue';
9
+ import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
10
+ import type { Conversation } from '../../../types';
11
+ import type { CkHistoryEmits, CkHistoryProps } from './types';
12
+
13
+ defineOptions({ name: 'CkHistory' });
14
+
15
+ const props = withDefaults(defineProps<CkHistoryProps>(), {
16
+ activeId: undefined,
17
+ showAction: true,
18
+ showActionOnHover: true,
19
+ });
20
+
21
+ const emit = defineEmits<CkHistoryEmits>();
22
+
23
+ defineSlots<{
24
+ /**
25
+ * Replace the entire row rendering. Use this to wrap each row in your
26
+ * own navigation primitive (e.g. vue-router `<RouterLink>`) while still
27
+ * receiving the resolved row data and the standard event helpers.
28
+ *
29
+ * Slot scope:
30
+ * - `conversation` — raw `Conversation` for this row
31
+ * - `href` — URL produced by `hrefResolver(conversation.id)`
32
+ * - `isActive` — whether this row matches `activeId`
33
+ * - `title` — resolved title (falls back to `historyItemUntitled` label)
34
+ * - `select(event)` — RouterLink-style click guard: `preventDefault()`
35
+ * and emit `select` for plain left-clicks; ignores middle/modifier
36
+ * clicks so native browser affordances still work.
37
+ * - `edit()` / `delete()` — emit the corresponding event for this row
38
+ */
39
+ item: (props: {
40
+ conversation: Conversation;
41
+ href: string;
42
+ isActive: boolean;
43
+ title: string;
44
+ select: (event: MouseEvent) => void;
45
+ edit: () => void;
46
+ delete: () => void;
47
+ }) => void;
48
+ }>();
49
+
50
+ const labels = useChatKitLabels();
51
+
52
+ function hrefFor(c: Conversation) {
53
+ return props.hrefResolver(c.id);
54
+ }
55
+
56
+ function titleFor(c: Conversation) {
57
+ const title = c.title?.trim();
58
+ return title ? title : labels.value.historyItemUntitled;
59
+ }
60
+
61
+ /**
62
+ * RouterLink-style click guard: only emit `select` for plain left-clicks.
63
+ * Middle-click, modifier-clicks, and right-click fall through to the
64
+ * browser so the `<a href>` provides native affordances (open in new tab,
65
+ * copy link, etc).
66
+ */
67
+ function handleRowClick(c: Conversation, event: MouseEvent) {
68
+ if (event.defaultPrevented) return;
69
+ if (event.button !== 0) return;
70
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
71
+ event.preventDefault();
72
+ emit('select', c, event);
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <CkHistoryWrapper>
78
+ <template
79
+ v-for="conversation in props.conversations"
80
+ :key="conversation.id"
81
+ >
82
+ <slot
83
+ name="item"
84
+ :conversation="conversation"
85
+ :href="hrefFor(conversation)"
86
+ :is-active="conversation.id === props.activeId"
87
+ :title="titleFor(conversation)"
88
+ :select="(event: MouseEvent) => handleRowClick(conversation, event)"
89
+ :edit="() => emit('edit', conversation)"
90
+ :delete="() => emit('delete', conversation)"
91
+ >
92
+ <CkHistoryItem
93
+ as="a"
94
+ :href="hrefFor(conversation)"
95
+ :is-active="conversation.id === props.activeId"
96
+ :title="titleFor(conversation)"
97
+ :show-action="props.showAction"
98
+ :show-action-on-hover="props.showActionOnHover"
99
+ :action-popover="props.showAction"
100
+ :action-aria-label="labels.historyItemActionAriaLabel"
101
+ @click="handleRowClick(conversation, $event)"
102
+ ><template #action-popover>
103
+ <UiSidebarPopoverMenu :show-footer-separator="false">
104
+ <UiSidebarPopoverMenuItem
105
+ :label="labels.historyItemEditAction"
106
+ @click="emit('edit', conversation)"
107
+ >
108
+ <template #icon>
109
+ <UiIcon name="pencil" size="s" />
110
+ </template>
111
+ </UiSidebarPopoverMenuItem>
112
+ <UiSidebarPopoverMenuItem
113
+ :label="labels.historyItemDeleteAction"
114
+ @click="emit('delete', conversation)"
115
+ >
116
+ <template #icon>
117
+ <UiIcon name="trash" size="s" />
118
+ </template>
119
+ </UiSidebarPopoverMenuItem>
120
+ </UiSidebarPopoverMenu>
121
+ </template>
122
+ </CkHistoryItem>
123
+ </slot>
124
+ </template>
125
+ </CkHistoryWrapper>
126
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as CkHistory } from './CkHistory.vue';
2
+ export type * from './types';
@@ -0,0 +1,72 @@
1
+ import type { Conversation } from '../../../types';
2
+
3
+ /**
4
+ * Composed conversation history list. Renders each conversation as a real
5
+ * `<a href>` row so middle-click, cmd/ctrl-click, and right-click "Copy link
6
+ * address" all work natively. Plain left-clicks are intercepted via a
7
+ * RouterLink-style guard and exposed as a `select` event so consumers can
8
+ * call `router.push(...)` (or any other navigation primitive) themselves.
9
+ *
10
+ * Per-row "Edit" and "Delete" actions are rendered internally; consumers
11
+ * receive `edit` / `delete` events with the corresponding conversation.
12
+ *
13
+ * Use the `#item` scoped slot to fully replace the row rendering — for
14
+ * example to wrap each row in `<RouterLink>` for native vue-router
15
+ * integration. The slot scope exposes the resolved row data
16
+ * (`conversation`, `href`, `isActive`, `title`) plus `select` / `edit` /
17
+ * `delete` helpers that emit the same events as the default rendering.
18
+ *
19
+ * @category Messages
20
+ * @useCases conversation history list, recent chats sidebar entry, thread navigation, chat history pane
21
+ * @keywords conversation, history, list, navigation, link, sidebar, threads, routerlink
22
+ * @related CkHistoryWrapper, CkHistoryItem
23
+ */
24
+ export interface CkHistoryProps {
25
+ /** Conversations to display, in the order they should render. */
26
+ conversations: Conversation[];
27
+ /**
28
+ * Resolves a conversation id to the URL set on each row's `<a href>`.
29
+ * Required so middle-click / cmd-click / right-click-copy work natively.
30
+ * Gives the consumer full control over URL shape (nested routes, query
31
+ * params, hash routes, locale prefixes, etc).
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const router = useRouter();
36
+ * const hrefResolver = (id: string) =>
37
+ * router.resolve({ name: 'chat', params: { id } }).href;
38
+ * ```
39
+ */
40
+ hrefResolver: (id: string) => string;
41
+ /** Id of the currently-active conversation; marks that row as active. */
42
+ activeId?: string;
43
+ /**
44
+ * Whether to show the trailing action kebab (edit / delete popover).
45
+ * @default true
46
+ */
47
+ showAction?: boolean;
48
+ /**
49
+ * Whether the trailing action is only revealed on row hover/focus-within.
50
+ * @default true
51
+ */
52
+ showActionOnHover?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Emitted events for `CkHistory`.
57
+ */
58
+ export interface CkHistoryEmits {
59
+ /**
60
+ * Fired on a plain left-click of a conversation row. Middle-click,
61
+ * cmd/ctrl/shift/alt-click, and right-click do NOT emit — they fall
62
+ * through to native browser behaviour so users can open the conversation
63
+ * in a new tab, copy the link, etc. The component calls
64
+ * `event.preventDefault()` before emitting so consumers can safely call
65
+ * `router.push(...)` in the handler.
66
+ */
67
+ select: [conversation: Conversation, event: MouseEvent];
68
+ /** Fired when the "Edit" action is selected from a row's popover menu. */
69
+ edit: [conversation: Conversation];
70
+ /** Fired when the "Delete" action is selected from a row's popover menu. */
71
+ delete: [conversation: Conversation];
72
+ }