@aleph-alpha/chat-kit 1.0.0 → 1.2.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-CRymGkQs.js} +24 -69
- 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/composables/index.d.ts +2 -0
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/index.js +3 -2
- package/dist/composables/useConversationSessions.d.ts +29 -0
- package/dist/composables/useConversationSessions.d.ts.map +1 -0
- package/dist/handlers/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/purify.es-md74IFlY.js +1081 -0
- package/dist/{useChatKitLabels-Dvi2FmjE.js → useChatKitLabels-BmWmMyqX.js} +75 -1152
- package/dist/{useConversations-Ca242D2P.js → useConversationSessions-ulFhtpQr.js} +50 -2
- package/dist/{useMessageHandler-uLHNac39.js → useMessageHandler-BmBdu2EW.js} +4 -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 +15 -0
- package/src/composables/types.ts +36 -0
- package/src/composables/useChat.ts +18 -0
- package/src/composables/useConversationSessions.spec.ts +86 -0
- package/src/composables/useConversationSessions.ts +85 -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 +86 -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
- package/dist/generateId-BHf0W4Rq.js +0 -6
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, useAttrs, useSlots, watch } from 'vue';
|
|
3
|
+
import {
|
|
4
|
+
Primitive,
|
|
5
|
+
UiIcon,
|
|
6
|
+
UiPopover,
|
|
7
|
+
UiPopoverContent,
|
|
8
|
+
UiPopoverTrigger,
|
|
9
|
+
UiTooltip,
|
|
10
|
+
} from '@aleph-alpha/ui-library';
|
|
11
|
+
import { CHAT_HISTORY_ACTIVE_INDICATOR_CLASS } from './activeIndicator';
|
|
12
|
+
import type { CkHistoryItemEmits, CkHistoryItemOwnProps } from './types';
|
|
13
|
+
|
|
14
|
+
defineOptions({
|
|
15
|
+
name: 'CkHistoryItem',
|
|
16
|
+
inheritAttrs: false,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits<CkHistoryItemEmits>();
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<CkHistoryItemOwnProps>(), {
|
|
22
|
+
isActive: false,
|
|
23
|
+
showAction: true,
|
|
24
|
+
showActionOnHover: true,
|
|
25
|
+
actionTooltip: '',
|
|
26
|
+
actionAriaLabel: 'History item action',
|
|
27
|
+
actionPopover: false,
|
|
28
|
+
actionPopoverSide: 'right',
|
|
29
|
+
actionPopoverAlign: 'start',
|
|
30
|
+
actionPopoverSideOffset: 8,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const attrs = useAttrs();
|
|
34
|
+
const slots = useSlots();
|
|
35
|
+
|
|
36
|
+
const hasMeta = computed(() =>
|
|
37
|
+
Boolean(props.metaStart || props.metaEnd || slots.meta),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* When true, pointer/focus is in the action column. The row-level hover
|
|
42
|
+
* fill is suppressed so the icon's own hover fill + outlined border
|
|
43
|
+
* don't stack on top of it.
|
|
44
|
+
*/
|
|
45
|
+
const pointerOnAction = ref(false);
|
|
46
|
+
|
|
47
|
+
function onActionPointerEnter() {
|
|
48
|
+
pointerOnAction.value = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function onActionPointerLeave() {
|
|
52
|
+
pointerOnAction.value = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function onActionFocusIn() {
|
|
56
|
+
pointerOnAction.value = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onActionFocusOut() {
|
|
60
|
+
pointerOnAction.value = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
watch(
|
|
64
|
+
() => props.showAction,
|
|
65
|
+
(show) => {
|
|
66
|
+
if (!show) {
|
|
67
|
+
pointerOnAction.value = false;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const containerClass = computed(() => [
|
|
73
|
+
'group/ck-history-item relative flex w-full min-w-0 items-stretch gap-2 rounded-md border border-transparent px-2 py-1.5 transition-colors',
|
|
74
|
+
hasMeta.value ? 'h-12' : 'h-10',
|
|
75
|
+
// Whole-row focus ring: first tab lands on the main row control; second tab on the action button — both are inside this container.
|
|
76
|
+
// `:has(:focus-visible)` instead of `:focus-within` so mouse clicks on the row don't flash a keyboard focus ring.
|
|
77
|
+
'has-[:focus-visible]:border-border-surface-focus-ring has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50',
|
|
78
|
+
// Row hover/active fill. Hover is suppressed while the
|
|
79
|
+
// pointer/focus is on the action column so the icon's own fill does not stack on top.
|
|
80
|
+
(props.isActive || !pointerOnAction.value) &&
|
|
81
|
+
'hover:bg-background-surface-muted-subtle',
|
|
82
|
+
props.isActive && 'bg-background-surface-muted-subtle',
|
|
83
|
+
CHAT_HISTORY_ACTIVE_INDICATOR_CLASS,
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const itemClass = computed(() => [
|
|
87
|
+
'text-left outline-hidden transition-colors',
|
|
88
|
+
'body-14-regular text-content-on-surface-primary',
|
|
89
|
+
'!hover:bg-transparent !active:bg-transparent',
|
|
90
|
+
// Suppress a second ring on the primary control; the row ring is on the outer container.
|
|
91
|
+
'focus-visible:ring-0 focus-visible:outline-none',
|
|
92
|
+
'data-[active=true]:text-content-on-surface-primary',
|
|
93
|
+
// Stretched-link: extend the primary control's hit area to cover the full row
|
|
94
|
+
// container (including its `px-2 py-1.5` padding and the gap before the action
|
|
95
|
+
// column) so clicks anywhere on the row activate the item. The action button
|
|
96
|
+
// stays interactive because it comes later in the DOM and is `relative`.
|
|
97
|
+
'before:content-[""] before:absolute before:inset-0 before:z-0',
|
|
98
|
+
'flex min-w-0 flex-1 items-center',
|
|
99
|
+
attrs.class as unknown,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Wrapper column that owns the action button and the `pointerenter` /
|
|
104
|
+
* `pointerleave` hit area driving `pointerOnAction`. Uses `self-stretch` so
|
|
105
|
+
* the full row height reports pointer-enter on the column (not just the
|
|
106
|
+
* 32×32 button), and — when `showActionOnHover` is true — hides the inner
|
|
107
|
+
* button with `opacity-0` until the row is hovered or keyboard focus lands
|
|
108
|
+
* anywhere inside it.
|
|
109
|
+
*/
|
|
110
|
+
const actionColumnClass = computed(() => [
|
|
111
|
+
'ml-auto flex shrink-0 items-center self-stretch',
|
|
112
|
+
props.showActionOnHover
|
|
113
|
+
? '[&_button]:opacity-0 [&_button]:transition-opacity group-hover/ck-history-item:[&_button]:opacity-100 group-focus-within/ck-history-item:[&_button]:opacity-100'
|
|
114
|
+
: '',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Action control: same hover fill as the row, plus the
|
|
119
|
+
* `border/button/outlined` token so the icon picks up a visible outline on
|
|
120
|
+
* hover/focus (mirrors the outline button variant). Applied only while
|
|
121
|
+
* pointer/focus is on the action column so the row tint does not stack
|
|
122
|
+
* underneath the icon.
|
|
123
|
+
*/
|
|
124
|
+
const actionButtonClass = computed(() => [
|
|
125
|
+
'relative z-10 inline-flex size-8 shrink-0 items-center justify-center rounded-sm border border-transparent text-content-on-surface-primary outline-hidden transition-colors',
|
|
126
|
+
'[&_svg]:size-4 [&_svg]:shrink-0',
|
|
127
|
+
'focus-visible:ring-0 focus-visible:outline-none',
|
|
128
|
+
pointerOnAction.value &&
|
|
129
|
+
'bg-background-surface-muted-subtle border-border-button-outlined',
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
// Popover panel surface is `background/surface/secondary` to match the
|
|
133
|
+
// look of the original sidebar chat-history item popover. This is
|
|
134
|
+
// intentional for visual parity and can be overridden via
|
|
135
|
+
// `actionPopoverContentClass`.
|
|
136
|
+
const actionPopoverPanelClass = computed(() => [
|
|
137
|
+
'w-auto min-w-48 max-w-sm p-0 bg-background-surface-secondary',
|
|
138
|
+
props.actionPopoverContentClass,
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const passthroughAttrs = computed(() => {
|
|
142
|
+
const {
|
|
143
|
+
class: _class,
|
|
144
|
+
as: _as,
|
|
145
|
+
asChild: _asChild,
|
|
146
|
+
'as-child': _asChildAttr,
|
|
147
|
+
...rest
|
|
148
|
+
} = attrs as Record<string, unknown>;
|
|
149
|
+
return rest;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const primitiveAs = computed(() => attrs.as ?? 'button');
|
|
153
|
+
const primitiveAsChild = computed(
|
|
154
|
+
() =>
|
|
155
|
+
attrs.asChild === true ||
|
|
156
|
+
attrs.asChild === '' ||
|
|
157
|
+
attrs['as-child'] === true ||
|
|
158
|
+
attrs['as-child'] === '',
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
function onActionClick(event: MouseEvent) {
|
|
162
|
+
event.stopPropagation();
|
|
163
|
+
const el = event.currentTarget;
|
|
164
|
+
if (el instanceof HTMLElement && el.focus) {
|
|
165
|
+
queueMicrotask(() => el.focus({ preventScroll: true }));
|
|
166
|
+
}
|
|
167
|
+
if (!props.actionPopover) {
|
|
168
|
+
// Public kebab-case event preserved for API parity.
|
|
169
|
+
|
|
170
|
+
emit('action-click', event);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<template>
|
|
176
|
+
<div :data-active="props.isActive" :class="containerClass">
|
|
177
|
+
<Primitive
|
|
178
|
+
v-bind="passthroughAttrs"
|
|
179
|
+
:as="primitiveAs"
|
|
180
|
+
:as-child="primitiveAsChild"
|
|
181
|
+
:data-active="props.isActive"
|
|
182
|
+
:class="itemClass"
|
|
183
|
+
>
|
|
184
|
+
<div class="min-w-0 flex-1">
|
|
185
|
+
<div class="truncate body-14-regular text-content-on-surface-primary">
|
|
186
|
+
{{ props.title }}
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div
|
|
190
|
+
v-if="props.metaStart || props.metaEnd || $slots.meta"
|
|
191
|
+
class="mt-0.5 flex min-w-0 items-center gap-1 label-12-medium text-content-on-surface-muted"
|
|
192
|
+
>
|
|
193
|
+
<slot name="meta">
|
|
194
|
+
<span v-if="props.metaStart" class="truncate">{{
|
|
195
|
+
props.metaStart
|
|
196
|
+
}}</span>
|
|
197
|
+
<span v-if="props.metaStart && props.metaEnd" aria-hidden="true"
|
|
198
|
+
>|</span
|
|
199
|
+
>
|
|
200
|
+
<span v-if="props.metaEnd" class="truncate">{{
|
|
201
|
+
props.metaEnd
|
|
202
|
+
}}</span>
|
|
203
|
+
</slot>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</Primitive>
|
|
207
|
+
|
|
208
|
+
<div
|
|
209
|
+
v-if="props.showAction"
|
|
210
|
+
:class="actionColumnClass"
|
|
211
|
+
@pointerenter="onActionPointerEnter"
|
|
212
|
+
@pointerleave="onActionPointerLeave"
|
|
213
|
+
>
|
|
214
|
+
<UiPopover v-if="props.actionPopover">
|
|
215
|
+
<UiPopoverTrigger as-child>
|
|
216
|
+
<button
|
|
217
|
+
type="button"
|
|
218
|
+
:aria-label="props.actionAriaLabel"
|
|
219
|
+
:aria-haspopup="'dialog'"
|
|
220
|
+
:class="actionButtonClass"
|
|
221
|
+
@focusin="onActionFocusIn"
|
|
222
|
+
@focusout="onActionFocusOut"
|
|
223
|
+
@click.stop="onActionClick"
|
|
224
|
+
>
|
|
225
|
+
<component
|
|
226
|
+
:is="props.actionIcon"
|
|
227
|
+
v-if="props.actionIcon"
|
|
228
|
+
:size="16"
|
|
229
|
+
aria-hidden="true"
|
|
230
|
+
/>
|
|
231
|
+
<UiIcon v-else name="more-vertical" :size="16" aria-hidden="true" />
|
|
232
|
+
</button>
|
|
233
|
+
</UiPopoverTrigger>
|
|
234
|
+
<UiPopoverContent
|
|
235
|
+
:side="props.actionPopoverSide"
|
|
236
|
+
:align="props.actionPopoverAlign"
|
|
237
|
+
:side-offset="props.actionPopoverSideOffset"
|
|
238
|
+
:class="actionPopoverPanelClass"
|
|
239
|
+
>
|
|
240
|
+
<slot name="action-popover" />
|
|
241
|
+
</UiPopoverContent>
|
|
242
|
+
</UiPopover>
|
|
243
|
+
|
|
244
|
+
<UiTooltip
|
|
245
|
+
v-else-if="props.actionTooltip"
|
|
246
|
+
:content="props.actionTooltip"
|
|
247
|
+
side="right"
|
|
248
|
+
>
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
:aria-label="props.actionAriaLabel"
|
|
252
|
+
:class="actionButtonClass"
|
|
253
|
+
@focusin="onActionFocusIn"
|
|
254
|
+
@focusout="onActionFocusOut"
|
|
255
|
+
@click.stop="onActionClick"
|
|
256
|
+
>
|
|
257
|
+
<component
|
|
258
|
+
:is="props.actionIcon"
|
|
259
|
+
v-if="props.actionIcon"
|
|
260
|
+
:size="16"
|
|
261
|
+
aria-hidden="true"
|
|
262
|
+
/>
|
|
263
|
+
<UiIcon v-else name="more-vertical" :size="16" aria-hidden="true" />
|
|
264
|
+
</button>
|
|
265
|
+
</UiTooltip>
|
|
266
|
+
<button
|
|
267
|
+
v-else
|
|
268
|
+
type="button"
|
|
269
|
+
:aria-label="props.actionAriaLabel"
|
|
270
|
+
:class="actionButtonClass"
|
|
271
|
+
@focusin="onActionFocusIn"
|
|
272
|
+
@focusout="onActionFocusOut"
|
|
273
|
+
@click.stop="onActionClick"
|
|
274
|
+
>
|
|
275
|
+
<component
|
|
276
|
+
:is="props.actionIcon"
|
|
277
|
+
v-if="props.actionIcon"
|
|
278
|
+
:size="16"
|
|
279
|
+
aria-hidden="true"
|
|
280
|
+
/>
|
|
281
|
+
<UiIcon v-else name="more-vertical" :size="16" aria-hidden="true" />
|
|
282
|
+
</button>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared class string for the left-edge active indicator used by a chat
|
|
3
|
+
* history row.
|
|
4
|
+
*
|
|
5
|
+
* The `::before` pseudo-element is always mounted so CSS has something to
|
|
6
|
+
* transition. At rest it's painted from `scale-x-0` + `opacity-0` with its
|
|
7
|
+
* transform anchored on the left edge (`origin-left`); when the row's
|
|
8
|
+
* `data-active="true"` flips on, the bar paints in from left to right while
|
|
9
|
+
* fading in, using a 200ms linear curve.
|
|
10
|
+
*
|
|
11
|
+
* Kept as a string constant (rather than generated via `cn`) so UnoCSS's
|
|
12
|
+
* static scanner picks up every utility class for atomic CSS generation.
|
|
13
|
+
*/
|
|
14
|
+
export const CHAT_HISTORY_ACTIVE_INDICATOR_CLASS =
|
|
15
|
+
'before:absolute before:z-10 before:top-1/2 before:left-0 before:h-1/2 before:w-0.5 before:-translate-y-1/2 before:origin-left before:scale-x-0 before:rounded-full before:bg-background-accent-default before:opacity-0 before:transition-[transform,opacity] before:duration-200 before:ease-linear before:content-[""] data-[active=true]:before:scale-x-100 data-[active=true]:before:opacity-100';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Component, HTMLAttributes } from 'vue';
|
|
2
|
+
import type { PrimitiveProps } from '@aleph-alpha/ui-library';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single chat history entry with title, optional meta line, active
|
|
6
|
+
* indicator, and a trailing action control (plain button or popover).
|
|
7
|
+
* Sidebar-agnostic: can be used anywhere, not only inside a sidebar.
|
|
8
|
+
*
|
|
9
|
+
* @category Actions
|
|
10
|
+
* @useCases chat history row, thread list entry, recent conversation item
|
|
11
|
+
* @keywords chat, history, item, row, conversation, thread
|
|
12
|
+
* @related CkHistoryWrapper
|
|
13
|
+
*/
|
|
14
|
+
export type CkHistoryItemProps = PrimitiveProps & CkHistoryItemOwnProps;
|
|
15
|
+
|
|
16
|
+
export interface CkHistoryItemOwnProps {
|
|
17
|
+
/**
|
|
18
|
+
* Main title of the history item.
|
|
19
|
+
*/
|
|
20
|
+
title: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* First metadata label shown under the title.
|
|
24
|
+
*/
|
|
25
|
+
metaStart?: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Second metadata label shown under the title.
|
|
29
|
+
*/
|
|
30
|
+
metaEnd?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Marks the item as active (shows the left-edge rail indicator and
|
|
34
|
+
* background tint).
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
isActive?: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether to render the end action button.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
showAction?: boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether the end action should only appear on hover/focus.
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
showActionOnHover?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tooltip for the end action button.
|
|
53
|
+
*/
|
|
54
|
+
actionTooltip?: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Accessible label for the end action button.
|
|
58
|
+
* @default 'History item action'
|
|
59
|
+
*/
|
|
60
|
+
actionAriaLabel?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Custom action icon rendered at the end of the row. Defaults to a
|
|
64
|
+
* `more-vertical` icon.
|
|
65
|
+
*/
|
|
66
|
+
actionIcon?: Component;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* When true, the action control opens a popover (use `#action-popover`
|
|
70
|
+
* slot for the body). Tooltip on the action is not used in this mode;
|
|
71
|
+
* set `actionAriaLabel` for accessibility.
|
|
72
|
+
* @default false
|
|
73
|
+
*/
|
|
74
|
+
actionPopover?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Popover side relative to the action trigger.
|
|
78
|
+
* @default 'right'
|
|
79
|
+
*/
|
|
80
|
+
actionPopoverSide?: 'top' | 'right' | 'bottom' | 'left';
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Popover alignment relative to the trigger.
|
|
84
|
+
* @default 'start'
|
|
85
|
+
*/
|
|
86
|
+
actionPopoverAlign?: 'start' | 'center' | 'end';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Offset between trigger and popover content.
|
|
90
|
+
* @default 8
|
|
91
|
+
*/
|
|
92
|
+
actionPopoverSideOffset?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extra classes for the popover panel (merged with pattern defaults).
|
|
96
|
+
*/
|
|
97
|
+
actionPopoverContentClass?: HTMLAttributes['class'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Emitted events for `CkHistoryItem`.
|
|
102
|
+
*/
|
|
103
|
+
export interface CkHistoryItemEmits {
|
|
104
|
+
/**
|
|
105
|
+
* Fired when the end action button is clicked.
|
|
106
|
+
* Not emitted when `actionPopover` is `true` — in that mode the click
|
|
107
|
+
* opens the popover and consumers react to interactions inside the
|
|
108
|
+
* `#action-popover` slot.
|
|
109
|
+
*/
|
|
110
|
+
'action-click': [event: MouseEvent];
|
|
111
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render } from '@testing-library/vue';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import CkHistoryWrapper from './CkHistoryWrapper.vue';
|
|
4
|
+
|
|
5
|
+
describe('CkHistoryWrapper', () => {
|
|
6
|
+
it('sets data-slot and applies gap class by default', () => {
|
|
7
|
+
const { container } = render(CkHistoryWrapper, {
|
|
8
|
+
slots: { default: '<span>x</span>' },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const root = container.querySelector('[data-slot="chat-history"]');
|
|
12
|
+
expect(root).toBeTruthy();
|
|
13
|
+
expect(root?.className).toMatch(/gap-1/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('omits gap class when withGap is false', () => {
|
|
17
|
+
const { container } = render(CkHistoryWrapper, {
|
|
18
|
+
props: { withGap: false },
|
|
19
|
+
slots: { default: '<span>y</span>' },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const root = container.querySelector('[data-slot="chat-history"]');
|
|
23
|
+
expect(root).toBeTruthy();
|
|
24
|
+
expect(root?.className).not.toMatch(/gap-1/);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import CkHistoryWrapper from './CkHistoryWrapper.vue';
|
|
4
|
+
import { CkHistoryItem } from '../CkHistoryItem';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof CkHistoryWrapper> = {
|
|
7
|
+
title: 'Base/CkHistoryWrapper',
|
|
8
|
+
component: CkHistoryWrapper,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'padded',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'Vertical stack for chat history rows (`CkHistoryItem`). Sidebar-agnostic — use anywhere in the app. Toggle **`withGap`** to add spacing between items.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
argTypes: {
|
|
20
|
+
withGap: {
|
|
21
|
+
control: 'boolean',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof CkHistoryWrapper>;
|
|
29
|
+
|
|
30
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
31
|
+
import { CkHistoryWrapper, CkHistoryItem } from '@aleph-alpha/chat-kit';
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<CkHistoryWrapper>
|
|
36
|
+
<CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
|
|
37
|
+
<CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" is-active />
|
|
38
|
+
<CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
|
|
39
|
+
<CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
|
|
40
|
+
</CkHistoryWrapper>
|
|
41
|
+
</template>`;
|
|
42
|
+
|
|
43
|
+
const withoutGapTemplateSource = `<script setup lang="ts">
|
|
44
|
+
import { CkHistoryWrapper, CkHistoryItem } from '@aleph-alpha/chat-kit';
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<CkHistoryWrapper :with-gap="false">
|
|
49
|
+
<CkHistoryItem title="Thread A" meta-start="Today" />
|
|
50
|
+
<CkHistoryItem title="Thread B" meta-start="Yesterday" />
|
|
51
|
+
<CkHistoryItem title="Thread C" meta-start="Last week" />
|
|
52
|
+
</CkHistoryWrapper>
|
|
53
|
+
</template>`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default wrapper with the small vertical gap (`gap-1`) applied between
|
|
57
|
+
* child rows. Use as the container for any list of `CkHistoryItem`s.
|
|
58
|
+
*/
|
|
59
|
+
export const Default: Story = {
|
|
60
|
+
render: () => ({
|
|
61
|
+
components: { CkHistoryWrapper, CkHistoryItem },
|
|
62
|
+
setup() {
|
|
63
|
+
const actionCount = ref(0);
|
|
64
|
+
const items = [
|
|
65
|
+
{
|
|
66
|
+
title: 'Project Proposal Overview',
|
|
67
|
+
metaStart: 'Today',
|
|
68
|
+
metaEnd: 'Innsbruck',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Project Proposal Overview',
|
|
72
|
+
metaStart: 'Today',
|
|
73
|
+
metaEnd: 'Innsbruck',
|
|
74
|
+
isActive: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: 'Project Proposal Overview',
|
|
78
|
+
metaStart: 'Today',
|
|
79
|
+
metaEnd: 'Innsbruck',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: 'Project Proposal Overview',
|
|
83
|
+
metaStart: 'Today',
|
|
84
|
+
metaEnd: 'Innsbruck',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
function handleActionClick() {
|
|
89
|
+
actionCount.value += 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { actionCount, handleActionClick, items };
|
|
93
|
+
},
|
|
94
|
+
template: `
|
|
95
|
+
<div class="bg-background-surface-primary p-4 w-[22rem]">
|
|
96
|
+
<CkHistoryWrapper>
|
|
97
|
+
<CkHistoryItem
|
|
98
|
+
v-for="(item, index) in items"
|
|
99
|
+
:key="index"
|
|
100
|
+
:title="item.title"
|
|
101
|
+
:meta-start="item.metaStart"
|
|
102
|
+
:meta-end="item.metaEnd"
|
|
103
|
+
:is-active="item.isActive"
|
|
104
|
+
action-tooltip="More actions"
|
|
105
|
+
@action-click="handleActionClick"
|
|
106
|
+
/>
|
|
107
|
+
</CkHistoryWrapper>
|
|
108
|
+
<p class="label-12-regular mt-3 text-content-on-surface-muted">
|
|
109
|
+
Action clicks: {{ actionCount }}
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
`,
|
|
113
|
+
}),
|
|
114
|
+
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Disables the inter-item gap (`with-gap="false"`). Use for dense lists
|
|
119
|
+
* where items should sit flush against each other.
|
|
120
|
+
*/
|
|
121
|
+
export const WithoutGap: Story = {
|
|
122
|
+
args: {
|
|
123
|
+
withGap: false,
|
|
124
|
+
},
|
|
125
|
+
render: (args) => ({
|
|
126
|
+
components: { CkHistoryWrapper, CkHistoryItem },
|
|
127
|
+
setup() {
|
|
128
|
+
const items = [
|
|
129
|
+
{ title: 'Thread A', metaStart: 'Today' },
|
|
130
|
+
{ title: 'Thread B', metaStart: 'Yesterday' },
|
|
131
|
+
{ title: 'Thread C', metaStart: 'Last week' },
|
|
132
|
+
];
|
|
133
|
+
return { args, items };
|
|
134
|
+
},
|
|
135
|
+
template: `
|
|
136
|
+
<div class="bg-background-surface-primary p-4 w-[22rem]">
|
|
137
|
+
<CkHistoryWrapper v-bind="args">
|
|
138
|
+
<CkHistoryItem
|
|
139
|
+
v-for="(item, index) in items"
|
|
140
|
+
:key="index"
|
|
141
|
+
:title="item.title"
|
|
142
|
+
:meta-start="item.metaStart"
|
|
143
|
+
/>
|
|
144
|
+
</CkHistoryWrapper>
|
|
145
|
+
</div>
|
|
146
|
+
`,
|
|
147
|
+
}),
|
|
148
|
+
parameters: { docs: { source: { code: withoutGapTemplateSource } } },
|
|
149
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue';
|
|
3
|
+
import type { CkHistoryWrapperProps } from './types';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'CkHistoryWrapper',
|
|
7
|
+
inheritAttrs: false,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<CkHistoryWrapperProps>(), {
|
|
11
|
+
withGap: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const attrs = useAttrs();
|
|
15
|
+
|
|
16
|
+
const baseClass = 'flex w-full min-w-0 flex-col p-2';
|
|
17
|
+
|
|
18
|
+
const rootClass = computed(() => {
|
|
19
|
+
const consumerClass =
|
|
20
|
+
typeof attrs.class === 'string' ? attrs.class : undefined;
|
|
21
|
+
return [baseClass, props.withGap ? 'gap-1' : undefined, consumerClass]
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.join(' ');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const passthroughAttrs = computed(() => {
|
|
27
|
+
const { class: _class, ...rest } = attrs as Record<string, unknown>;
|
|
28
|
+
return rest;
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div v-bind="passthroughAttrs" data-slot="chat-history" :class="rootClass">
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertical stack for chat history rows (`CkHistoryItem`). Sidebar-agnostic:
|
|
3
|
+
* can be used anywhere in the app, not only inside a sidebar.
|
|
4
|
+
*
|
|
5
|
+
* @category Layout
|
|
6
|
+
* @useCases chat history list, thread list container, recent conversations stack
|
|
7
|
+
* @keywords chat, history, list, stack, container
|
|
8
|
+
* @related CkHistoryItem
|
|
9
|
+
*/
|
|
10
|
+
export interface CkHistoryWrapperProps {
|
|
11
|
+
/**
|
|
12
|
+
* When true, applies a small vertical gap (`gap-1`) between child items.
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
withGap?: boolean;
|
|
16
|
+
}
|