-
Notifications
You must be signed in to change notification settings - Fork 957
fix(ChatMessages): added message slot to support custom message item
#5535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The getProxySlots() function should exclude the 'message' slot since it's a container-level slot for custom message rendering, not a slot that should be forwarded to the inner message component.
View Details
π Patch Details
diff --git a/src/runtime/components/ChatMessages.vue b/src/runtime/components/ChatMessages.vue
index 5af9a82a..f1995db7 100644
--- a/src/runtime/components/ChatMessages.vue
+++ b/src/runtime/components/ChatMessages.vue
@@ -99,7 +99,7 @@ const props = withDefaults(defineProps<ChatMessagesProps>(), {
})
const slots = defineSlots<ChatMessagesSlots>()
-const getProxySlots = () => omit(slots, ['default', 'indicator', 'viewport'])
+const getProxySlots = () => omit(slots, ['default', 'indicator', 'viewport', 'message'])
const appConfig = useAppConfig() as ChatMessages['AppConfig']
Analysis
getProxySlots() incorrectly includes 'message' slot in ChatMessages.vue
What fails: The getProxySlots() function at line 102 in src/runtime/components/ChatMessages.vue omits ['default', 'indicator', 'viewport'] but should also omit 'message', causing the message slot to be incorrectly forwarded to inner message components.
How to reproduce:
- Create a ChatMessages component with a custom
#messageslot - Use that slot to render a custom message component
- The custom component (or UChatMessage) receives a
#messageslot template that incorrectly points to the parent's message slot
What happens: When getProxySlots() includes 'message', the code iterates over it (line 307-309) and creates a named slot template #message that forwards the parent's message slot into the component. This is semantically incorrect.
Expected behavior: The message slot is a container-level slot that replaces the entire message rendering - it should not be forwarded as a child slot to inner components. Only the slots defined in ChatMessageSlots (leading, content, actions) should be forwarded. This follows the established pattern in similar container components like BlogPosts.vue and ChangelogVersions.vue.
Line 102 should be:
const getProxySlots = () => omit(slots, ['default', 'indicator', 'viewport', 'message'])6dd34c7 to
0cfc668
Compare
commit: |
c8b8d5a to
3a6053c
Compare
3a6053c to
858c391
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The CSS selector for the last message height styling no longer works because the DOM structure changed - message elements are now wrapped in divs, breaking the direct child selector [&>article].
View Details
π Patch Details
diff --git a/src/theme/chat-messages.ts b/src/theme/chat-messages.ts
index ca60a357..b480ed57 100644
--- a/src/theme/chat-messages.ts
+++ b/src/theme/chat-messages.ts
@@ -1,6 +1,6 @@
export default {
slots: {
- root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>article]:last-of-type:min-h-(--last-message-height)',
+ root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>div]:last-of-type:min-h-(--last-message-height)',
indicator: 'h-6 flex items-center gap-1 py-3 *:size-2 *:rounded-full *:bg-elevated [&>*:nth-child(1)]:animate-[bounce_1s_infinite] [&>*:nth-child(2)]:animate-[bounce_1s_0.15s_infinite] [&>*:nth-child(3)]:animate-[bounce_1s_0.3s_infinite]',
viewport: 'absolute inset-x-0 top-[86%] data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
autoScroll: 'rounded-full absolute right-1/2 translate-x-1/2 bottom-0'
Analysis
CSS selector for last message height styling doesn't match DOM structure
What fails: The CSS selector [&>article]:last-of-type:min-h-(--last-message-height) in src/theme/chat-messages.ts targets direct <article> children, but messages are now wrapped in <div> elements, so the selector never matches.
How to reproduce:
- Render ChatMessages component with messages
- Inspect the DOM - you'll see structure:
<div class="root">β<div (wrapper)>β<article (message)> - The CSS rule for
[&>article]:last-of-typewill not apply because articles are not direct children of root
Result: The min-height CSS variable --last-message-height is calculated in JavaScript (line 269, 305 in ChatMessages.vue) but never applied because the selector doesn't match any elements. The spacing between the last message and bottom of container doesn't work correctly.
Expected: The last message wrapper should receive the min-height styling to create proper spacing, as calculated by updateLastMessageHeight().
Fix: Change selector from [&>article]:last-of-type to [&>div]:last-of-type to target the wrapper divs that are actual direct children of root.
858c391 to
6fcc070
Compare
d3c9c8a to
25a4591
Compare
25a4591 to
1fc2bc2
Compare
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
@benjamincanac If you have time, please help me take a look at this, thank you. |
|
@PBK-B Why don't you use the default slot and iterate over messages yourself? π€ |
1fc2bc2 to
265adf8
Compare
want to reuse scrolling logic for message list |
| v-bind="{ | ||
| ...messageData, | ||
| ...(messageData.role === 'user' ? userProps : assistantProps) | ||
| }" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message slot doesn't pass the message property to the slot scope, even though the type definition requires it. Users won't be able to access the message data through the slot props.
View Details
π Patch Details
diff --git a/src/runtime/components/ChatMessages.vue b/src/runtime/components/ChatMessages.vue
index 1e5d975c..0cdbab27 100644
--- a/src/runtime/components/ChatMessages.vue
+++ b/src/runtime/components/ChatMessages.vue
@@ -309,6 +309,7 @@ onMounted(() => {
:ref="(el: HTMLElement) => registerMessageRef(messageData.id, el)"
name="message"
:compact="compact"
+ :message="messageData"
v-bind="{
...messageData,
...(messageData.role === 'user' ? userProps : assistantProps)
Analysis
Missing message property in ChatMessages slot
What fails: The message slot in ChatMessages.vue (line 308-316) doesn't pass the message property to slot scope, even though the ChatSlotMessageProps type definition (line 62) specifies message?: UIMessage should be available to consumers.
How to reproduce:
</div>
</template>
</UChatMessages>
</template>Result: The message property is undefined in the slot scope because only messageData's properties are spread (id, role, parts, etc.) without creating a message self-reference. The slot receives properties from spreading the UIMessage object, but not the full message object wrapped in a message property.
Expected: The message property should be available in the slot scope as messageData (the full UIMessage object), matching the type definition and consistent with how inner slots forward the message on line 327: :message="messageData".
Fix: Add explicit :message="messageData" binding to the slot element, ensuring the full message object is accessible to slot consumers.
| </UChatMessage> | ||
| <slot | ||
| v-if="$slots.message" | ||
| :ref="(el: HTMLElement) => registerMessageRef(messageData.id, el)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| :ref="(el: HTMLElement) => registerMessageRef(messageData.id, el)" | |
| :ref="(el: HTMLElement | null) => registerMessageRef(messageData.id, el)" |
The ref callback type annotation is too narrow and will cause TypeScript errors. Vue's ref callbacks must accept null for cleanup, but the type only specifies HTMLElement.
View Details
Analysis
Ref callback type annotation missing null parameter
What fails: Template ref callback on line 309 of ChatMessages.vue has type annotation (el: HTMLElement) which doesn't accept null, violating Vue's ref callback contract that requires handling unmounting with null parameter.
How to reproduce: Run TypeScript strict mode type checking:
pnpm exec tsc --strict src/runtime/components/ChatMessages.vueResult: When the slot element unmounts or is conditionally removed (via v-if), Vue calls the ref callback with null, but the function signature only accepts HTMLElement, causing a type mismatch.
Expected behavior: Per Vue documentation, ref callbacks must accept null as the parameter: "When the element is unmounted, the argument will be null."
Fix applied: Updated line 309 to include null in the type annotation: :ref="(el: HTMLElement | null) => registerMessageRef(messageData.id, el)"
π Linked issue
null
β Type of change
π Description
added
messageslot to support custom message itemExample
Preview
π Checklist