@aleph-alpha/chat-kit 1.13.0 → 1.15.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-DCZTSiBj.js → CkInput.vue_vue_type_script_setup_true_lang-D8In_Dnl.js} +85 -27
- package/dist/chat-kit.css +28 -0
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts +6 -0
- package/dist/components/base/CkUserMessage/CkUserMessage.stories.d.ts.map +1 -1
- package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts +4 -1
- package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkUserMessage/types.d.ts +14 -5
- package/dist/components/base/CkUserMessage/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 +4 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{useChatKitLabels-DeHxZG2a.js → useChatKitLabels-BxNkTzr2.js} +2 -0
- 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/CkUserMessage/CkUserMessage.spec.ts +25 -0
- package/src/components/base/CkUserMessage/CkUserMessage.stories.ts +34 -0
- package/src/components/base/CkUserMessage/CkUserMessage.vue +115 -5
- package/src/components/base/CkUserMessage/types.ts +14 -5
- package/src/components/composed/CkConversation/CkConversation.spec.ts +8 -35
- package/src/handlers/useChatKitLabels.spec.ts +2 -0
- package/src/handlers/useChatKitLabels.ts +6 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineComponent, openBlock, createElementBlock, createElementVNode, renderSlot, createCommentVNode, ref, onMounted, nextTick, onUnmounted, watch, Fragment, renderList, normalizeStyle, createBlock, unref, mergeProps, withCtx, createVNode, Transition, toDisplayString, computed, normalizeClass, toRefs, h, useId, createTextVNode, useAttrs, useSlots, withModifiers, resolveDynamicComponent, useTemplateRef, createSlots, normalizeProps, guardReactiveProps } from "vue";
|
|
2
|
-
import { u as useChatKitLabels, d as defaultChatKitLabels, a as useMarkdownParser } from "./useChatKitLabels-
|
|
1
|
+
import { defineComponent, openBlock, createElementBlock, createElementVNode, renderSlot, createCommentVNode, ref, onMounted, nextTick, onUnmounted, watch, Fragment, renderList, normalizeStyle, createBlock, unref, mergeProps, withCtx, createVNode, Transition, toDisplayString, computed, normalizeClass, toRefs, h, useId, createTextVNode, useAttrs, useSlots, withModifiers, resolveDynamicComponent, useTemplateRef, onBeforeUnmount, createSlots, normalizeProps, guardReactiveProps } from "vue";
|
|
2
|
+
import { u as useChatKitLabels, d as defaultChatKitLabels, a as useMarkdownParser } from "./useChatKitLabels-BxNkTzr2.js";
|
|
3
3
|
import { UiButton, UiTooltip, UiIcon, UiDialog, UiDialogContent, UiDialogDescription, UiFieldLabel, UiToggleGroup, UiToggleGroupItem, UiField, UiTextarea, UiFileItem, UiPopover, UiPopoverTrigger, UiFileType, UiPopoverContent, Primitive, UiAvatar, UiAvatarImage, UiSpinner, UiAvatarFallback, UiSidebarPopoverMenu, UiSidebarPopoverMenuItem } from "@aleph-alpha/ui-library";
|
|
4
4
|
const _hoisted_1$b = {
|
|
5
5
|
xmlns: "http://www.w3.org/2000/svg",
|
|
@@ -45,19 +45,25 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
-
const _hoisted_1$9 = { class: "flex w-full
|
|
48
|
+
const _hoisted_1$9 = { class: "border-border-surface-default flex w-full flex-col gap-6 border-b pb-6" };
|
|
49
49
|
const _hoisted_2$6 = {
|
|
50
50
|
key: 0,
|
|
51
|
+
class: "flex items-center gap-3"
|
|
52
|
+
};
|
|
53
|
+
const _hoisted_3$5 = {
|
|
54
|
+
key: 0,
|
|
55
|
+
"data-testid": "ck-agent-message-icon",
|
|
51
56
|
class: "flex flex-shrink-0 items-center justify-center"
|
|
52
57
|
};
|
|
53
|
-
const _hoisted_3$5 = { class: "text-core-content-primary flex min-w-0 flex-1 flex-col gap-2" };
|
|
54
58
|
const _hoisted_4$4 = {
|
|
55
|
-
key:
|
|
56
|
-
|
|
59
|
+
key: 1,
|
|
60
|
+
"data-testid": "ck-agent-message-header",
|
|
61
|
+
class: "body-14-regular text-content-on-surface-muted min-w-0 flex-1 [word-break:break-word]"
|
|
57
62
|
};
|
|
58
|
-
const _hoisted_5$3 = {
|
|
63
|
+
const _hoisted_5$3 = { class: "text-content-on-surface-primary text-base leading-7" };
|
|
64
|
+
const _hoisted_6$2 = {
|
|
59
65
|
key: 1,
|
|
60
|
-
class: "flex items-center"
|
|
66
|
+
class: "flex items-center gap-4"
|
|
61
67
|
};
|
|
62
68
|
const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
|
63
69
|
...{ name: "CkAgentMessage" },
|
|
@@ -65,20 +71,20 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
|
|
65
71
|
setup(__props) {
|
|
66
72
|
return (_ctx, _cache) => {
|
|
67
73
|
return openBlock(), createElementBlock("div", _hoisted_1$9, [
|
|
68
|
-
_ctx.$slots.icon ? (openBlock(), createElementBlock("div", _hoisted_2$6, [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
_ctx.$slots.icon || _ctx.$slots.header ? (openBlock(), createElementBlock("div", _hoisted_2$6, [
|
|
75
|
+
_ctx.$slots.icon ? (openBlock(), createElementBlock("div", _hoisted_3$5, [
|
|
76
|
+
renderSlot(_ctx.$slots, "icon")
|
|
77
|
+
])) : createCommentVNode("", true),
|
|
72
78
|
_ctx.$slots.header ? (openBlock(), createElementBlock("span", _hoisted_4$4, [
|
|
73
79
|
renderSlot(_ctx.$slots, "header")
|
|
74
|
-
])) : createCommentVNode("", true),
|
|
75
|
-
createElementVNode("div", null, [
|
|
76
|
-
renderSlot(_ctx.$slots, "default")
|
|
77
|
-
]),
|
|
78
|
-
_ctx.$slots.footer ? (openBlock(), createElementBlock("div", _hoisted_5$3, [
|
|
79
|
-
renderSlot(_ctx.$slots, "footer")
|
|
80
80
|
])) : createCommentVNode("", true)
|
|
81
|
-
])
|
|
81
|
+
])) : createCommentVNode("", true),
|
|
82
|
+
createElementVNode("div", _hoisted_5$3, [
|
|
83
|
+
renderSlot(_ctx.$slots, "default")
|
|
84
|
+
]),
|
|
85
|
+
_ctx.$slots.footer ? (openBlock(), createElementBlock("div", _hoisted_6$2, [
|
|
86
|
+
renderSlot(_ctx.$slots, "footer")
|
|
87
|
+
])) : createCommentVNode("", true)
|
|
82
88
|
]);
|
|
83
89
|
};
|
|
84
90
|
}
|
|
@@ -1453,26 +1459,78 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
|
1453
1459
|
};
|
|
1454
1460
|
}
|
|
1455
1461
|
});
|
|
1456
|
-
const _hoisted_1 = { class: "
|
|
1457
|
-
const _hoisted_2 =
|
|
1462
|
+
const _hoisted_1 = { class: "inline-flex max-w-[536px] flex-col items-end justify-start gap-1" };
|
|
1463
|
+
const _hoisted_2 = ["aria-expanded"];
|
|
1458
1464
|
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|
1459
1465
|
...{ name: "CkUserMessage" },
|
|
1460
1466
|
__name: "CkUserMessage",
|
|
1461
1467
|
props: {
|
|
1462
|
-
message: {}
|
|
1468
|
+
message: { default: void 0 },
|
|
1469
|
+
collapsedLines: { default: 0 }
|
|
1463
1470
|
},
|
|
1464
1471
|
setup(__props) {
|
|
1472
|
+
const props = __props;
|
|
1473
|
+
const labels = useChatKitLabels();
|
|
1474
|
+
const showMoreLabel = computed(
|
|
1475
|
+
() => labels.value.userMessageShowMore ?? defaultChatKitLabels.userMessageShowMore
|
|
1476
|
+
);
|
|
1477
|
+
const showLessLabel = computed(
|
|
1478
|
+
() => labels.value.userMessageShowLess ?? defaultChatKitLabels.userMessageShowLess
|
|
1479
|
+
);
|
|
1480
|
+
const clampEnabled = computed(() => (props.collapsedLines ?? 0) > 0);
|
|
1481
|
+
const contentRef = ref(null);
|
|
1482
|
+
const expanded = ref(false);
|
|
1483
|
+
const isOverflowing = ref(false);
|
|
1484
|
+
function measure() {
|
|
1485
|
+
const el = contentRef.value;
|
|
1486
|
+
if (!el || !clampEnabled.value || expanded.value) return;
|
|
1487
|
+
isOverflowing.value = el.scrollHeight - el.clientHeight > 1;
|
|
1488
|
+
}
|
|
1489
|
+
function toggle() {
|
|
1490
|
+
expanded.value = !expanded.value;
|
|
1491
|
+
}
|
|
1492
|
+
let resizeObserver;
|
|
1493
|
+
onMounted(() => {
|
|
1494
|
+
nextTick(measure);
|
|
1495
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1496
|
+
resizeObserver = new ResizeObserver(() => measure());
|
|
1497
|
+
if (contentRef.value) resizeObserver.observe(contentRef.value);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
onBeforeUnmount(() => resizeObserver?.disconnect());
|
|
1501
|
+
watch([() => props.message, () => props.collapsedLines], () => {
|
|
1502
|
+
expanded.value = false;
|
|
1503
|
+
nextTick(measure);
|
|
1504
|
+
});
|
|
1465
1505
|
return (_ctx, _cache) => {
|
|
1466
1506
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
1467
|
-
createElementVNode("div",
|
|
1507
|
+
createElementVNode("div", {
|
|
1508
|
+
ref_key: "contentRef",
|
|
1509
|
+
ref: contentRef,
|
|
1510
|
+
class: normalizeClass(["ck-user-message__content text-content-on-surface-muted min-w-0 max-w-full self-stretch text-lg leading-7 font-medium break-words whitespace-pre-wrap [overflow-wrap:anywhere]", {
|
|
1511
|
+
"ck-user-message__content--clamped": clampEnabled.value && !expanded.value,
|
|
1512
|
+
"ck-user-message__content--faded": clampEnabled.value && !expanded.value && isOverflowing.value
|
|
1513
|
+
}]),
|
|
1514
|
+
style: normalizeStyle(
|
|
1515
|
+
clampEnabled.value ? { "--ck-collapsed-lines": __props.collapsedLines } : void 0
|
|
1516
|
+
)
|
|
1517
|
+
}, [
|
|
1468
1518
|
renderSlot(_ctx.$slots, "default", {}, () => [
|
|
1469
1519
|
createTextVNode(toDisplayString(__props.message), 1)
|
|
1470
|
-
])
|
|
1471
|
-
])
|
|
1520
|
+
], true)
|
|
1521
|
+
], 6),
|
|
1522
|
+
clampEnabled.value && isOverflowing.value ? (openBlock(), createElementBlock("button", {
|
|
1523
|
+
key: 0,
|
|
1524
|
+
type: "button",
|
|
1525
|
+
"aria-expanded": expanded.value,
|
|
1526
|
+
class: "ck-user-message__toggle label-12-medium text-content-on-surface-primary bg-background-surface-floating focus-visible:ring-ring self-center rounded-3xl px-3 py-2 focus-visible:ring-2 focus-visible:outline-none",
|
|
1527
|
+
onClick: toggle
|
|
1528
|
+
}, toDisplayString(expanded.value ? showLessLabel.value : showMoreLabel.value), 9, _hoisted_2)) : createCommentVNode("", true)
|
|
1472
1529
|
]);
|
|
1473
1530
|
};
|
|
1474
1531
|
}
|
|
1475
1532
|
});
|
|
1533
|
+
const CkUserMessage = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-c759a863"]]);
|
|
1476
1534
|
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
1477
1535
|
...{ name: "CkConversation" },
|
|
1478
1536
|
__name: "CkConversation",
|
|
@@ -1501,7 +1559,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
|
1501
1559
|
class: normalizeClass({ "ml-8": hasIcon.value })
|
|
1502
1560
|
}, [
|
|
1503
1561
|
renderSlot(_ctx.$slots, "user-message", { message }, () => [
|
|
1504
|
-
createVNode(
|
|
1562
|
+
createVNode(CkUserMessage, null, {
|
|
1505
1563
|
default: withCtx(() => [
|
|
1506
1564
|
renderSlot(_ctx.$slots, "user-message-content", { message }, () => [
|
|
1507
1565
|
createTextVNode(toDisplayString(getMessageText(message)), 1)
|
|
@@ -1815,5 +1873,5 @@ export {
|
|
|
1815
1873
|
_sfc_main$6 as o,
|
|
1816
1874
|
_sfc_main$k as p,
|
|
1817
1875
|
_sfc_main$j as q,
|
|
1818
|
-
|
|
1876
|
+
CkUserMessage as r
|
|
1819
1877
|
};
|
package/dist/chat-kit.css
CHANGED
|
@@ -41,3 +41,31 @@
|
|
|
41
41
|
.ck-feedback-dialog__response[data-v-eaffd2d5] p:last-child {
|
|
42
42
|
margin-bottom: 0;
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/* Floating pill per design — the inset white + soft drop shadow aren't
|
|
46
|
+
expressible with the design-system shadow utilities, so set them here. */
|
|
47
|
+
.ck-user-message__toggle[data-v-c759a863] {
|
|
48
|
+
box-shadow:
|
|
49
|
+
0 2px 2px 0 rgba(0, 0, 0, 0.05),
|
|
50
|
+
0 0 4px 0 #fff inset;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* text-lg has a 1.75rem (28px) line-height; clamp height = lines × line-height. */
|
|
54
|
+
.ck-user-message__content--clamped[data-v-c759a863] {
|
|
55
|
+
max-height: calc(var(--ck-collapsed-lines) * 1.75rem);
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Fade the last visible line to low opacity. */
|
|
60
|
+
.ck-user-message__content--faded[data-v-c759a863] {
|
|
61
|
+
-webkit-mask-image: linear-gradient(
|
|
62
|
+
to bottom,
|
|
63
|
+
#000 calc(100% - 1.75rem),
|
|
64
|
+
transparent 100%
|
|
65
|
+
);
|
|
66
|
+
mask-image: linear-gradient(
|
|
67
|
+
to bottom,
|
|
68
|
+
#000 calc(100% - 1.75rem),
|
|
69
|
+
transparent 100%
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CkAgentMessage.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkAgentMessage/CkAgentMessage.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CkAgentMessage.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkAgentMessage/CkAgentMessage.vue"],"names":[],"mappings":"AA6GA,QAAA,IAAI,OAAO,IAAU,EAAE,OAAO,IAAU,EAAE,OAAO,IAAU,EAAE,OAAO,IAAW,CAAE;AACjF,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,GACzC;IAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,GAC3C;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,GAC5C;IAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAK9C,QAAA,MAAM,UAAU,+QACd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -22,4 +22,10 @@ export declare const MultilineMessage: Story;
|
|
|
22
22
|
* attachments). Use when a plain string is not sufficient.
|
|
23
23
|
*/
|
|
24
24
|
export declare const WithSlotContent: Story;
|
|
25
|
+
/**
|
|
26
|
+
* Long message clamped to 4 lines. The last visible line fades out and a
|
|
27
|
+
* centered "Show more" / "Show less" toggle expands and collapses the text.
|
|
28
|
+
* The toggle only appears when the content actually overflows the clamp.
|
|
29
|
+
*/
|
|
30
|
+
export declare const Collapsible: Story;
|
|
25
31
|
//# sourceMappingURL=CkUserMessage.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CkUserMessage.stories.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/CkUserMessage.stories.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE3D,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,aAAa,
|
|
1
|
+
{"version":3,"file":"CkUserMessage.stories.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/CkUserMessage.stories.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE3D,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,aAAa,CAsBpC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAwDnC;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAMzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAM9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAU7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KAMzB,CAAC"}
|
|
@@ -3,7 +3,10 @@ declare var __VLS_1: {};
|
|
|
3
3
|
type __VLS_Slots = {} & {
|
|
4
4
|
default?: (props: typeof __VLS_1) => any;
|
|
5
5
|
};
|
|
6
|
-
declare const __VLS_base: import("vue").DefineComponent<CkUserMessageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<CkUserMessageProps> & Readonly<{}>, {
|
|
6
|
+
declare const __VLS_base: import("vue").DefineComponent<CkUserMessageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<CkUserMessageProps> & Readonly<{}>, {
|
|
7
|
+
message: string;
|
|
8
|
+
collapsedLines: number;
|
|
9
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
10
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
8
11
|
declare const _default: typeof __VLS_export;
|
|
9
12
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CkUserMessage.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/CkUserMessage.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CkUserMessage.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/CkUserMessage.vue"],"names":[],"mappings":"AAwIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA2JlD,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAO/C,QAAA,MAAM,UAAU;;;6EAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Layout for a single user turn in a conversation. Renders the message text
|
|
3
|
+
* right-aligned in the muted on-surface color with a max-width; supports a
|
|
4
|
+
* default slot for custom inline content. When `collapsedLines` is set, long
|
|
5
|
+
* messages are clamped with a fade and a "Show more" / "Show less" toggle.
|
|
5
6
|
*
|
|
6
7
|
* @category Messages
|
|
7
|
-
* @useCases render user message
|
|
8
|
-
* @keywords user, message,
|
|
8
|
+
* @useCases render user message, conversation turn from user, chat input preview, right-aligned message, collapsible long message
|
|
9
|
+
* @keywords user, message, chat, conversation, turn, collapse, show more
|
|
9
10
|
* @related CkAgentMessage, CkConversation
|
|
10
11
|
*/
|
|
11
12
|
export interface CkUserMessageProps {
|
|
12
13
|
/** Text content of the user message. Rendered inside the bubble when no default slot is provided. */
|
|
13
14
|
message?: string;
|
|
15
|
+
/**
|
|
16
|
+
* When greater than `0`, collapse the message to at most this many lines.
|
|
17
|
+
* The last visible line fades out and a "Show more" / "Show less" toggle is
|
|
18
|
+
* revealed only when the content actually overflows. Omit or pass `0`
|
|
19
|
+
* (the default) to render the full message with no clamping — preserving the
|
|
20
|
+
* original behavior for existing consumers.
|
|
21
|
+
*/
|
|
22
|
+
collapsedLines?: number;
|
|
14
23
|
}
|
|
15
24
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/base/CkUserMessage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/dist/components/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _, a, b, C, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r } from "../CkInput.vue_vue_type_script_setup_true_lang-
|
|
1
|
+
import { _, a, b, C, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r } from "../CkInput.vue_vue_type_script_setup_true_lang-D8In_Dnl.js";
|
|
2
2
|
export {
|
|
3
3
|
_ as CkAgentMessage,
|
|
4
4
|
a as CkConversation,
|
package/dist/handlers/index.js
CHANGED
|
@@ -63,6 +63,10 @@ export type ChatKitLabels = {
|
|
|
63
63
|
feedbackDialogCancelLabel?: string;
|
|
64
64
|
/** Submit button label in `CkFeedbackDialog`. */
|
|
65
65
|
feedbackDialogSubmitLabel?: string;
|
|
66
|
+
/** Label for the `CkUserMessage` toggle when the message is collapsed. */
|
|
67
|
+
userMessageShowMore?: string;
|
|
68
|
+
/** Label for the `CkUserMessage` toggle when the message is expanded. */
|
|
69
|
+
userMessageShowLess?: string;
|
|
66
70
|
/** Fallback title used by `CkHistory` when a conversation has no `title`. */
|
|
67
71
|
historyItemUntitled: string;
|
|
68
72
|
/** Accessible name for the trailing kebab trigger on each `CkHistory` row. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatKitLabels.d.ts","sourceRoot":"","sources":["../../src/handlers/useChatKitLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAC;IACzB,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,uBAAuB,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,+DAA+D;IAC/D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,gEAAgE;IAChE,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,kFAAkF;IAClF,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,qEAAqE;IACrE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qEAAqE;IACrE,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,iEAAiE;IACjE,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C,iDAAiD;IACjD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iDAAiD;IACjD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,6EAA6E;IAC7E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,0BAA0B,EAAE,MAAM,CAAC;IACnC,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"useChatKitLabels.d.ts","sourceRoot":"","sources":["../../src/handlers/useChatKitLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAC;IACzB,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,uBAAuB,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,+DAA+D;IAC/D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,gEAAgE;IAChE,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,kFAAkF;IAClF,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,qEAAqE;IACrE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qEAAqE;IACrE,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,iEAAiE;IACjE,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C,iDAAiD;IACjD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iDAAiD;IACjD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,0BAA0B,EAAE,MAAM,CAAC;IACnC,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,aA0BlC,CAAC;AAEF,4CAA4C;AAC5C,eAAO,MAAM,eAAe,EAAE,YAAY,CAAC,WAAW,CAAC,aAAa,CAAC,CAC1C,CAAC;AAE5B,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5C,yFAAyF;AACzF,KAAK,cAAc,GACf,GAAG,CAAC,aAAa,CAAC,GAClB,WAAW,CAAC,aAAa,CAAC,GAC1B,CAAC,MAAM,aAAa,CAAC,CAAC;AAE1B;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAKjE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAAC,aAAa,CAAC,CAK7D"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { u } from "./useMessageHandler-CMdy-lm0.js";
|
|
2
|
-
import { C, d, p, u as u2, a } from "./useChatKitLabels-
|
|
2
|
+
import { C, d, p, u as u2, a } from "./useChatKitLabels-BxNkTzr2.js";
|
|
3
3
|
import { S, c, d as d2, a as a2, u as u3, b, e, f } from "./index-Ct5hD214.js";
|
|
4
|
-
import { _, a as a3, b as b2, C as C2, c as c2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r } from "./CkInput.vue_vue_type_script_setup_true_lang-
|
|
4
|
+
import { _, a as a3, b as b2, C as C2, c as c2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r } from "./CkInput.vue_vue_type_script_setup_true_lang-D8In_Dnl.js";
|
|
5
5
|
export {
|
|
6
6
|
C as CHAT_KIT_LABELS,
|
|
7
7
|
_ as CkAgentMessage,
|
|
@@ -12717,6 +12717,8 @@ const defaultChatKitLabels = {
|
|
|
12717
12717
|
feedbackDialogFreeTextPlaceholder: "Write down some additional details",
|
|
12718
12718
|
feedbackDialogCancelLabel: "Cancel",
|
|
12719
12719
|
feedbackDialogSubmitLabel: "Submit",
|
|
12720
|
+
userMessageShowMore: "Show more",
|
|
12721
|
+
userMessageShowLess: "Show less",
|
|
12720
12722
|
historyItemUntitled: "Untitled conversation",
|
|
12721
12723
|
historyItemActionAriaLabel: "Conversation options",
|
|
12722
12724
|
historyItemEditAction: "Edit",
|
package/package.json
CHANGED
|
@@ -12,12 +12,12 @@ describe('CkAgentMessage', () => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('hides the header section when no header slot is provided', () => {
|
|
15
|
-
|
|
15
|
+
render(CkAgentMessage, {
|
|
16
16
|
slots: { default: 'Some content' },
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
expect(
|
|
20
|
-
|
|
20
|
+
screen.queryByTestId('ck-agent-message-header'),
|
|
21
21
|
).not.toBeInTheDocument();
|
|
22
22
|
});
|
|
23
23
|
});
|
|
@@ -42,26 +42,20 @@ describe('CkAgentMessage', () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('renders the icon container only when the icon slot is provided', () => {
|
|
45
|
-
|
|
45
|
+
render(CkAgentMessage, {
|
|
46
46
|
slots: { icon: '<img src="" alt="bot-icon" />' },
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
expect(
|
|
50
|
-
container.querySelector(
|
|
51
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
52
|
-
),
|
|
53
|
-
).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
|
|
54
50
|
});
|
|
55
51
|
|
|
56
52
|
it('does not render the icon container when no icon slot is provided', () => {
|
|
57
|
-
|
|
53
|
+
render(CkAgentMessage, {
|
|
58
54
|
slots: { default: 'Some content' },
|
|
59
55
|
});
|
|
60
56
|
|
|
61
57
|
expect(
|
|
62
|
-
|
|
63
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
64
|
-
),
|
|
58
|
+
screen.queryByTestId('ck-agent-message-icon'),
|
|
65
59
|
).not.toBeInTheDocument();
|
|
66
60
|
});
|
|
67
61
|
});
|
|
@@ -3,23 +3,32 @@ defineOptions({ name: 'CkAgentMessage' });
|
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
|
-
<div
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
<div
|
|
7
|
+
class="border-border-surface-default flex w-full flex-col gap-6 border-b pb-6"
|
|
8
|
+
>
|
|
9
|
+
<div v-if="$slots.icon || $slots.header" class="flex items-center gap-3">
|
|
10
|
+
<div
|
|
11
|
+
v-if="$slots.icon"
|
|
12
|
+
data-testid="ck-agent-message-icon"
|
|
13
|
+
class="flex flex-shrink-0 items-center justify-center"
|
|
14
|
+
>
|
|
15
|
+
<slot name="icon" />
|
|
16
|
+
</div>
|
|
17
|
+
<span
|
|
18
|
+
v-if="$slots.header"
|
|
19
|
+
data-testid="ck-agent-message-header"
|
|
20
|
+
class="body-14-regular text-content-on-surface-muted min-w-0 flex-1 [word-break:break-word]"
|
|
21
|
+
>
|
|
15
22
|
<slot name="header" />
|
|
16
23
|
</span>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="text-content-on-surface-primary text-base leading-7">
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div v-if="$slots.footer" class="flex items-center gap-4">
|
|
31
|
+
<slot name="footer" />
|
|
23
32
|
</div>
|
|
24
33
|
</div>
|
|
25
34
|
</template>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import CkUserMessage from './CkUserMessage.vue';
|
|
2
2
|
import { render, screen } from '@testing-library/vue';
|
|
3
3
|
|
|
4
|
+
const CONTENT_SELECTOR = '.ck-user-message__content';
|
|
5
|
+
const CLAMPED_CLASS = 'ck-user-message__content--clamped';
|
|
6
|
+
|
|
4
7
|
describe('CkUserMessage', () => {
|
|
5
8
|
it('displays the message text from prop', () => {
|
|
6
9
|
render(CkUserMessage, { props: { message: 'Hello from tests' } });
|
|
@@ -17,4 +20,26 @@ describe('CkUserMessage', () => {
|
|
|
17
20
|
expect(screen.getByText('Custom slot content')).toBeInTheDocument();
|
|
18
21
|
expect(screen.queryByText('Fallback text')).not.toBeInTheDocument();
|
|
19
22
|
});
|
|
23
|
+
|
|
24
|
+
describe('collapsedLines clamping', () => {
|
|
25
|
+
it('does not clamp or render a toggle when collapsedLines is omitted', () => {
|
|
26
|
+
const { container } = render(CkUserMessage, {
|
|
27
|
+
props: { message: 'A message' },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const content = container.querySelector(CONTENT_SELECTOR);
|
|
31
|
+
expect(content).not.toHaveClass(CLAMPED_CLASS);
|
|
32
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('clamps the content to the requested number of lines', () => {
|
|
36
|
+
const { container } = render(CkUserMessage, {
|
|
37
|
+
props: { message: 'A long message', collapsedLines: 3 },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const content = container.querySelector<HTMLElement>(CONTENT_SELECTOR);
|
|
41
|
+
expect(content).toHaveClass(CLAMPED_CLASS);
|
|
42
|
+
expect(content?.style.getPropertyValue('--ck-collapsed-lines')).toBe('3');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
20
45
|
});
|
|
@@ -11,6 +11,11 @@ const meta: Meta<typeof CkUserMessage> = {
|
|
|
11
11
|
description:
|
|
12
12
|
'The message text to display. Can also be provided via the default slot.',
|
|
13
13
|
},
|
|
14
|
+
collapsedLines: {
|
|
15
|
+
control: { type: 'number', min: 0 },
|
|
16
|
+
description:
|
|
17
|
+
'When > 0, clamp the message to this many lines with a fade and a "Show more" / "Show less" toggle (shown only when the content overflows). 0 disables clamping.',
|
|
18
|
+
},
|
|
14
19
|
},
|
|
15
20
|
decorators: [
|
|
16
21
|
() => ({
|
|
@@ -61,6 +66,22 @@ import { CkUserMessage } from '@aleph-alpha/chat-kit';
|
|
|
61
66
|
</CkUserMessage>
|
|
62
67
|
</template>`;
|
|
63
68
|
|
|
69
|
+
const collapsibleMessage =
|
|
70
|
+
'Lorem ipsum dolor sit amet consectetur. Scelerisque justo pharetra enim eros risus. ' +
|
|
71
|
+
'Urna dictumst ultrices mattis tristique lacus in in. Elit sed in a pellentesque purus ' +
|
|
72
|
+
'lectus quis. Massa facilisis eget etiam maecenas eget libero iaculis consectetur. ' +
|
|
73
|
+
'Curabitur vivamus a nulla cras venenatis nec ultrices donec. Dui tempus posuere nulla ' +
|
|
74
|
+
'in id dictumst id congue odio. At lacus elit sit amet. Sodales lorem lacus faucibus ' +
|
|
75
|
+
'condimentum lectus auctor.';
|
|
76
|
+
|
|
77
|
+
const collapsibleTemplateSource = `<script setup lang="ts">
|
|
78
|
+
import { CkUserMessage } from '@aleph-alpha/chat-kit';
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<CkUserMessage :collapsed-lines="4" message="..." />
|
|
83
|
+
</template>`;
|
|
84
|
+
|
|
64
85
|
/**
|
|
65
86
|
* Standard user message bubble populated via the `message` prop. Use as the
|
|
66
87
|
* default way to render the user turn in a conversation.
|
|
@@ -110,3 +131,16 @@ export const WithSlotContent: Story = {
|
|
|
110
131
|
}),
|
|
111
132
|
parameters: { docs: { source: { code: withSlotContentTemplateSource } } },
|
|
112
133
|
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Long message clamped to 4 lines. The last visible line fades out and a
|
|
137
|
+
* centered "Show more" / "Show less" toggle expands and collapses the text.
|
|
138
|
+
* The toggle only appears when the content actually overflows the clamp.
|
|
139
|
+
*/
|
|
140
|
+
export const Collapsible: Story = {
|
|
141
|
+
args: {
|
|
142
|
+
message: collapsibleMessage,
|
|
143
|
+
collapsedLines: 4,
|
|
144
|
+
},
|
|
145
|
+
parameters: { docs: { source: { code: collapsibleTemplateSource } } },
|
|
146
|
+
};
|
|
@@ -1,19 +1,129 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
defaultChatKitLabels,
|
|
4
|
+
useChatKitLabels,
|
|
5
|
+
} from '../../../handlers/useChatKitLabels';
|
|
2
6
|
import type { CkUserMessageProps } from './types';
|
|
7
|
+
import {
|
|
8
|
+
computed,
|
|
9
|
+
nextTick,
|
|
10
|
+
onBeforeUnmount,
|
|
11
|
+
onMounted,
|
|
12
|
+
ref,
|
|
13
|
+
watch,
|
|
14
|
+
} from 'vue';
|
|
3
15
|
|
|
4
16
|
defineOptions({ name: 'CkUserMessage' });
|
|
5
17
|
|
|
6
|
-
defineProps<CkUserMessageProps>()
|
|
18
|
+
const props = withDefaults(defineProps<CkUserMessageProps>(), {
|
|
19
|
+
message: undefined,
|
|
20
|
+
collapsedLines: 0,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const labels = useChatKitLabels();
|
|
24
|
+
|
|
25
|
+
// Resolution order: injected label, English default.
|
|
26
|
+
const showMoreLabel = computed(
|
|
27
|
+
() =>
|
|
28
|
+
labels.value.userMessageShowMore ??
|
|
29
|
+
defaultChatKitLabels.userMessageShowMore!,
|
|
30
|
+
);
|
|
31
|
+
const showLessLabel = computed(
|
|
32
|
+
() =>
|
|
33
|
+
labels.value.userMessageShowLess ??
|
|
34
|
+
defaultChatKitLabels.userMessageShowLess!,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/** Clamping is opt-in: only active when a positive `collapsedLines` is given. */
|
|
38
|
+
const clampEnabled = computed(() => (props.collapsedLines ?? 0) > 0);
|
|
39
|
+
|
|
40
|
+
const contentRef = ref<HTMLElement | null>(null);
|
|
41
|
+
const expanded = ref(false);
|
|
42
|
+
const isOverflowing = ref(false);
|
|
43
|
+
|
|
44
|
+
function measure() {
|
|
45
|
+
const el = contentRef.value;
|
|
46
|
+
if (!el || !clampEnabled.value || expanded.value) return;
|
|
47
|
+
isOverflowing.value = el.scrollHeight - el.clientHeight > 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toggle() {
|
|
51
|
+
expanded.value = !expanded.value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
55
|
+
|
|
56
|
+
onMounted(() => {
|
|
57
|
+
nextTick(measure);
|
|
58
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
59
|
+
resizeObserver = new ResizeObserver(() => measure());
|
|
60
|
+
if (contentRef.value) resizeObserver.observe(contentRef.value);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
onBeforeUnmount(() => resizeObserver?.disconnect());
|
|
65
|
+
|
|
66
|
+
// New content (or a changed clamp) re-evaluates from the collapsed state.
|
|
67
|
+
watch([() => props.message, () => props.collapsedLines], () => {
|
|
68
|
+
expanded.value = false;
|
|
69
|
+
nextTick(measure);
|
|
70
|
+
});
|
|
7
71
|
</script>
|
|
8
72
|
|
|
9
73
|
<template>
|
|
10
|
-
<div
|
|
11
|
-
class="bg-background-surface-primary inline-flex max-w-[600px] flex-col items-end justify-start gap-4 rounded-xl p-6"
|
|
12
|
-
>
|
|
74
|
+
<div class="inline-flex max-w-[536px] flex-col items-end justify-start gap-1">
|
|
13
75
|
<div
|
|
14
|
-
|
|
76
|
+
ref="contentRef"
|
|
77
|
+
class="ck-user-message__content text-content-on-surface-muted min-w-0 max-w-full self-stretch text-lg leading-7 font-medium break-words whitespace-pre-wrap [overflow-wrap:anywhere]"
|
|
78
|
+
:class="{
|
|
79
|
+
'ck-user-message__content--clamped': clampEnabled && !expanded,
|
|
80
|
+
'ck-user-message__content--faded':
|
|
81
|
+
clampEnabled && !expanded && isOverflowing,
|
|
82
|
+
}"
|
|
83
|
+
:style="
|
|
84
|
+
clampEnabled ? { '--ck-collapsed-lines': collapsedLines } : undefined
|
|
85
|
+
"
|
|
15
86
|
>
|
|
16
87
|
<slot>{{ message }}</slot>
|
|
17
88
|
</div>
|
|
89
|
+
<button
|
|
90
|
+
v-if="clampEnabled && isOverflowing"
|
|
91
|
+
type="button"
|
|
92
|
+
:aria-expanded="expanded"
|
|
93
|
+
class="ck-user-message__toggle label-12-medium text-content-on-surface-primary bg-background-surface-floating focus-visible:ring-ring self-center rounded-3xl px-3 py-2 focus-visible:ring-2 focus-visible:outline-none"
|
|
94
|
+
@click="toggle"
|
|
95
|
+
>
|
|
96
|
+
{{ expanded ? showLessLabel : showMoreLabel }}
|
|
97
|
+
</button>
|
|
18
98
|
</div>
|
|
19
99
|
</template>
|
|
100
|
+
|
|
101
|
+
<style scoped>
|
|
102
|
+
/* Floating pill per design — the inset white + soft drop shadow aren't
|
|
103
|
+
expressible with the design-system shadow utilities, so set them here. */
|
|
104
|
+
.ck-user-message__toggle {
|
|
105
|
+
box-shadow:
|
|
106
|
+
0 2px 2px 0 rgba(0, 0, 0, 0.05),
|
|
107
|
+
0 0 4px 0 #fff inset;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* text-lg has a 1.75rem (28px) line-height; clamp height = lines × line-height. */
|
|
111
|
+
.ck-user-message__content--clamped {
|
|
112
|
+
max-height: calc(var(--ck-collapsed-lines) * 1.75rem);
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Fade the last visible line to low opacity. */
|
|
117
|
+
.ck-user-message__content--faded {
|
|
118
|
+
-webkit-mask-image: linear-gradient(
|
|
119
|
+
to bottom,
|
|
120
|
+
#000 calc(100% - 1.75rem),
|
|
121
|
+
transparent 100%
|
|
122
|
+
);
|
|
123
|
+
mask-image: linear-gradient(
|
|
124
|
+
to bottom,
|
|
125
|
+
#000 calc(100% - 1.75rem),
|
|
126
|
+
transparent 100%
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
</style>
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Layout for a single user turn in a conversation. Renders the message text
|
|
3
|
+
* right-aligned in the muted on-surface color with a max-width; supports a
|
|
4
|
+
* default slot for custom inline content. When `collapsedLines` is set, long
|
|
5
|
+
* messages are clamped with a fade and a "Show more" / "Show less" toggle.
|
|
5
6
|
*
|
|
6
7
|
* @category Messages
|
|
7
|
-
* @useCases render user message
|
|
8
|
-
* @keywords user, message,
|
|
8
|
+
* @useCases render user message, conversation turn from user, chat input preview, right-aligned message, collapsible long message
|
|
9
|
+
* @keywords user, message, chat, conversation, turn, collapse, show more
|
|
9
10
|
* @related CkAgentMessage, CkConversation
|
|
10
11
|
*/
|
|
11
12
|
export interface CkUserMessageProps {
|
|
12
13
|
/** Text content of the user message. Rendered inside the bubble when no default slot is provided. */
|
|
13
14
|
message?: string;
|
|
15
|
+
/**
|
|
16
|
+
* When greater than `0`, collapse the message to at most this many lines.
|
|
17
|
+
* The last visible line fades out and a "Show more" / "Show less" toggle is
|
|
18
|
+
* revealed only when the content actually overflows. Omit or pass `0`
|
|
19
|
+
* (the default) to render the full message with no clamping — preserving the
|
|
20
|
+
* original behavior for existing consumers.
|
|
21
|
+
*/
|
|
22
|
+
collapsedLines?: number;
|
|
14
23
|
}
|
|
@@ -132,56 +132,36 @@ describe('CkConversation', () => {
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it('hides the icon area when no agentAvatarImage, no agentAvatarFallback, and no agent-message-icon slot', () => {
|
|
135
|
-
|
|
135
|
+
render(CkConversation, {
|
|
136
136
|
props: { messages: [agentMessage()] },
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
const agentMessageEl = container.querySelector(
|
|
140
|
-
'.flex.w-full.items-start.gap-2',
|
|
141
|
-
);
|
|
142
|
-
expect(agentMessageEl).toBeInTheDocument();
|
|
143
139
|
expect(
|
|
144
|
-
|
|
145
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
146
|
-
),
|
|
140
|
+
screen.queryByTestId('ck-agent-message-icon'),
|
|
147
141
|
).not.toBeInTheDocument();
|
|
148
142
|
});
|
|
149
143
|
|
|
150
144
|
it('shows the icon area when agentAvatarFallback is provided', () => {
|
|
151
|
-
|
|
145
|
+
render(CkConversation, {
|
|
152
146
|
props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
|
|
153
147
|
});
|
|
154
148
|
|
|
155
|
-
|
|
156
|
-
'.flex.w-full.items-start.gap-2',
|
|
157
|
-
);
|
|
158
|
-
expect(
|
|
159
|
-
agentMessageEl?.querySelector(
|
|
160
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
161
|
-
),
|
|
162
|
-
).toBeInTheDocument();
|
|
149
|
+
expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
|
|
163
150
|
});
|
|
164
151
|
|
|
165
152
|
it('shows the icon area when agentAvatarImage URL is provided', () => {
|
|
166
|
-
|
|
153
|
+
render(CkConversation, {
|
|
167
154
|
props: {
|
|
168
155
|
messages: [agentMessage()],
|
|
169
156
|
agentAvatarImage: 'https://example.com/icon.png',
|
|
170
157
|
},
|
|
171
158
|
});
|
|
172
159
|
|
|
173
|
-
|
|
174
|
-
'.flex.w-full.items-start.gap-2',
|
|
175
|
-
);
|
|
176
|
-
expect(
|
|
177
|
-
agentMessageEl?.querySelector(
|
|
178
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
179
|
-
),
|
|
180
|
-
).toBeInTheDocument();
|
|
160
|
+
expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
|
|
181
161
|
});
|
|
182
162
|
|
|
183
163
|
it('shows the icon area when agent-message-icon slot is provided', () => {
|
|
184
|
-
|
|
164
|
+
render(CkConversation, {
|
|
185
165
|
props: { messages: [agentMessage()] },
|
|
186
166
|
slots: {
|
|
187
167
|
'agent-message-icon': '<span data-testid="custom-icon">🤖</span>',
|
|
@@ -189,14 +169,7 @@ describe('CkConversation', () => {
|
|
|
189
169
|
});
|
|
190
170
|
|
|
191
171
|
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
|
|
192
|
-
|
|
193
|
-
'.flex.w-full.items-start.gap-2',
|
|
194
|
-
);
|
|
195
|
-
expect(
|
|
196
|
-
agentMessageEl?.querySelector(
|
|
197
|
-
'.flex.flex-shrink-0.items-center.justify-center',
|
|
198
|
-
),
|
|
199
|
-
).toBeInTheDocument();
|
|
172
|
+
expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
|
|
200
173
|
});
|
|
201
174
|
});
|
|
202
175
|
|
|
@@ -54,6 +54,8 @@ describe('useChatKitLabels', () => {
|
|
|
54
54
|
feedbackDialogFreeTextPlaceholder: 'Zusätzliche Details eingeben',
|
|
55
55
|
feedbackDialogCancelLabel: 'Abbrechen',
|
|
56
56
|
feedbackDialogSubmitLabel: 'Senden',
|
|
57
|
+
userMessageShowMore: 'Mehr anzeigen',
|
|
58
|
+
userMessageShowLess: 'Weniger anzeigen',
|
|
57
59
|
historyItemUntitled: 'Unbenannte Konversation',
|
|
58
60
|
historyItemActionAriaLabel: 'Konversationsoptionen',
|
|
59
61
|
historyItemEditAction: 'Bearbeiten',
|
|
@@ -65,6 +65,10 @@ export type ChatKitLabels = {
|
|
|
65
65
|
feedbackDialogCancelLabel?: string;
|
|
66
66
|
/** Submit button label in `CkFeedbackDialog`. */
|
|
67
67
|
feedbackDialogSubmitLabel?: string;
|
|
68
|
+
/** Label for the `CkUserMessage` toggle when the message is collapsed. */
|
|
69
|
+
userMessageShowMore?: string;
|
|
70
|
+
/** Label for the `CkUserMessage` toggle when the message is expanded. */
|
|
71
|
+
userMessageShowLess?: string;
|
|
68
72
|
/** Fallback title used by `CkHistory` when a conversation has no `title`. */
|
|
69
73
|
historyItemUntitled: string;
|
|
70
74
|
/** Accessible name for the trailing kebab trigger on each `CkHistory` row. */
|
|
@@ -96,6 +100,8 @@ export const defaultChatKitLabels: ChatKitLabels = {
|
|
|
96
100
|
feedbackDialogFreeTextPlaceholder: 'Write down some additional details',
|
|
97
101
|
feedbackDialogCancelLabel: 'Cancel',
|
|
98
102
|
feedbackDialogSubmitLabel: 'Submit',
|
|
103
|
+
userMessageShowMore: 'Show more',
|
|
104
|
+
userMessageShowLess: 'Show less',
|
|
99
105
|
historyItemUntitled: 'Untitled conversation',
|
|
100
106
|
historyItemActionAriaLabel: 'Conversation options',
|
|
101
107
|
historyItemEditAction: 'Edit',
|