fix(ChatPromptSubmit): emits provides event externally so that external events can be consumed
[ChatPromptSubmit] emits provides event externally so that external events can be consumed
๐ Linked issue
The problem is explained in the description.
โ Type of change
- [ ] ๐ Documentation (updates to the documentation or readme)
- [x] ๐ Bug fix (a non-breaking change that fixes an issue)
- [ ] ๐ Enhancement (improving an existing functionality)
- [ ] โจ New feature (a non-breaking change that adds functionality)
- [ ] ๐งน Chore (updates to the build process or auxiliary tools and libraries)
- [ ] โ ๏ธ Breaking change (fix or feature that would cause existing functionality to change)
๐ Description
Reproduction: https://codesandbox.io/p/devbox/sweet-dirac-7hpdy2?file=%2Fapp%2Fapp.tsx
Due to the event bubble, when UChatPrompt nests UChatPromptSubmit, the onStop and onReload of UChatPromptSubmit do not consume the event, which will bubble to the onSubmit event of UChatPrompt, causing some side effects (onSubmit event is called multiple times as shown in the figure below).
๐ Checklist
- [x] I have linked an issue or discussion.
- [x] I have updated the documentation accordingly.
I'm having a hard-time understand your issue here, do you have the issue in a real environment when using @ai-sdk/vue?
Also, I tried your changes locally but adding this event still logs handleSubmit when pressing the stop button ๐ค
I'm having a hard-time understand your issue here, do you have the issue in a real environment when using
@ai-sdk/vue?Also, I tried your changes locally but adding this event still logs
handleSubmitwhen pressing the stop button ๐ค
@benjamincanac This has nothing to do with @ai-sdk/vue. In my submission, I did not directly consume the click event (to avoid destructive updates). Instead, I chose to send the obtained event: MouseEvent through stop and reload emits in ChatPromptSubmit, so that developers can decide whether they need to call event.preventDefault to prevent the browser from triggering the submit event of the form.
To put it simply, onClick implemented in ChatPromptSubmit will call stop and reload based on the message status but will not send the event instance. Developers cannot implement operations such as preventing bubbling and consuming default events in onStop and onReload implemented by themselves.
The final effect is that users will be able to call event.preventDefault in handleStop and handleReload. This will prevent handleSubmit from being triggered. You can try the following sample code.
import { defineComponent, ref } from "vue";
import { type ChatPromptSubmitProps } from "@nuxt/ui/components/ChatPromptSubmit.vue";
import { UChatPrompt, UChatPromptSubmit } from "#components";
let timer: any;
export default defineComponent({
setup() {
const input = ref("value");
const status = ref<ChatPromptSubmitProps["status"]>("ready");
const handleSubmit = (e: Event) => {
e.preventDefault();
console.log("handleSubmit");
if (timer) clearTimeout(timer);
status.value = "streaming";
timer = setTimeout(() => {
status.value = "error";
}, 3600);
};
const handleStop = (event) => {
console.log("handleStop");
status.value = "ready";
event.preventDefault(); // this is the key place
};
const handleReload = (event) => {
console.log("handleReload");
status.value = "ready";
event.preventDefault(); // this is the key place
};
return () => {
return (
<div>
<UChatPrompt
modelValue={input.value}
onSubmit={handleSubmit}
style={{ height: "5rem", paddingLeft: "0" }}
>
{{
footer: () => (
<div class="chat_prompt_buttons">
<div style={{ flex: 1 }}></div>
<UChatPromptSubmit
status={status.value} // simulation state
onStop={handleStop}
onReload={handleReload}
/>
</div>
),
}}
</UChatPrompt>
</div>
);
};
},
});
@benjamincanac is there anything I can do?
I almos add a new issue here about this! -> https://github.com/nuxt/nuxt/issues/33738
OK, now @stop.prevent="stopChat" stops the prompt sending, but not @stop.stop="stopChat" (before this PR it will throw an undefined error) (I'm not using @vercel/ai-sdk as supposed, I'm creating messages manually)
But I find if I do this, the stop event effectively stops the resending of the prompt (even before this PR exists):
function stopChat() {
controller.value.abort()
controller.value = new AbortController()
setTimeout(() => { // <-- THIS!!!
chatStatus.value = 'ready'
}, 0)
toast.add({ title: 'Consulta detenida', icon: 'i-lucide-stop-circle' })
}
Other values of chatStatus.value different from ready will not resend the prompt. It only happens when the value is ready
To summarize:
-
Nuxt UI 4.2.0
-
stop.preventORstop.stopโ Undefined error -
chatStatus.valuedifferent fromreadywill stop the prompt resending -
chatStatus.value = "ready"inside asetTimeouteven with value 0, will stop the prompt resending
-
-
Nuxt UI 4.2.1
-
stop.preventORstop.stopโ No error, but onlystop.preventwill actually stop the prompt resending, no need forsetTimeouthack - Only
stopevent without Event Modifiers need the manuallyevent.preventDefault()OR thesetTimeouthack
-
To summarize:
Nuxt UI 4.2.0
stop.preventORstop.stopโ Undefined errorchatStatus.valuedifferent fromreadywill stop the prompt resendingchatStatus.value = "ready"inside asetTimeouteven with value 0, will stop the prompt resendingNuxt UI 4.2.1
stop.preventORstop.stopโ No error, but onlystop.preventwill actually stop the prompt resending, no need forsetTimeouthack- Only
stopevent without Event Modifiers need the manuallyevent.preventDefault()OR thesetTimeouthack
@rodriciru Yes, you are right. I seem to have to think about the syntax of stop.prevent OR stop.stop. I would be very grateful if you could help me fix it and submit the PR.
To summarize:
Nuxt UI 4.2.0
stop.preventORstop.stopโ Undefined errorchatStatus.valuedifferent fromreadywill stop the prompt resendingchatStatus.value = "ready"inside asetTimeouteven with value 0, will stop the prompt resendingNuxt UI 4.2.1
stop.preventORstop.stopโ No error, but onlystop.preventwill actually stop the prompt resending, no need forsetTimeouthack- Only
stopevent without Event Modifiers need the manuallyevent.preventDefault()OR thesetTimeouthack@rodriciru Yes, you are right. I seem to have to think about the syntax of
stop.preventORstop.stop. I would be very grateful if you could help me fix it and submit the PR.
Fix what? stop.prevent OR stop.stop now are doing his thing without errors, but only stop.prevent has the desired effect (stop the bubbling up) in this case. I have no idea of vue or nuxt internals
@rodriciru my understanding is that when you use the @reload.stop syntax, the event triggering needs to call e.stopPropagation() by default without the need to call it manually in handleReload to prevent the event from bubbling?
<UChatPromptSubmit
:status="status"
@stop="handleStop"
@reload.stop="handleReload"
/>
I tested that the above code does not meet expectations in Nuxt UI 4.2.1. This may be related to vue. https://github.com/vuejs/core/blob/25ebe3a42cd80ac0256355c2740a0258cdd7419d/packages/runtime-dom/src/directives/vOn.ts#L52-L53 Maybe you can help investigate the reason?
I'm far for begin a nuxt teacher. In fact I'm supper new to Vue and nuxt. But yes, this is what I understand https://vuejs.org/guide/essentials/event-handling#event-modifiers