@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.
- package/dist/{CkInput.vue_vue_type_script_setup_true_lang-744ZHsQU.js → CkInput.vue_vue_type_script_setup_true_lang-DpYyyCgU.js} +23 -68
- package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts +3 -4
- package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -1
- package/dist/components/base/CkHistoryItem/types.d.ts +3 -2
- package/dist/components/base/CkHistoryItem/types.d.ts.map +1 -1
- package/dist/components/base/CkPromptStopButton/CkPromptStopButton.vue.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +91 -0
- package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +215 -0
- package/src/components/base/CkAgentMessage/CkAgentMessage.vue +25 -0
- package/src/components/base/CkAgentMessage/index.ts +2 -0
- package/src/components/base/CkAgentMessage/types.ts +11 -0
- package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +129 -0
- package/src/components/base/CkConversationLayout/CkConversationLayout.stories.ts +251 -0
- package/src/components/base/CkConversationLayout/CkConversationLayout.vue +206 -0
- package/src/components/base/CkConversationLayout/index.ts +2 -0
- package/src/components/base/CkConversationLayout/types.ts +18 -0
- package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts +89 -0
- package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts +70 -0
- package/src/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue +110 -0
- package/src/components/base/CkCopyMessageActionButton/index.ts +2 -0
- package/src/components/base/CkCopyMessageActionButton/types.ts +43 -0
- package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +42 -0
- package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +230 -0
- package/src/components/base/CkHistoryItem/CkHistoryItem.vue +285 -0
- package/src/components/base/CkHistoryItem/activeIndicator.ts +15 -0
- package/src/components/base/CkHistoryItem/index.ts +2 -0
- package/src/components/base/CkHistoryItem/types.ts +111 -0
- package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.spec.ts +26 -0
- package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +149 -0
- package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +36 -0
- package/src/components/base/CkHistoryWrapper/index.ts +2 -0
- package/src/components/base/CkHistoryWrapper/types.ts +16 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +348 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +382 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +89 -0
- package/src/components/base/CkMarkdownRenderer/defaultMarkdownClasses.ts +19 -0
- package/src/components/base/CkMarkdownRenderer/index.ts +5 -0
- package/src/components/base/CkMarkdownRenderer/types.ts +53 -0
- package/src/components/base/CkMessageActionButton/CkMessageActionButton.spec.ts +40 -0
- package/src/components/base/CkMessageActionButton/CkMessageActionButton.stories.ts +56 -0
- package/src/components/base/CkMessageActionButton/CkMessageActionButton.vue +23 -0
- package/src/components/base/CkMessageActionButton/index.ts +2 -0
- package/src/components/base/CkMessageActionButton/types.ts +21 -0
- package/src/components/base/CkPromptActionButton/CkPromptActionButton.spec.ts +52 -0
- package/src/components/base/CkPromptActionButton/CkPromptActionButton.stories.ts +126 -0
- package/src/components/base/CkPromptActionButton/CkPromptActionButton.vue +25 -0
- package/src/components/base/CkPromptActionButton/index.ts +2 -0
- package/src/components/base/CkPromptActionButton/types.ts +32 -0
- package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +181 -0
- package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +224 -0
- package/src/components/base/CkPromptInput/CkPromptInput.vue +104 -0
- package/src/components/base/CkPromptInput/index.ts +2 -0
- package/src/components/base/CkPromptInput/types.ts +39 -0
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +40 -0
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.ts +103 -0
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +16 -0
- package/src/components/base/CkPromptInputDisclaimer/index.ts +2 -0
- package/src/components/base/CkPromptInputDisclaimer/types.ts +17 -0
- package/src/components/base/CkPromptStopButton/CkPromptStopButton.spec.ts +64 -0
- package/src/components/base/CkPromptStopButton/CkPromptStopButton.stories.ts +81 -0
- package/src/components/base/CkPromptStopButton/CkPromptStopButton.vue +27 -0
- package/src/components/base/CkPromptStopButton/index.ts +2 -0
- package/src/components/base/CkPromptStopButton/types.ts +28 -0
- package/src/components/base/CkUserMessage/CkUserMessage.spec.ts +20 -0
- package/src/components/base/CkUserMessage/CkUserMessage.stories.ts +112 -0
- package/src/components/base/CkUserMessage/CkUserMessage.vue +19 -0
- package/src/components/base/CkUserMessage/index.ts +2 -0
- package/src/components/base/CkUserMessage/types.ts +14 -0
- package/src/components/base/icons/CkSendIcon.vue +22 -0
- package/src/components/base/icons/CkStopIcon.vue +18 -0
- package/src/components/base/icons/index.ts +2 -0
- package/src/components/base/index.ts +21 -0
- package/src/components/composed/CkConversation/CkConversation.spec.ts +439 -0
- package/src/components/composed/CkConversation/CkConversation.stories.ts +642 -0
- package/src/components/composed/CkConversation/CkConversation.vue +146 -0
- package/src/components/composed/CkConversation/index.ts +2 -0
- package/src/components/composed/CkConversation/types.ts +37 -0
- package/src/components/composed/CkHistory/CkHistory.spec.ts +327 -0
- package/src/components/composed/CkHistory/CkHistory.stories.ts +373 -0
- package/src/components/composed/CkHistory/CkHistory.vue +126 -0
- package/src/components/composed/CkHistory/index.ts +2 -0
- package/src/components/composed/CkHistory/types.ts +72 -0
- package/src/components/composed/CkInput/CkInput.spec.ts +191 -0
- package/src/components/composed/CkInput/CkInput.stories.ts +438 -0
- package/src/components/composed/CkInput/CkInput.vue +104 -0
- package/src/components/composed/CkInput/index.ts +2 -0
- package/src/components/composed/CkInput/types.ts +60 -0
- package/src/components/composed/index.ts +5 -0
- package/src/components/index.ts +34 -0
- package/src/composables/index.ts +9 -0
- package/src/composables/types.ts +36 -0
- package/src/composables/useChat.ts +18 -0
- package/src/composables/useConversations.spec.ts +557 -0
- package/src/composables/useConversations.ts +321 -0
- package/src/handlers/index.ts +9 -0
- package/src/handlers/useChatKitLabels.spec.ts +290 -0
- package/src/handlers/useChatKitLabels.ts +96 -0
- package/src/handlers/useMarkdownParser.spec.ts +112 -0
- package/src/handlers/useMarkdownParser.ts +47 -0
- package/src/handlers/useMessageHandler.spec.ts +535 -0
- package/src/handlers/useMessageHandler.ts +138 -0
- package/src/helpers/conversationTree.spec.ts +423 -0
- package/src/helpers/conversationTree.ts +117 -0
- package/src/helpers/generateId.ts +5 -0
- package/src/helpers/index.ts +4 -0
- package/src/helpers/secondsToDate.ts +7 -0
- package/src/helpers/toError.ts +8 -0
- package/src/index.ts +79 -0
- package/src/services/ChatService.ts +60 -0
- package/src/services/MockChatService.ts +47 -0
- package/src/services/index.ts +12 -0
- package/src/services/types.ts +92 -0
- package/src/test-setup.ts +12 -0
- package/src/test-utils/createTextPlugin.ts +9 -0
- 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,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
|
+
}
|