Skip to content

Conversation

@joshuap233
Copy link

@joshuap233 joshuap233 commented Dec 24, 2025

💻 Change Type

  • ✨ feat
  • 🐛 fix
  • ♻️ refactor
  • 💄 style
  • 👷 build
  • ⚡️ perf
  • ✅ test
  • 📝 docs
  • 🔨 chore

🔗 Related Issue

Fixes #8327

🔀 Description of Change

Fixes two issues related to message isolation within threaded conversations:

  1. Fix issue of main topic mixing with messages from its sub-threads.
  2. Fix issue where a thread carried messages from other threads under the same topic.

🧪 How to Test

  • Tested locally
  • Added/updated tests
  • No tests needed

📸 Screenshots / Videos

N/A

📝 Additional Information

Impact of the Bug:
Without this fix, users with existing conversation threads would incur significantly higher operational costs due to unnecessary token expenditure. Each request was effectively billed for double the actual required context.

Summary by Sourcery

Fix message selection for AI responses so that threaded conversations remain isolated from each other and from the main topic.

Bug Fixes:

  • Prevent main topic messages from being mixed with messages from sub-threads when generating AI responses.
  • Ensure each thread only includes its own messages and relevant parent context when generating AI responses.
@vercel
Copy link

vercel bot commented Dec 24, 2025

@joshuap233 is attempting to deploy a commit to the LobeHub OSS Team on Vercel.

A member of the Team first needs to authorize it.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 24, 2025

Reviewer's Guide

Adjusts AI chat message selection logic to properly isolate messages between main topics and their threads when generating responses.

Sequence diagram for AI chat message selection with thread isolation

sequenceDiagram
  actor User
  participant UI as ChatUI
  participant Store as AIChatStore
  participant Action as generateAIChatV2
  participant ThreadSelectors as ThreadSelectors
  participant ChatSelectors as ChatSelectors

  User->>UI: SendMessage(topicId, optional threadId)
  UI->>Store: dispatch generateAIChatV2(data)
  Store->>Action: invoke(data, get, set)

  Action->>Store: get() to read state
  Action->>ThreadSelectors: getThreadsByTopic(activeTopicId)
  ThreadSelectors-->>Action: threads
  Action->>Action: find active ThreadItem by id
  Action->>Action: set sourceMessageId from ThreadItem

  Action->>ChatSelectors: activeBaseChats(get())
  ChatSelectors-->>Action: baseMessages

  Action->>Action: filter out assistantMessageId
  Action->>Action: apply thread isolation filter
  Note over Action: If no activeThreadId
  Action->>Action: keep messages with null threadId

  Note over Action: If activeThreadId is set
  Action->>Action: track isParent flag
  Action->>Action: include messages while isParent is true
  Action->>Action: when message.id == sourceMessageId
  Action->>Action: set isParent to false
  Action->>Action: from then on, include only messages with threadId == activeThreadId

  Action-->>Store: proceed with AI response using filtered messages
  Store-->>UI: update chat with new assistant message
  UI-->>User: display isolated thread conversation
Loading

Class diagram for updated AI chat threading and message selection

classDiagram
  class AIChatStore {
    +generateAIChatV2(data, get, set)
  }

  class ThreadItem {
    +string id
    +string topicId
    +string sourceMessageId
  }

  class UIChatMessage {
    +string id
    +string topicId
    +string threadId
    +string role
    +string content
  }

  class ThreadSelectors {
    +getThreadsByTopic(topicId)
  }

  class ChatSelectors {
    +activeBaseChats(storeState)
  }

  class GenerateAIChatContext {
    +string activeTopicId
    +string activeThreadId
    +string sourceMessageId
    +boolean isParent
    +UIChatMessage[] baseMessages
    +UIChatMessage[] filteredMessages
    +void applyAssistantFilter(assistantMessageId)
    +void applyThreadIsolationFilter()
  }

  AIChatStore --> GenerateAIChatContext : uses
  AIChatStore --> ThreadSelectors : calls
  AIChatStore --> ChatSelectors : calls
  ThreadSelectors --> ThreadItem : returns
  ChatSelectors --> UIChatMessage : returns
  GenerateAIChatContext --> UIChatMessage : filters
  GenerateAIChatContext --> ThreadItem : reads sourceMessageId
Loading

File-Level Changes

Change Details Files
Update base message selection to respect thread boundaries when generating AI chat responses.
  • Import ThreadItem type and threadSelectors to access thread and source message metadata.
  • Lookup threads for the active topic and derive the source message id for the active thread.
  • Derive baseMessages by first excluding the latest assistant message and then filtering so that, when no thread is active, only non-threaded messages are used, and when a thread is active, messages belong either to the active thread or to the parent context up to the thread’s source message.
src/store/chat/slices/aiChat/actions/generateAIChatV2.ts

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Dec 24, 2025
@gru-agent
Copy link
Contributor

gru-agent bot commented Dec 24, 2025

TestGru Assignment

Summary

Link CommitId Status Reason
Detail 39d9f9e ✅ Finished

History Assignment

Files

File Pull Request
src/store/chat/slices/aiChat/actions/generateAIChatV2.ts ❌ Failed (I failed to setup the environment.)

Tip

You can @gru-agent and leave your feedback. TestGru will make adjustments based on your input

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The isParent flag being mutated inside the baseMessages filter callback makes the inclusion logic order-dependent and hard to reason about; consider deriving the parent/child sets in a separate pass (e.g., first slice messages up to sourceMessageId, then filter by threadId) instead of relying on mutable outer state.
  • When resolving sourceMessageId, using threads.find(t => t.id === activeThreadId) instead of forEach would be clearer and allow you to avoid mutating a local variable inside the loop, which would simplify the control flow.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `isParent` flag being mutated inside the `baseMessages` filter callback makes the inclusion logic order-dependent and hard to reason about; consider deriving the parent/child sets in a separate pass (e.g., first slice messages up to `sourceMessageId`, then filter by `threadId`) instead of relying on mutable outer state.
- When resolving `sourceMessageId`, using `threads.find(t => t.id === activeThreadId)` instead of `forEach` would be clearer and allow you to avoid mutating a local variable inside the loop, which would simplify the control flow.

## Individual Comments

### Comment 1
<location> `src/store/chat/slices/aiChat/actions/generateAIChatV2.ts:244-253` </location>
<code_context>
+      }
+    });
+
+    let isParent = true;
     const baseMessages = chatSelectors
       .activeBaseChats(get())
-      .filter((item) => item.id !== data.assistantMessageId);
+      .filter((item) => item.id !== data.assistantMessageId)
+      .filter((item) => {
+        // eslint-disable-next-line eqeqeq
+        if (activeThreadId == null) {
+          // eslint-disable-next-line eqeqeq
+          return item.threadId == null;
+        }

+        const _isParent = isParent;
+        if (item.id === sourceMessageId) {
+          isParent = false;
+        }
+        return item.threadId === activeThreadId || _isParent;
+      });
     if (data.topicId) get().internal_updateTopicLoading(data.topicId, true);
</code_context>

<issue_to_address>
**issue (bug_risk):** The `isParent` side effect inside `filter` can lead to subtle ordering-dependent behavior, especially when `sourceMessageId` is missing.

This relies on mutating `isParent` during `filter` to include all messages up to `sourceMessageId`, then only thread-specific ones. Two issues:

1) If `sourceMessageId` is not in `baseMessages`, `isParent` never becomes `false`, so with a non-null `activeThreadId` you include all messages instead of limiting to that thread.
2) Mutating `isParent` inside `filter` makes control flow order-dependent and harder to reason about.

Consider first finding the index of `sourceMessageId` and slicing/splitting the array, or using an explicit `for` loop / `reduce` to separate parent vs thread messages without side effects.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
@joshuap233 joshuap233 changed the title fix: mixed message error Dec 25, 2025
@joshuap233 joshuap233 changed the title fix: mixed message bug Dec 25, 2025
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Dec 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

1 participant