Skip to content

Fix Chat Page Refreshing Before Saving Chat #4

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

arvi18
Copy link

@arvi18 arvi18 commented Apr 26, 2025

  • FIxes: Page refresh firing before saveChat vercel/ai-chatbot#302
  • Resolve race condition when aiState.done is called before onSetAIState
  • This PR prevents the UI from refreshing before the db is updated.
  • As per the approach proposed by @Godrules500 in Page refresh firing before saveChat vercel/ai-chatbot#302 (comment) the onSetAIState callback is removed and instead an aiStateDone wrapper around aiState.done is called in place of aiState.done. This wrapper function waits until the chat is saved to the db before marking the aiState updates as done.
  • In chat.tsx, the useEffect hook for refreshing the router contains an additional check to refresh the router if the chat contains 3 messages and the last message is a tool call. The existing check only checked for 2 messages, meaning the router was not refreshed if the first user message in a chat resulted in a tool call response.
  • The Purchase component status now defaults to requires_action, instead of expired, with a useEffect hook added to check and update the purchase status based on the relevant tool call message and any related system messages.
  • The toolCallId is passed as a prop to Purchase. This is used to find the relevant tool call in the aiState.messages.
  • If the message immediately following the tool call message is a system message containing the string purchased, then the purchase is marked as completed.
  • An optional createdAt property is added to the Message interface. If the tool call message was created more than 30 seconds ago, the checkout is marked as expired. While the status remains in a requires_action state, an interval is set to check every 5 seconds whether the status should be expired.

Summary by CodeRabbit

  • New Features
    • Improved purchase tracking in the stock purchase flow, including automatic detection of completed or expired purchases.
    • Purchase status now updates in real time based on system messages and timing.
  • Bug Fixes
    • Enhanced reliability of chat state updates to prevent UI refresh before database persistence.
  • Refactor
    • Streamlined state management and improved type safety in chat and purchase components.
  • Chores
    • Added utility functions for timestamp generation and error-handled async execution.

@arvi18
Copy link
Author

arvi18 commented Apr 26, 2025

@Saran33 is attempting to deploy a commit to the Uncurated Tests Team on Vercel.

A member of the Team first needs to authorize it.

@arvi18
Copy link
Author

arvi18 commented Apr 26, 2025

Thanks @Saran33 - I used the code from this PR and it looks like it does solve the issue!

Copy link

coderabbitai bot commented Apr 26, 2025

Walkthrough

This update introduces several changes to improve chat state management and ensure database consistency before UI refreshes. The Chat component now refreshes the router based on updated conditions involving AI messages, while the Purchase component tracks purchase status internally and monitors AI state messages for updates. In the backend, chat state persistence is centralized through new helper functions that guarantee the chat is saved to the database before UI updates occur, addressing a race condition. Supporting types and utilities are extended, including enhanced message typing and timestamp utilities, and error handling is added to asynchronous function execution.

Changes

File(s) Change Summary
components/chat.tsx Updated the router refresh logic to trigger on two messages or three messages where the third has the role 'tool'; improved type usage for AI state.
components/stocks/stock-purchase.tsx Added toolCallId prop, internalized purchase status tracking, and implemented polling of AI state messages for purchase completion or expiration.
lib/chat/actions.tsx Centralized chat state persistence with new updateChat and aiStateDone functions; ensured state is saved before UI refresh; passed timestamps and toolCallId to components.
lib/types.ts Extended Message type with optional createdAt; added generic MutableAIState type for state management.
lib/utils.ts Enhanced runAsyncFnWithoutBlocking with error logging; added unixTsNow utility for Unix timestamp generation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatComponent
    participant AIState
    participant Database

    User->>ChatComponent: Sends message
    ChatComponent->>AIState: Updates AI state
    AIState->>Database: Calls updateChat() to persist state
    Database-->>AIState: Confirms save
    AIState->>ChatComponent: Calls aiState.done()
    ChatComponent->>ChatComponent: Triggers router.refresh() after save
Loading

Assessment against linked issues

Objective Addressed Explanation
Ensure router refresh waits for saveChat to finish (vercel#302)

Poem

A bunny hops with code delight,
Saving chats before the site
Refreshes fast—no race, no fright!
Purchases tracked, each tick and tock,
With timestamps now upon the clock.
All is synced, no data loss—
This rabbit’s proud to be the boss! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @arvi18, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

This pull request addresses a race condition where the UI was refreshing before the database was updated, leading to potential data inconsistencies. To resolve this, the onSetAIState callback has been removed and replaced with an aiStateDone wrapper around aiState.done. This wrapper ensures that the chat is saved to the database before marking AI state updates as complete. Additionally, the Purchase component's status now defaults to requires_action and includes logic to check and update the purchase status based on tool call messages and system messages. A check was added to the chat.tsx to refresh the router if the chat contains 3 messages and the last message is a tool call.

Highlights

  • Race Condition Fix: The pull request resolves a race condition by ensuring the database is updated before the UI refreshes, preventing data inconsistencies.
  • Purchase Status Handling: The Purchase component now defaults to requires_action and includes logic to update the purchase status based on tool call and system messages, including an expiration check.
  • Router Refresh Logic: The router refresh logic in chat.tsx has been updated to handle scenarios where the first user message results in a tool call response.

Changelog

Click here to see the changelog
  • components/chat.tsx
    • Added import for AI type.
    • Updated the useAIState hook to specify the type as typeof AI.
    • Added a check to refresh the router if the chat contains 3 messages and the last message is a tool call.
  • components/stocks/stock-purchase.tsx
    • Imported useEffect hook.
    • Added toolCallId prop to the Purchase interface.
    • The status prop now defaults to requires_action instead of expired.
    • Added a useEffect hook to check and update the purchase status based on tool call messages and system messages.
    • Added logic to set purchase status to 'expired' if the tool call message was created more than 30 seconds ago, and an interval to check every 5 seconds whether the status should be expired.
  • lib/chat/actions.tsx
    • Imported MutableAIState type.
    • Imported unixTsNow from lib/utils.
    • Replaced direct calls to aiState.done with calls to aiStateDone to ensure the chat is saved before the state is updated.
    • Added createdAt property to the tool call message.
    • Passed toolCallId as a prop to the Purchase component.
    • Removed onSetAIState and replaced it with aiStateDone to prevent race conditions.
    • Added logic to handle errors in runAsyncFnWithoutBlocking.
  • lib/types.ts
    • Added an optional createdAt property to the Message interface.
    • Added MutableAIState type definition.
  • lib/utils.ts
    • Added error handling to runAsyncFnWithoutBlocking.
    • Added unixTsNow function to get the current Unix timestamp.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A race, a chase,
UI's swift, DB's slow pace.
Wrapper waits, all's well.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a race condition in the chat page refreshing logic and improves the purchase component's status updates. The changes seem well-structured and address the identified issues effectively.

Summary of Findings

  • Potential infinite loop in useEffect: The useEffect in Purchase component depends on aiState.messages. If setAIState is called within the useEffect callback, it could potentially lead to an infinite loop. While the current logic seems to prevent this, it's a pattern that warrants careful consideration.
  • Clarity of checkPurchaseStatus logic: The checkPurchaseStatus function in Purchase component could benefit from additional comments to clarify the order of operations and the conditions under which the purchase status is updated.

Merge Readiness

The pull request appears to address the identified issues effectively. However, the potential for an infinite loop in the Purchase component's useEffect hook should be carefully considered. I am unable to approve this pull request, and recommend that others review and approve this code before merging. I recommend that the author address the high severity issue before merging.

Comment on lines +64 to +107
useEffect(() => {
const checkPurchaseStatus = () => {
if (purchaseStatus !== 'requires_action') {
return;
}
// check for purchase completion
// Find the tool message with the matching toolCallId
const toolMessage = aiState.messages.find(
message =>
message.role === 'tool' &&
message.content.some(part => part.toolCallId === toolCallId)
);

if (toolMessage) {
const toolMessageIndex = aiState.messages.indexOf(toolMessage);
// Check if the next message is a system message containing "purchased"
const nextMessage = aiState.messages[toolMessageIndex + 1];
if (
nextMessage?.role === 'system' &&
nextMessage.content.includes('purchased')
) {
setPurchaseStatus('completed');
} else {
// Check for expiration
const requestedAt = toolMessage.createdAt;
if (!requestedAt || unixTsNow() - requestedAt > 30) {
setPurchaseStatus('expired');
}
}
}
};
checkPurchaseStatus();

let intervalId: NodeJS.Timeout | null = null;
if (purchaseStatus === 'requires_action') {
intervalId = setInterval(checkPurchaseStatus, 5000);
}

return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [purchaseStatus, toolCallId, aiState.messages]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The useEffect hook depends on aiState.messages. If setPurchaseStatus triggers a re-render and subsequently modifies aiState.messages (directly or indirectly), it could lead to an infinite loop. While the current logic seems to prevent this, it's a pattern that warrants careful consideration. Consider using a more stable dependency or a ref to store the previous state.

Comment on lines +65 to +93
const checkPurchaseStatus = () => {
if (purchaseStatus !== 'requires_action') {
return;
}
// check for purchase completion
// Find the tool message with the matching toolCallId
const toolMessage = aiState.messages.find(
message =>
message.role === 'tool' &&
message.content.some(part => part.toolCallId === toolCallId)
);

if (toolMessage) {
const toolMessageIndex = aiState.messages.indexOf(toolMessage);
// Check if the next message is a system message containing "purchased"
const nextMessage = aiState.messages[toolMessageIndex + 1];
if (
nextMessage?.role === 'system' &&
nextMessage.content.includes('purchased')
) {
setPurchaseStatus('completed');
} else {
// Check for expiration
const requestedAt = toolMessage.createdAt;
if (!requestedAt || unixTsNow() - requestedAt > 30) {
setPurchaseStatus('expired');
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic within checkPurchaseStatus could benefit from additional comments to clarify the order of operations and the conditions under which the purchase status is updated. This would improve readability and maintainability.

Comment on lines +54 to +56
fn().catch(error => {
console.error('An error occurred in the async function:', error);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This catch block logs errors that occur within the async function. This is good for debugging. Consider adding more context to the error message, such as the function name or arguments.

Suggested change
fn().catch(error => {
console.error('An error occurred in the async function:', error);
});
fn().catch(error => {
console.error(`An error occurred in the async function ${fn.name}:`, error);
});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
components/stocks/stock-purchase.tsx (1)

89-91: Add fallback for missing timestamps.

When checking for expiration, there's a proper check for when createdAt is undefined, but you might want to add logging to track when this happens as it shouldn't be undefined.

  // Check for expiration
  const requestedAt = toolMessage.createdAt;
  if (!requestedAt || unixTsNow() - requestedAt > 30) {
+   if (!requestedAt) {
+     console.warn('Missing createdAt timestamp for tool call', toolCallId);
+   }
    setPurchaseStatus('expired');
  }
lib/chat/actions.tsx (2)

87-100: Minor: avoid multiple aiState.get() calls in the same tick

Inside confirmPurchase you call aiState.get() three times when constructing newState. Capture it once to guarantee consistency and reduce overhead:

-    aiStateDone(aiState, {
-      ...aiState.get(),
-      messages: [
-        ...aiState.get().messages,
+    const prev = aiState.get()
+    aiStateDone(aiState, {
+      ...prev,
+      messages: [
+        ...prev.messages,
         {
           id: nanoid(),

584-590: Props spread can leak unexpected fields into Purchase

tool.result already contains symbol, price, numberOfShares, and now possibly status. Spreading the result object means any new property returned by the backend will silently become a prop, bypassing Purchase's explicit interface. Prefer listing allowed props:

-  props={{
-    ...(tool.result as object),
-    toolCallId: tool.toolCallId,
-  }}
+  props={{
+    symbol: tool.result.symbol,
+    price: tool.result.price,
+    numberOfShares: tool.result.numberOfShares,
+    status: tool.result.status ?? 'requires_action',
+    toolCallId: tool.toolCallId,
+  }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 020494f and d236bf7.

📒 Files selected for processing (5)
  • components/chat.tsx (3 hunks)
  • components/stocks/stock-purchase.tsx (4 hunks)
  • lib/chat/actions.tsx (13 hunks)
  • lib/types.ts (2 hunks)
  • lib/utils.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
components/chat.tsx (1)
lib/chat/actions.tsx (1)
  • AI (500-523)
lib/types.ts (1)
lib/chat/actions.tsx (1)
  • AIState (490-493)
🪛 Biome (1.9.4)
lib/chat/actions.tsx

[error] 530-530: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (12)
lib/utils.ts (2)

54-57: Good addition of error handling.

Adding error handling to runAsyncFnWithoutBlocking is a good improvement to prevent unhandled promise rejections. This will help with debugging by ensuring errors are logged rather than silently failing.


93-93: Well-implemented utility function.

The unixTsNow function provides a clean way to get Unix timestamps in seconds, which is used for tracking message creation times and determining expiration in the Purchase component.

components/chat.tsx (3)

3-3: Good type import for better type safety.

Importing the AI type from actions provides better type checking and IDE support.


29-29: Improved type annotation.

Adding the explicit type annotation to useAIState ensures proper type inference for the AI state.


45-46: Good enhancement to router refresh logic.

This additional condition correctly handles the case where the first user message triggers a tool call response, which was part of the race condition issue being fixed in this PR.

lib/types.ts (2)

5-5: Good addition of timestamp property.

Adding the optional createdAt timestamp to the Message type supports the new expiration tracking logic in the Purchase component.


44-48: Well-defined type for AI state management.

The MutableAIState generic type provides a clear interface for interacting with AI state, supporting the improved state management described in the PR objectives.

components/stocks/stock-purchase.tsx (4)

13-13: Good addition of toolCallId for purchase tracking.

Adding the toolCallId property allows the component to identify the specific tool call message in the AI state, enabling proper status tracking.


18-18: Good state management approach.

Setting a default status of 'requires_action' and tracking it with an internal state variable allows the component to manage its lifecycle independently while still being initializable from props.

Also applies to: 23-23


64-107: Well-implemented purchase status monitoring.

This effect properly tracks the purchase status by:

  1. Finding the tool message with the matching toolCallId
  2. Checking if a subsequent system message indicates purchase completion
  3. Checking for expiration based on the timestamp
  4. Setting up an interval to periodically check status
  5. Properly cleaning up the interval

This addresses the core issue by ensuring UI state correctly reflects the actual status of the purchase transaction.


118-118: Consistent UI rendering based on internal state.

The conditional rendering now correctly uses the internal purchaseStatus state instead of the prop, which is consistent with the state management approach.

Also applies to: 183-183, 188-188

lib/chat/actions.tsx (1)

383-398: Ensure the extra createdAt field complies with Message typing

Adding createdAt to a tool message is great for timeout logic, but double-check that Message in @/lib/types marks it as optional. If not, TypeScript will complain in other call sites.
Also consider adding createdAt to all messages when they are first inserted (e.g. in submitUserMessage) to keep the data model uniform.

Comment on lines +525 to +549
const updateChat = async (state: AIState) => {
'use server'

if (session && session.user) {
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 chat: Chat = {
id: chatId,
title,
userId,
createdAt,
messages,
path
}
const session = await auth()

await saveChat(chat)
} else {
return
if (session && session.user) {
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 chat: Chat = {
id: chatId,
title,
userId,
createdAt,
messages,
path
}

await saveChat(chat)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

updateChat omits the chatId field and is fragile when the first message is non-text

  1. The Chat object you persist contains id but not chatId, whereas downstream code (see getUIStateFromAIState) relies on chatId. When the chat is read back from the DB the field will be missing and aiState.chatId will be undefined, breaking UI-key generation and router paths.

  2. title is produced via messages[0].content.substring(...).
    • If messages is empty this will throw.
    • For tool calls or any structured message, content is an array, not a string – calling substring will crash.

A robust fix:

-  const firstMessageContent = messages[0].content as string
-  const title = firstMessageContent.substring(0, 100)
+  const firstContent =
+    typeof messages[0]?.content === 'string' ? messages[0].content : ''
+  const title = firstContent.slice(0, 100)
   const chat: Chat = {
+    chatId,               // keep in sync with UI layer
     id: chatId,
@@
     path
   }
🧰 Tools
🪛 Biome (1.9.4)

[error] 530-530: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines +555 to +561
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

aiStateDone needs error handling & preserves race-condition fix

Good idea to gate aiState.done behind updateChat, but if updateChat throws, aiState.done is silently skipped and UI never updates. Wrap the await in try / catch / finally so the UI is never blocked by a persistence failure while still logging the error:

-runAsyncFnWithoutBlocking(async () => {
-  await updateChat(newState);
-  aiState.done(newState);
-});
+runAsyncFnWithoutBlocking(async () => {
+  try {
+    await updateChat(newState);
+  } catch (err) {
+    console.error('[aiStateDone] failed to persist chat', err);
+    // TODO: surface non-fatal toast to the user if desired
+  } finally {
+    aiState.done(newState);
+  }
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
await updateChat(newState);
aiState.done(newState);
});
};
const aiStateDone = (aiState: MutableAIState<AIState>, newState: AIState) => {
runAsyncFnWithoutBlocking(async () => {
// resolves race condition in aiState.done - the UI refreshed before db was updated
try {
await updateChat(newState);
} catch (err) {
console.error('[aiStateDone] failed to persist chat', err);
// TODO: surface non-fatal toast to the user if desired
} finally {
aiState.done(newState);
}
});
};

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 this pull request may close these issues.

Page refresh firing before saveChat
2 participants