Skip to content
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

Page refresh firing before saveChat #302

Open
Godrules500 opened this issue Apr 5, 2024 · 10 comments
Open

Page refresh firing before saveChat #302

Godrules500 opened this issue Apr 5, 2024 · 10 comments

Comments

@Godrules500
Copy link

I have a situation every now and then when I create a new chat that, and the saveChat takes a little bit of time, the chat.tsx router.refresh() fires too soon. How can I ensure that it waits for saveChat to finish?

This is the code that is firing before the saveChat finishes. So the assistant responds, and the saveChat finishes after the aiState.done is called.
useEffect(() => { const messagesLength = aiState.messages?.length if (messagesLength === 2) { router.refresh() } }, [aiState.messages, router])

@amxv
Copy link

amxv commented Apr 8, 2024

having the same issue atm, were you able to fix it?

@Godrules500
Copy link
Author

Godrules500 commented Apr 9, 2024

Hey @amxv yes, I was able to resolve it. Since I'm using gemini, I had to follow the AI SDK 3 example "next-ai-rsc". But essentially it should be the same thing.

Changes I made:

  • Added a "saved" property to AIState (to prevent saving multiple times).
  • Refactored saveChat called inside of unstable_onSetAIState to a method
  • Called await saveChat when text completion "isFinal" was true.

I now call saveChat inside of unstable_onSetAIState like this

unstable_onSetAIState: async ({ state, done }) => {
    'use server'

    const session = await auth()

    if (session && session.user) {

      // only save it once it's done. No need to save it after each streamed result
      if (done && !state.saved) {
        await addOrUpdateChat(state, session)
        state.saved = true
      }
    } else {
      return
    }
  }
})

I then call await saveChat(state) when isFinal is true and set saved to true.

let state = aiState.get()
        state.messages = [
          ...state.messages,
          {
            id: nanoid(),
            role: 'assistant',
            content
          }
        ]

        await saveChat(state)

        reply.done()
        aiState.done({ ...state, done: isFinal, saved: true })

And then the saveChat() is this

async function saveChat(state: AIState, session: Session | null = null) {
  if (!session) {
    session = await auth()
    if (!session?.user?.id) {
      throw new Error('User not Authorized to save')
    }
  }

  const { chatId, messages } = state

  const createdAt = Date.now()
  const userId = session.user?.id as string
  const path = `/chat/${chatId}`
  const title = messages[0].content.substring(0, 100)

  const chat: Chat = {
    id: chatId,
    title,
    userId,
    createdAt,
    path,
    messages
  }

  await dynamoDbService.saveChat(chat)
}

Let me know if that fixes it for you too or if you run into any issues with it please!

@athrael-soju
Copy link

@Godrules500 could you please raise a PR with the fix?

Fixes such as this one are super helpful for keeping the template updated.

@audvin
Copy link

audvin commented Apr 11, 2024

Experiencing the same issue.

@Godrules500
Copy link
Author

@athrael-soju I will try to get to it soon!

I also found another solution to another issue if it helps y'all.
If you go to new chat, and then clear history, and then send a prompt, it will refresh because the chat ids are out of sync. My fix for me is as follow.

on chat.tsx, this is the problem area for me.

useEffect(() => {
    setNewChatId(id)
  })

I updated mine to

  useEffect(() => {
    // This is here, because it resolve a bug when you go to 'new chat' --> clear history, and then generate a new chat, the IDs were out of sync.
    updateAIState({ ...aiState, chatId: id! })
    setNewChatId(id)
  })

then on actions.tsx, I added this.

async function updateAIState(currentAIState: any) {
  'use server'
  const aiState = getMutableAIState<typeof AI>()
  aiState.done({ ...currentAIState })
}

don't forget to add it where submitUserMessage is added.

Then update chat.tsx to

@athrael-soju
Copy link

@athrael-soju I will try to get to it soon!

I also found another solution to another issue if it helps y'all. If you go to new chat, and then clear history, and then send a prompt, it will refresh because the chat ids are out of sync. My fix for me is as follow.

on chat.tsx, this is the problem area for me.

useEffect(() => {
    setNewChatId(id)
  })

I updated mine to

  useEffect(() => {
    // This is here, because it resolve a bug when you go to 'new chat' --> clear history, and then generate a new chat, the IDs were out of sync.
    updateAIState({ ...aiState, chatId: id! })
    setNewChatId(id)
  })

then on actions.tsx, I added this.

async function updateAIState(currentAIState: any) {
  'use server'
  const aiState = getMutableAIState<typeof AI>()
  aiState.done({ ...currentAIState })
}

don't forget to add it where submitUserMessage is added.

Then update chat.tsx to

I think you maybe sharing code from the ai-rsc template here. Is this an issue with this template? I tried reproducing this issue and I'm not seeing it.

@yaberkane05
Copy link

Hi, any news on this issue ?

@navkuun
Copy link

navkuun commented May 23, 2024

Made the changes as said here, but still facing this issue. Anyone else?

My current code:

export type AIState = {
  chatId: string
  messages: Message[]
  saved?: boolean
}

export type UIState = {
  id: string
  display: React.ReactNode
}[]


async function addOrUpdateChat(state: AIState, session: Session | null = null) {
  if (!session) {
    session = await auth()
    if (!session?.user?.id) {
      throw new Error('User not Authorized to save')
    }
  }

  const { chatId, messages } = state

  const createdAt = new Date()
  const userId = session?.user?.id as string
  const path = `/chat/${chatId}`

  const firstMessageContent = messages[0].content as string
  const title = firstMessageContent.substring(0, 100)

  const flowchart = await getFlowchart(chatId)

  const chat: Chat = {
    id: chatId,
    title,
    userId,
    createdAt,
    path,
    messages,
    flowchart: flowchart || { nodes: [], edges: [] } // Use existing flowchart data if available, otherwise initialize with empty arrays
  }

  await saveChat(chat)
}

export const AI = createAI<AIState, UIState>({
  actions: {
    submitUserMessage
  },
  initialUIState: [],
  initialAIState: { chatId: nanoid(), messages: [] },
  onGetUIState: async () => {
    'use server'

    const session = await auth()

    if (session && session.user) {
      const aiState = getAIState()

      if (aiState) {
        const uiState = getUIStateFromAIState(aiState)
        return uiState
      }
    } else {
      return
    }
  },
  onSetAIState: async ({ state, done }) => {
    'use server'

    const session = await auth()

    if (session && session.user) {
      // only save it once it's done. No need to save it after each streamed result
      if (done && !state.saved) {
        await addOrUpdateChat(state, session)
        state.saved = true
      }
    } else {
      return
    }
  }
})

@yaberkane05
Copy link

same for me.

@athrael-soju
Copy link

athrael-soju commented May 23, 2024

I'd wait a bit for the next version of the template to come out, as this one still has some breaking issues. They've already closed down several PRs with fixes, which hopefully points to a release soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants