Skip to content

Conversation

@Sg312
Copy link
Contributor

@Sg312 Sg312 commented Jan 11, 2026

Summary

Fix copilot chat loading

Type of Change

  • Bug fix

Testing

Manual

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Jan 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Review Updated (UTC)
docs Skipped Skipped Jan 11, 2026 2:24am

@Sg312 Sg312 merged commit e347486 into staging Jan 11, 2026
6 checks passed
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 11, 2026

Greptile Overview

Greptile Summary

What Changed

This PR fixes copilot chat loading issues by reorganizing the message persistence strategy. The key architectural change is that the server no longer saves complete messages - it only updates the conversationId. The client now owns message persistence by calling the update-messages endpoint with full contentBlocks data after streaming completes.

Core Changes

  1. Server-side (route.ts): Removed code that saved user and assistant messages during streaming. Now only updates conversationId to maintain conversation continuity without overwriting client data.

  2. Client-side (store.ts):

    • Enhanced deepClone validation to detect serialization failures
    • Added state verb prefixes (Executing/Executed/Failed) to tool display names for better verb-noun rendering
    • Improved error handling and logging for save operations
    • Client now explicitly saves messages via update-messages endpoint with complete contentBlocks
  3. UI Improvements:

    • Simplified streaming indicator to always show while streaming (removed complex conditional logic)
    • Redesigned tool call input display with card layout and JSON editor for complex values
    • Made scroll stickiness configurable (80px for copilot, 100px default)

Architecture Benefits

  • Prevents data loss: Client has the authoritative rendering state (contentBlocks with tool call states, timestamps, etc.)
  • Reduces redundancy: Server doesn't duplicate work that client must do anyway
  • Clearer separation: Server handles conversation state, client handles presentation state

Issues Found

Critical Issues

None - the changes follow a sound architectural pattern.

Logic Issues

  1. deepClone string comparison bug (line 596): Checks json === 'undefined' which never matches since JSON.stringify(undefined) returns the value undefined, not the string.

  2. Missing updatedAt update (line 812-827): When responseId is falsy, the chat's updatedAt timestamp isn't updated, potentially making recent activity invisible in chat lists.

Style Suggestions

  1. Silent save failures (line 3214-3219): Failed saves only log errors without user-facing feedback, which could lead to confusion about whether chats are being saved.

Confidence Score: 4/5

  • This PR is safe to merge with minor issues that should be addressed
  • Score of 4 reflects that the PR successfully fixes the loading issue through a solid architectural change (client-side message ownership), but contains two logic bugs: (1) a string comparison bug in deepClone that may not properly detect serialization failures, and (2) missing updatedAt timestamp updates when responseId is falsy. These are not critical but should be fixed to ensure robust error handling and proper chat activity tracking.
  • Pay attention to apps/sim/stores/panel/copilot/store.ts (deepClone validation logic) and apps/sim/app/api/copilot/chat/route.ts (timestamp update logic)

Important Files Changed

File Analysis

Filename Score Overview
apps/sim/app/api/copilot/chat/route.ts 4/5 Server no longer saves messages to DB (delegates to client), only updates conversationId - reduces redundant saves and prevents overwriting client's richer contentBlocks data
apps/sim/stores/panel/copilot/store.ts 4/5 Enhanced deepClone validation, added state verb prefixes to tool display names, improved error handling in save operations, added extensive logging
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx 3/5 Redesigned input display UI with card layout, added JSON editor for complex values, improved formatting - significant visual changes with new imports

Sequence Diagram

sequenceDiagram
    participant Client as Copilot UI
    participant Store as Copilot Store
    participant ChatAPI as /api/copilot/chat
    participant UpdateAPI as /api/copilot/chat/update-messages
    participant Agent as Sim Agent
    participant DB as Database

    Client->>Store: sendMessage()
    Store->>ChatAPI: POST with message
    ChatAPI->>Agent: Forward to Sim Agent
    Agent-->>ChatAPI: Stream SSE events
    
    loop Streaming
        ChatAPI-->>Store: Forward SSE events
        Store->>Store: Build contentBlocks
        Store->>Client: Update UI with content
    end
    
    ChatAPI->>DB: Update conversationId only
    Note over ChatAPI,DB: Server no longer saves messages<br/>to avoid overwriting client data
    
    Store->>Store: Serialize messages with contentBlocks
    Store->>UpdateAPI: POST full messages array
    UpdateAPI->>DB: Save messages with contentBlocks
    Note over UpdateAPI,DB: Client has full rendering data<br/>including tool states
    
    UpdateAPI-->>Store: Success response
    Store->>Client: Update complete
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

return JSON.parse(JSON.stringify(obj))
} catch {
const json = JSON.stringify(obj)
if (!json || json === 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

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

The check json === 'undefined' will never match the string "undefined" from JSON.stringify(undefined). When you call JSON.stringify(undefined), it returns undefined (the value, not the string), not the string "undefined".

This condition should check for the undefined value itself:

Suggested change
if (!json || json === 'undefined') {
if (!json || json === undefined) {

Or more simply:

Suggested change
if (!json || json === 'undefined') {
if (!json) {

This ensures the deepClone function properly handles cases where JSON.stringify returns undefined.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/stores/panel/copilot/store.ts
Line: 596:596

Comment:
The check `json === 'undefined'` will never match the string "undefined" from `JSON.stringify(undefined)`. When you call `JSON.stringify(undefined)`, it returns `undefined` (the value, not the string), not the string `"undefined"`.

This condition should check for the undefined value itself:

```suggestion
    if (!json || json === undefined) {
```

Or more simply:
```suggestion
    if (!json) {
```

This ensures the deepClone function properly handles cases where JSON.stringify returns undefined.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +812 to +827
if (responseId) {
await db
.update(copilotChats)
.set({
updatedAt: new Date(),
conversationId: responseId,
})
.where(eq(copilotChats.id, actualChatId!))

logger.info(`[${tracker.requestId}] Updated chat ${actualChatId} with new messages`, {
messageCount: updatedMessages.length,
savedUserMessage: true,
savedAssistantMessage: assistantContent.trim().length > 0,
updatedConversationId: responseId || null,
})
logger.info(
`[${tracker.requestId}] Updated conversationId for chat ${actualChatId}`,
{
updatedConversationId: responseId,
}
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The server now only updates conversationId when responseId is truthy, but there's no update to updatedAt when responseId is falsy. This means if a chat completes without a valid responseId, the database won't reflect that the chat was recently active.

Consider always updating updatedAt to track chat activity:

Suggested change
if (responseId) {
await db
.update(copilotChats)
.set({
updatedAt: new Date(),
conversationId: responseId,
})
.where(eq(copilotChats.id, actualChatId!))
logger.info(`[${tracker.requestId}] Updated chat ${actualChatId} with new messages`, {
messageCount: updatedMessages.length,
savedUserMessage: true,
savedAssistantMessage: assistantContent.trim().length > 0,
updatedConversationId: responseId || null,
})
logger.info(
`[${tracker.requestId}] Updated conversationId for chat ${actualChatId}`,
{
updatedConversationId: responseId,
}
)
}
if (responseId) {
await db
.update(copilotChats)
.set({
updatedAt: new Date(),
conversationId: responseId,
})
.where(eq(copilotChats.id, actualChatId!))
logger.info(
`[${tracker.requestId}] Updated conversationId for chat ${actualChatId}`,
{
updatedConversationId: responseId,
}
)
} else {
// Still update the timestamp even without conversationId
await db
.update(copilotChats)
.set({ updatedAt: new Date() })
.where(eq(copilotChats.id, actualChatId!))
}

This ensures chat activity is properly tracked even in edge cases.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/api/copilot/chat/route.ts
Line: 812:827

Comment:
The server now only updates `conversationId` when `responseId` is truthy, but there's no update to `updatedAt` when `responseId` is falsy. This means if a chat completes without a valid `responseId`, the database won't reflect that the chat was recently active.

Consider always updating `updatedAt` to track chat activity:

```suggestion
              if (responseId) {
                await db
                  .update(copilotChats)
                  .set({
                    updatedAt: new Date(),
                    conversationId: responseId,
                  })
                  .where(eq(copilotChats.id, actualChatId!))

                logger.info(
                  `[${tracker.requestId}] Updated conversationId for chat ${actualChatId}`,
                  {
                    updatedConversationId: responseId,
                  }
                )
              } else {
                // Still update the timestamp even without conversationId
                await db
                  .update(copilotChats)
                  .set({ updatedAt: new Date() })
                  .where(eq(copilotChats.id, actualChatId!))
              }
```

This ensures chat activity is properly tracked even in edge cases.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +3214 to +3219
if (!saveResponse.ok) {
const errorText = await saveResponse.text().catch(() => '')
logger.error('[Stream Done] Failed to save messages to DB', {
status: saveResponse.status,
error: errorText,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

The error handling only logs the error but doesn't provide user feedback. If saving messages fails silently, users won't know their chat history might not be persisted.

Consider setting an error state that the UI can display:

Suggested change
if (!saveResponse.ok) {
const errorText = await saveResponse.text().catch(() => '')
logger.error('[Stream Done] Failed to save messages to DB', {
status: saveResponse.status,
error: errorText,
})
if (!saveResponse.ok) {
const errorText = await saveResponse.text().catch(() => '')
logger.error('[Stream Done] Failed to save messages to DB', {
status: saveResponse.status,
error: errorText,
})
// Set error state for user visibility
set({ saveError: 'Failed to save chat messages' })

This provides user awareness when persistence fails.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/stores/panel/copilot/store.ts
Line: 3214:3219

Comment:
The error handling only logs the error but doesn't provide user feedback. If saving messages fails silently, users won't know their chat history might not be persisted.

Consider setting an error state that the UI can display:

```suggestion
            if (!saveResponse.ok) {
              const errorText = await saveResponse.text().catch(() => '')
              logger.error('[Stream Done] Failed to save messages to DB', {
                status: saveResponse.status,
                error: errorText,
              })
              // Set error state for user visibility
              set({ saveError: 'Failed to save chat messages' })
```

This provides user awareness when persistence fails.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

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.

3 participants