@aleph-alpha/chat-kit 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CkInput.vue_vue_type_script_setup_true_lang-BVZ7FG88.js → CkInput.vue_vue_type_script_setup_true_lang-DedLzDoO.js} +157 -82
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts.map +1 -1
- package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -1
- package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts.map +1 -1
- package/dist/components/composed/CkHistory/CkHistory.stories.d.ts +15 -0
- package/dist/components/composed/CkHistory/CkHistory.stories.d.ts.map +1 -1
- package/dist/components/composed/CkHistory/CkHistory.vue.d.ts +6 -0
- package/dist/components/composed/CkHistory/CkHistory.vue.d.ts.map +1 -1
- package/dist/components/composed/CkHistory/types.d.ts +33 -0
- package/dist/components/composed/CkHistory/types.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/handlers/index.js +1 -1
- package/dist/handlers/useChatKitLabels.d.ts +5 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/helpers/formatRelativeTime.d.ts +10 -0
- package/dist/helpers/formatRelativeTime.d.ts.map +1 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/{useChatKitLabels-BxNkTzr2.js → useChatKitLabels-BC6ItJc6.js} +2 -1
- package/package.json +1 -1
- package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +6 -12
- package/src/components/base/CkAgentMessage/CkAgentMessage.vue +24 -15
- package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +0 -2
- package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +0 -6
- package/src/components/base/CkHistoryItem/CkHistoryItem.vue +2 -3
- package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +23 -23
- package/src/components/composed/CkConversation/CkConversation.spec.ts +8 -35
- package/src/components/composed/CkHistory/CkHistory.spec.ts +107 -0
- package/src/components/composed/CkHistory/CkHistory.stories.ts +91 -3
- package/src/components/composed/CkHistory/CkHistory.vue +94 -44
- package/src/components/composed/CkHistory/types.ts +33 -0
- package/src/handlers/useChatKitLabels.spec.ts +1 -0
- package/src/handlers/useChatKitLabels.ts +6 -0
- package/src/helpers/formatRelativeTime.ts +49 -0
- package/src/helpers/index.ts +1 -0
- package/src/index.ts +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
2
3
|
import {
|
|
3
4
|
UiIcon,
|
|
4
5
|
UiSidebarPopoverMenu,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
import CkHistoryItem from '../../base/CkHistoryItem/CkHistoryItem.vue';
|
|
8
9
|
import CkHistoryWrapper from '../../base/CkHistoryWrapper/CkHistoryWrapper.vue';
|
|
9
10
|
import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
|
|
11
|
+
import { formatRelativeTime } from '../../../helpers/formatRelativeTime';
|
|
10
12
|
import type { Conversation } from '../../../types';
|
|
11
13
|
import type { CkHistoryEmits, CkHistoryProps } from './types';
|
|
12
14
|
|
|
@@ -16,6 +18,9 @@ const props = withDefaults(defineProps<CkHistoryProps>(), {
|
|
|
16
18
|
activeId: undefined,
|
|
17
19
|
showAction: true,
|
|
18
20
|
showActionOnHover: true,
|
|
21
|
+
timeFormatter: formatRelativeTime,
|
|
22
|
+
collapsedLimit: 5,
|
|
23
|
+
maxHeight: undefined,
|
|
19
24
|
});
|
|
20
25
|
|
|
21
26
|
const emit = defineEmits<CkHistoryEmits>();
|
|
@@ -31,6 +36,8 @@ defineSlots<{
|
|
|
31
36
|
* - `href` — URL produced by `hrefResolver(conversation.id)`
|
|
32
37
|
* - `isActive` — whether this row matches `activeId`
|
|
33
38
|
* - `title` — resolved title (falls back to `historyItemUntitled` label)
|
|
39
|
+
* - `time` — formatted relative time string from `timeFormatter`, or
|
|
40
|
+
* `undefined` when no formatter is set
|
|
34
41
|
* - `select(event)` — RouterLink-style click guard: `preventDefault()`
|
|
35
42
|
* and emit `select` for plain left-clicks; ignores middle/modifier
|
|
36
43
|
* clicks so native browser affordances still work.
|
|
@@ -41,6 +48,7 @@ defineSlots<{
|
|
|
41
48
|
href: string;
|
|
42
49
|
isActive: boolean;
|
|
43
50
|
title: string;
|
|
51
|
+
time: string | undefined;
|
|
44
52
|
select: (event: MouseEvent) => void;
|
|
45
53
|
edit: () => void;
|
|
46
54
|
delete: () => void;
|
|
@@ -49,6 +57,23 @@ defineSlots<{
|
|
|
49
57
|
|
|
50
58
|
const labels = useChatKitLabels();
|
|
51
59
|
|
|
60
|
+
const isExpanded = ref(false);
|
|
61
|
+
|
|
62
|
+
const canCollapse = computed(
|
|
63
|
+
() =>
|
|
64
|
+
props.collapsedLimit > 0 &&
|
|
65
|
+
props.conversations.length > props.collapsedLimit,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const visibleConversations = computed(() => {
|
|
69
|
+
if (!canCollapse.value || isExpanded.value) return props.conversations;
|
|
70
|
+
return props.conversations.slice(0, props.collapsedLimit);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function toggleExpanded() {
|
|
74
|
+
isExpanded.value = !isExpanded.value;
|
|
75
|
+
}
|
|
76
|
+
|
|
52
77
|
function hrefFor(c: Conversation) {
|
|
53
78
|
return props.hrefResolver(c.id);
|
|
54
79
|
}
|
|
@@ -58,6 +83,10 @@ function titleFor(c: Conversation) {
|
|
|
58
83
|
return title ? title : labels.value.historyItemUntitled;
|
|
59
84
|
}
|
|
60
85
|
|
|
86
|
+
function timeFor(c: Conversation): string | undefined {
|
|
87
|
+
return props.timeFormatter?.(c.updatedAt);
|
|
88
|
+
}
|
|
89
|
+
|
|
61
90
|
/**
|
|
62
91
|
* RouterLink-style click guard: only emit `select` for plain left-clicks.
|
|
63
92
|
* Middle-click, modifier-clicks, and right-click fall through to the
|
|
@@ -74,53 +103,74 @@ function handleRowClick(c: Conversation, event: MouseEvent) {
|
|
|
74
103
|
</script>
|
|
75
104
|
|
|
76
105
|
<template>
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
:
|
|
106
|
+
<div>
|
|
107
|
+
<CkHistoryWrapper
|
|
108
|
+
:class="isExpanded && props.maxHeight ? 'overflow-y-auto' : undefined"
|
|
109
|
+
:style="
|
|
110
|
+
isExpanded && props.maxHeight
|
|
111
|
+
? { maxHeight: props.maxHeight }
|
|
112
|
+
: undefined
|
|
113
|
+
"
|
|
81
114
|
>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
:
|
|
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)"
|
|
115
|
+
<template
|
|
116
|
+
v-for="conversation in visibleConversations"
|
|
117
|
+
:key="conversation.id"
|
|
91
118
|
>
|
|
92
|
-
<
|
|
93
|
-
|
|
119
|
+
<slot
|
|
120
|
+
name="item"
|
|
121
|
+
:conversation="conversation"
|
|
94
122
|
:href="hrefFor(conversation)"
|
|
95
123
|
:is-active="conversation.id === props.activeId"
|
|
96
124
|
:title="titleFor(conversation)"
|
|
97
|
-
:
|
|
98
|
-
:
|
|
99
|
-
:
|
|
100
|
-
:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
:time="timeFor(conversation)"
|
|
126
|
+
:select="(event: MouseEvent) => handleRowClick(conversation, event)"
|
|
127
|
+
:edit="() => emit('edit', conversation)"
|
|
128
|
+
:delete="() => emit('delete', conversation)"
|
|
129
|
+
>
|
|
130
|
+
<CkHistoryItem
|
|
131
|
+
as="a"
|
|
132
|
+
:href="hrefFor(conversation)"
|
|
133
|
+
:is-active="conversation.id === props.activeId"
|
|
134
|
+
:title="titleFor(conversation)"
|
|
135
|
+
:meta-start="timeFor(conversation)"
|
|
136
|
+
:show-action="props.showAction"
|
|
137
|
+
:show-action-on-hover="props.showActionOnHover"
|
|
138
|
+
:action-popover="props.showAction"
|
|
139
|
+
:action-aria-label="labels.historyItemActionAriaLabel"
|
|
140
|
+
@click="handleRowClick(conversation, $event)"
|
|
141
|
+
><template #action-popover>
|
|
142
|
+
<UiSidebarPopoverMenu :show-footer-separator="false">
|
|
143
|
+
<UiSidebarPopoverMenuItem
|
|
144
|
+
:label="labels.historyItemEditAction"
|
|
145
|
+
@click="emit('edit', conversation)"
|
|
146
|
+
>
|
|
147
|
+
<template #icon>
|
|
148
|
+
<UiIcon name="pencil" size="s" />
|
|
149
|
+
</template>
|
|
150
|
+
</UiSidebarPopoverMenuItem>
|
|
151
|
+
<UiSidebarPopoverMenuItem
|
|
152
|
+
:label="labels.historyItemDeleteAction"
|
|
153
|
+
@click="emit('delete', conversation)"
|
|
154
|
+
>
|
|
155
|
+
<template #icon>
|
|
156
|
+
<UiIcon name="trash" size="s" />
|
|
157
|
+
</template>
|
|
158
|
+
</UiSidebarPopoverMenuItem>
|
|
159
|
+
</UiSidebarPopoverMenu>
|
|
160
|
+
</template>
|
|
161
|
+
</CkHistoryItem>
|
|
162
|
+
</slot>
|
|
163
|
+
</template>
|
|
164
|
+
</CkHistoryWrapper>
|
|
165
|
+
|
|
166
|
+
<button
|
|
167
|
+
v-if="canCollapse && !isExpanded"
|
|
168
|
+
type="button"
|
|
169
|
+
data-testid="ck-history-show-all"
|
|
170
|
+
class="mt-1 w-full rounded-lg px-2 py-2 text-center body-14-medium text-content-on-surface-muted transition-colors hover:bg-background-surface-muted-subtle"
|
|
171
|
+
@click="toggleExpanded"
|
|
172
|
+
>
|
|
173
|
+
{{ labels.historyShowAll }}
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
126
176
|
</template>
|
|
@@ -50,6 +50,39 @@ export interface CkHistoryProps {
|
|
|
50
50
|
* @default true
|
|
51
51
|
*/
|
|
52
52
|
showActionOnHover?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Formats a conversation's `updatedAt` timestamp (Unix seconds) into a
|
|
55
|
+
* human-readable relative time string displayed under the title.
|
|
56
|
+
*
|
|
57
|
+
* Defaults to the built-in `formatRelativeTime` which produces English
|
|
58
|
+
* labels like "Just now", "Yesterday", "3 days ago". Pass your own
|
|
59
|
+
* function for locale-aware formatting (e.g. via `Intl.RelativeTimeFormat`
|
|
60
|
+
* or your i18n library).
|
|
61
|
+
*
|
|
62
|
+
* To hide the time label entirely, pass `() => ''`.
|
|
63
|
+
*
|
|
64
|
+
* @default formatRelativeTime
|
|
65
|
+
*/
|
|
66
|
+
timeFormatter?: (updatedAtSeconds: number) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Maximum number of conversations shown in collapsed mode. When the
|
|
69
|
+
* array has more items than this limit a "Show all" button is rendered
|
|
70
|
+
* below the visible rows. Clicking it expands the list and makes it
|
|
71
|
+
* scrollable (capped at `maxHeight`).
|
|
72
|
+
*
|
|
73
|
+
* Set to `0` or `undefined` to disable collapsing — all conversations
|
|
74
|
+
* are shown and the list is always scrollable.
|
|
75
|
+
*
|
|
76
|
+
* @default 5
|
|
77
|
+
*/
|
|
78
|
+
collapsedLimit?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Maximum height (CSS value) of the list when expanded. Allows the list
|
|
81
|
+
* to scroll when conversations exceed the available space.
|
|
82
|
+
*
|
|
83
|
+
* @default undefined — no max-height constraint
|
|
84
|
+
*/
|
|
85
|
+
maxHeight?: string;
|
|
53
86
|
}
|
|
54
87
|
|
|
55
88
|
/**
|
|
@@ -60,6 +60,7 @@ describe('useChatKitLabels', () => {
|
|
|
60
60
|
historyItemActionAriaLabel: 'Konversationsoptionen',
|
|
61
61
|
historyItemEditAction: 'Bearbeiten',
|
|
62
62
|
historyItemDeleteAction: 'Löschen',
|
|
63
|
+
historyShowAll: 'Alle anzeigen',
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
let result: ComputedRef<ChatKitLabels> | undefined;
|
|
@@ -77,6 +77,11 @@ export type ChatKitLabels = {
|
|
|
77
77
|
historyItemEditAction: string;
|
|
78
78
|
/** Label for the "Delete" action in the `CkHistory` row popover menu. */
|
|
79
79
|
historyItemDeleteAction: string;
|
|
80
|
+
/**
|
|
81
|
+
* Label for the "Show all" button that appears when `collapsedLimit` is
|
|
82
|
+
* set and more conversations exist than the limit.
|
|
83
|
+
*/
|
|
84
|
+
historyShowAll?: string;
|
|
80
85
|
};
|
|
81
86
|
|
|
82
87
|
/** English defaults used when no labels are provided by the consumer. */
|
|
@@ -106,6 +111,7 @@ export const defaultChatKitLabels: ChatKitLabels = {
|
|
|
106
111
|
historyItemActionAriaLabel: 'Conversation options',
|
|
107
112
|
historyItemEditAction: 'Edit',
|
|
108
113
|
historyItemDeleteAction: 'Delete',
|
|
114
|
+
historyShowAll: 'Show all',
|
|
109
115
|
};
|
|
110
116
|
|
|
111
117
|
/** Vue injection key for ChatKit labels. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const MINUTE = 60;
|
|
2
|
+
const HOUR = 60 * MINUTE;
|
|
3
|
+
const DAY = 24 * HOUR;
|
|
4
|
+
const WEEK = 7 * DAY;
|
|
5
|
+
const MONTH = 30 * DAY;
|
|
6
|
+
const YEAR = 365 * DAY;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert a Unix-seconds timestamp to a human-readable relative time string.
|
|
10
|
+
*
|
|
11
|
+
* Uses simple threshold-based bucketing (not `Intl.RelativeTimeFormat`) so the
|
|
12
|
+
* output is deterministic and easy to snapshot-test. Consumers who need
|
|
13
|
+
* locale-aware formatting can supply their own formatter via the
|
|
14
|
+
* `timeFormatter` prop on `CkHistory`.
|
|
15
|
+
*/
|
|
16
|
+
export function formatRelativeTime(
|
|
17
|
+
unixSeconds: number,
|
|
18
|
+
nowSeconds: number = Math.floor(Date.now() / 1000),
|
|
19
|
+
): string {
|
|
20
|
+
const delta = Math.max(0, nowSeconds - unixSeconds);
|
|
21
|
+
|
|
22
|
+
if (delta < MINUTE) return 'Just now';
|
|
23
|
+
if (delta < HOUR) {
|
|
24
|
+
const mins = Math.floor(delta / MINUTE);
|
|
25
|
+
return mins === 1 ? '1 minute ago' : `${mins} minutes ago`;
|
|
26
|
+
}
|
|
27
|
+
if (delta < DAY) {
|
|
28
|
+
const hrs = Math.floor(delta / HOUR);
|
|
29
|
+
return hrs === 1 ? '1 hour ago' : `${hrs} hours ago`;
|
|
30
|
+
}
|
|
31
|
+
if (delta < 2 * DAY) return 'Yesterday';
|
|
32
|
+
if (delta < WEEK) {
|
|
33
|
+
const days = Math.floor(delta / DAY);
|
|
34
|
+
return `${days} days ago`;
|
|
35
|
+
}
|
|
36
|
+
if (delta < 2 * WEEK) return '1 week ago';
|
|
37
|
+
if (delta < MONTH) {
|
|
38
|
+
const weeks = Math.floor(delta / WEEK);
|
|
39
|
+
return `${weeks} weeks ago`;
|
|
40
|
+
}
|
|
41
|
+
if (delta < 2 * MONTH) return '1 month ago';
|
|
42
|
+
if (delta < YEAR) {
|
|
43
|
+
const months = Math.floor(delta / MONTH);
|
|
44
|
+
return `${months} months ago`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const years = Math.floor(delta / YEAR);
|
|
48
|
+
return years === 1 ? '1 year ago' : `${years} years ago`;
|
|
49
|
+
}
|
package/src/helpers/index.ts
CHANGED