vercel / ai

Build AI-powered applications with React, Svelte, Vue, and Solid
https://sdk.vercel.ai/docs
Other
10.36k stars 1.56k forks source link

Ending `textStream.done()` in `streamUI()` causes another render in component after version `>=3.2.6` #2232

Open Theonlyhamstertoh opened 4 months ago

Theonlyhamstertoh commented 4 months ago

Description

Building on top of https://github.com/vercel/ai/issues/2183. It appears that any versions after ai@3.2.5 will cause an extra component rerender once textStream.done() is ran/when the stream ends after text generation. This bug is noticeable in ai@3.2.6

Video demo with ai@3.2.5

https://github.com/vercel/ai/assets/75579372/50d0b2b5-e930-4a7e-bd45-83bbadf86912

Video demo with ai@3.2.6

https://github.com/vercel/ai/assets/75579372/b47108d1-769a-4aab-bb67-47f039ea66fa

Code example

Example code using streamUI() and textStream.done():

async function submitUserMessage(content: string) {
  "use server"

  const aiState = getMutableAIState<typeof AI>()

  aiState.update({
    ...aiState.get(),
    messages: [
      ...aiState.get().messages,
      {
        id: nanoid(),
        role: "user",
        content,
      },
    ],
  })

  let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
  let textNode: undefined | React.ReactNode

  const result = await streamUI({
    model: openai("gpt-4o"),
    initial: <SpinnerMessage />,
    system: system_instructions,
    messages: [
      ...aiState.get().messages.map((message: any) => ({
        role: message.role,
        content: message.content,
        name: message.name,
      })),
    ],
    text: ({ content, done, delta }) => {
      if (!textStream) {
        textStream = createStreamableValue("")
        textNode = <BotMessage content={textStream.value} />
      }

      if (done) {
        textStream.done()
        aiState.done({
          ...aiState.get(),
          messages: [
            ...aiState.get().messages,
            {
              id: nanoid(),
              role: "assistant",
              content,
            },
          ],
        })
      } else {
        textStream.update(delta)
      }
      return textNode
    },
    onFinish: result => {
      console.log(result.finishReason)
    },
  })

  return {
    id: nanoid(),
    display: result.value,
  }
}

Additional context

No response

leo-paz commented 3 weeks ago

Hey @Theonlyhamstertoh did you end up staying on 3.2.5 or did you migrate away from using /rsc?

Theonlyhamstertoh commented 3 weeks ago

Hey @Theonlyhamstertoh did you end up staying on 3.2.5 or did you migrate away from using /rsc?

I am using latest, I noticed the issue is still there on my end. I kinda got used to it haha

leo-paz commented 3 weeks ago

I am using latest, I noticed the issue is still there on my end. I kinda got used to it haha

Ahhh okay fair enough. Might stick with it hopping that react 19 will fix some things haha