ui icon indicating copy to clipboard operation
ui copied to clipboard

fix(ChatPromptSubmit): emits provides event externally so that external events can be consumed

Open PBK-B opened this issue 5 months ago โ€ข 1 comments

[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).

ๆˆชๅฑ2025-11-07 15 18 03

๐Ÿ“ Checklist

  • [x] I have linked an issue or discussion.
  • [x] I have updated the documentation accordingly.

PBK-B avatar Nov 07 '25 07:11 PBK-B

npm i https://pkg.pr.new/@nuxt/ui@5400

commit: b82ad4e

pkg-pr-new[bot] avatar Nov 07 '25 07:11 pkg-pr-new[bot]

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 ๐Ÿค”

benjamincanac avatar Nov 17 '25 12:11 benjamincanac

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 ๐Ÿค”

@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>
      );
    };
  },
});

PBK-B avatar Nov 17 '25 12:11 PBK-B

@benjamincanac is there anything I can do?

PBK-B avatar Nov 20 '25 01:11 PBK-B

I almos add a new issue here about this! -> https://github.com/nuxt/nuxt/issues/33738

rodriciru avatar Nov 24 '25 10:11 rodriciru

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

rodriciru avatar Nov 24 '25 10:11 rodriciru

To summarize:

  1. Nuxt UI 4.2.0

    • stop.prevent OR stop.stop โ†’ Undefined error
    • chatStatus.value different from ready will stop the prompt resending
    • chatStatus.value = "ready" inside a setTimeout even with value 0, will stop the prompt resending
  2. Nuxt UI 4.2.1

    • stop.prevent OR stop.stop โ†’ No error, but only stop.prevent will actually stop the prompt resending, no need for setTimeout hack
    • Only stop event without Event Modifiers need the manually event.preventDefault() OR the setTimeout hack

rodriciru avatar Nov 24 '25 10:11 rodriciru

To summarize:

  1. Nuxt UI 4.2.0

    • stop.prevent OR stop.stop โ†’ Undefined error
    • chatStatus.value different from ready will stop the prompt resending
    • chatStatus.value = "ready" inside a setTimeout even with value 0, will stop the prompt resending
  2. Nuxt UI 4.2.1

    • stop.prevent OR stop.stop โ†’ No error, but only stop.prevent will actually stop the prompt resending, no need for setTimeout hack
    • Only stop event without Event Modifiers need the manually event.preventDefault() OR the setTimeout hack

@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.

PBK-B avatar Nov 24 '25 14:11 PBK-B

To summarize:

  1. Nuxt UI 4.2.0

    • stop.prevent OR stop.stop โ†’ Undefined error
    • chatStatus.value different from ready will stop the prompt resending
    • chatStatus.value = "ready" inside a setTimeout even with value 0, will stop the prompt resending
  2. Nuxt UI 4.2.1

    • stop.prevent OR stop.stop โ†’ No error, but only stop.prevent will actually stop the prompt resending, no need for setTimeout hack
    • Only stop event without Event Modifiers need the manually event.preventDefault() OR the setTimeout hack

@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.

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 avatar Nov 25 '25 11:11 rodriciru

@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?

PBK-B avatar Nov 26 '25 03:11 PBK-B

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

rodriciru avatar Nov 29 '25 15:11 rodriciru