# Message System

This document describes the Cherry Studio message system architecture, including message lifecycle, state management, and operation interfaces.

## Message Lifecycle

![Message Lifecycle](../../assets/images/message-lifecycle.png)

---

# messageBlock.ts Usage Guide

This file defines the Redux Slice for managing all `MessageBlock` entities in the application. It uses Redux Toolkit's `createSlice` and `createEntityAdapter` to efficiently handle normalized state, providing a set of actions and selectors for interacting with message block data.

## Core Goals

- **State Management**: Centrally manage the state of all `MessageBlock`s. A `MessageBlock` represents different content units within a message (e.g., text, code, images, citations).
- **Normalization**: Uses `createEntityAdapter` to store `MessageBlock` data in a normalized structure (`{ ids: [], entities: {} }`), improving performance and simplifying update logic.
- **Predictability**: Provides explicit actions to modify state and selectors to safely access state.

## Key Concepts

- **Slice (`createSlice`)**: Redux Toolkit's core API for creating Redux modules containing reducer logic, action creators, and initial state.
- **Entity Adapter (`createEntityAdapter`)**: A Redux Toolkit utility that simplifies CRUD (Create, Read, Update, Delete) operations on normalized data. It auto-generates reducer functions and selectors.
- **Selectors**: Functions for deriving and computing data from the Redux store. Selectors can be memoized for improved performance.

## State Structure

The `messageBlocks` slice state structure is defined by `createEntityAdapter`, roughly:

```typescript
{
  ids: string[];                              // Ordered list of all MessageBlock IDs
  entities: { [id: string]: MessageBlock };   // Dictionary mapping ID to MessageBlock objects
  loadingState: 'idle' | 'loading' | 'succeeded' | 'failed';
  error: string | null;
}
```

## Actions

The slice exports the following actions (auto-generated by `createSlice` and `createEntityAdapter` or custom):

- **`upsertOneBlock(payload: MessageBlock)`**: Add a new `MessageBlock` or update an existing one. If the `id` in the payload already exists, it performs an update; otherwise it inserts.

- **`upsertManyBlocks(payload: MessageBlock[])`**: Add or update multiple `MessageBlock`s. Commonly used for batch loading data (e.g., loading all message blocks for a Topic).

- **`removeOneBlock(payload: string)`**: Remove a single `MessageBlock` by the provided `id`.

- **`removeManyBlocks(payload: string[])`**: Remove multiple `MessageBlock`s by the provided `id` array. Commonly used when deleting messages or clearing a Topic.

- **`removeAllBlocks()`**: Remove all `MessageBlock` entities from state.

- **`updateOneBlock(payload: { id: string; changes: Partial<MessageBlock> })`**: Update an existing `MessageBlock`. The `payload` must contain the block's `id` and a `changes` object with the fields to modify.

- **`setMessageBlocksLoading(payload: 'idle' | 'loading')`**: (Custom) Set the `loadingState` property.

- **`setMessageBlocksError(payload: string)`**: (Custom) Set `loadingState` to `'failed'` and record the error message.

**Usage Example (in Thunks or other dispatch locations):**

```typescript
import { upsertOneBlock, removeManyBlocks, updateOneBlock } from './messageBlock'
import store from './store'

// Add or update a block
const newBlock: MessageBlock = { /* ... block data ... */ }
store.dispatch(upsertOneBlock(newBlock))

// Update a block's content
store.dispatch(updateOneBlock({ id: blockId, changes: { content: 'New content' } }))

// Delete multiple blocks
const blockIdsToRemove = ['id1', 'id2']
store.dispatch(removeManyBlocks(blockIdsToRemove))
```

## Selectors

The slice exports base selectors generated by `createEntityAdapter`, accessed through `messageBlocksSelectors`:

- **`messageBlocksSelectors.selectIds(state: RootState): string[]`**: Returns an array of all block IDs.
- **`messageBlocksSelectors.selectEntities(state: RootState): { [id: string]: MessageBlock }`**: Returns a dictionary mapping block IDs to block objects.
- **`messageBlocksSelectors.selectAll(state: RootState): MessageBlock[]`**: Returns an array of all block objects.
- **`messageBlocksSelectors.selectTotal(state: RootState): number`**: Returns the total number of blocks.
- **`messageBlocksSelectors.selectById(state: RootState, id: string): MessageBlock | undefined`**: Returns a single block by ID, or `undefined` if not found.

**Additionally, a custom memoized selector is provided:**

- **`selectFormattedCitationsByBlockId(state: RootState, blockId: string | undefined): Citation[]`**:
  - Takes a `blockId`.
  - If the corresponding block is of `CITATION` type, it extracts and formats citation information (from web search, knowledge base, etc.), deduplicates, renumbers, and returns a `Citation[]` array for UI display.
  - Returns an empty array `[]` if the block doesn't exist or the type doesn't match.
  - This selector encapsulates complex logic for handling different citation sources (Gemini, OpenAI, OpenRouter, Zhipu, etc.).

**Usage Example (in React components or `useSelector`):**

```typescript
import { useSelector } from 'react-redux'
import { messageBlocksSelectors, selectFormattedCitationsByBlockId } from './messageBlock'
import type { RootState } from './store'

// Get all blocks
const allBlocks = useSelector(messageBlocksSelectors.selectAll)

// Get a specific block by ID
const specificBlock = useSelector((state: RootState) => messageBlocksSelectors.selectById(state, someBlockId))

// Get formatted citations for a citation block
const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId))
```

## Integration

The `messageBlock.ts` slice typically works closely with Thunks in `messageThunk.ts`. Thunks handle async logic (API calls, database operations) and dispatch `messageBlock` slice actions to update state as needed. For example, when `messageThunk` receives a streaming response, it dispatches `upsertOneBlock` or `updateOneBlock` to update the corresponding `MessageBlock` in real-time. Similarly, message deletion Thunks dispatch `removeManyBlocks`.

Understanding that `messageBlock.ts` manages **the state itself** while `messageThunk.ts` handles **the async workflows that trigger state changes** is essential for maintaining a clear application architecture.

---

# messageThunk.ts Usage Guide

This file contains core Thunk Action Creators for managing message flows, handling assistant interactions, and synchronizing Redux state with the IndexedDB database. It primarily operates on `Message` and `MessageBlock` objects.

## Core Functions

1. **Send/Receive Messages**: Handle user message sending, trigger assistant responses, stream-process returned data, and parse it into different `MessageBlock`s.
2. **State Management**: Ensure consistency between message and message block state in the Redux store and persisted data in IndexedDB.
3. **Message Operations**: Provide deletion, resend, regeneration, edit-and-resend, append response, clone, and other message lifecycle management functions.
4. **Block Processing**: Dynamically create, update, and save various types of `MessageBlock`s (text, thinking process, tool calls, citations, images, errors, translations, etc.).

## Key Thunks

### 1. `sendMessage(userMessage, userMessageBlocks, assistant, topicId)`

- **Purpose**: Send a new user message.
- **Flow**:
  - Save user message and its blocks to Redux and DB.
  - Check `@mentions` to determine single-model or multi-model response.
  - Create assistant message stub(s).
  - Add stubs to Redux and DB.
  - Queue `fetchAndProcessAssistantResponseImpl` for the `topicId` to get actual responses.

### 2. `fetchAndProcessAssistantResponseImpl(dispatch, getState, topicId, assistant, assistantMessage)`

- **Purpose**: (Internal) Core logic for fetching and processing a single assistant response.
- **Flow**:
  - Set Topic loading state.
  - Prepare context messages.
  - Call `fetchChatCompletion` API service.
  - Process streaming response using `createStreamProcessor`.
  - Handle different event types via callbacks (`onTextChunk`, `onThinkingChunk`, `onToolCallComplete`, `onImageGenerated`, `onError`, `onComplete`, etc.).
- **Block handling**: Creates initial `UNKNOWN` blocks from stream events, real-time creates/updates `MAIN_TEXT` and `THINKING` blocks with throttled updates, creates `TOOL`, `CITATION`, `IMAGE`, `ERROR` blocks as needed.

### 3. `loadTopicMessagesThunk(topicId, forceReload)`

- **Purpose**: Load all messages and associated `MessageBlock`s for a topic from the database.
- **Flow**: Fetch `Topic` and messages from DB, get all related `MessageBlock`s, upsert blocks and messages to Redux.

### 4. Delete Thunks

- `deleteSingleMessageThunk(topicId, messageId)`: Delete a single message and all its blocks.
- `deleteMessageGroupThunk(topicId, askId)`: Delete a user message and all related assistant responses with their blocks.
- `clearTopicMessagesThunk(topicId)`: Clear all messages and blocks under a topic.

### 5. Resend/Regenerate Thunks

- `resendMessageThunk(topicId, userMessageToResend, assistant)`: Resend a user message. Resets all associated assistant responses (clears blocks, marks as PENDING), then re-requests generation.
- `resendUserMessageWithEditThunk(...)`: Update user message's `MAIN_TEXT` block content, then call `resendMessageThunk`.
- `regenerateAssistantResponseThunk(...)`: Regenerate a single assistant response. Resets the message (clears blocks, marks as PENDING), then re-requests generation.

### 6. `appendAssistantResponseThunk(topicId, existingAssistantMessageId, newModel, assistant)`

- **Purpose**: Append a new assistant response using a different model for the same user question.
- Creates a new assistant message stub using `newModel` (with the same `askId`), then queues `fetchAndProcessAssistantResponseImpl`.

### 7. `cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic)`

- **Purpose**: Clone messages (and their blocks) from a source topic up to a given index into a new topic.
- Generates new UUIDs for all cloned messages and blocks, maps `askId` relationships, updates file reference counts.

### 8. `initiateTranslationThunk(messageId, topicId, targetLanguage, sourceBlockId?, sourceLanguage?)`

- **Purpose**: Create an initial `TRANSLATION` type `MessageBlock` for a message.
- Creates a `STREAMING` status `TranslationMessageBlock`, adds to Redux and DB, updates the message's `blocks` list.

## Internal Mechanisms

- **Database Interaction**: Uses helper functions like `saveMessageAndBlocksToDB`, `throttledBlockDbUpdate`, etc. to interact with IndexedDB.
- **State Synchronization**: Thunks coordinate data consistency between the Redux Store and IndexedDB.
- **Queue (`getTopicQueue`)**: Uses `AsyncQueue` to ensure operations on the same topic execute sequentially, avoiding race conditions.
- **Throttling (`throttle`)**: Uses `throttle` from es-toolkit/compat to optimize frequent block updates (text, thinking) during streaming, reducing Redux dispatch and DB write frequency.
- **Error Handling**: Callbacks in `fetchAndProcessAssistantResponseImpl` (especially `onError`) handle stream processing and API call errors, creating `ERROR` type blocks.

---

# useMessageOperations.ts Usage Guide

This file defines a custom React Hook called `useMessageOperations`. Its main purpose is to provide React components with a convenient interface for performing various message operations related to a specific Topic. It encapsulates the logic for calling Redux Thunks and Actions, simplifying component interaction with message data.

## Core Goals

- **Encapsulation**: Wrap complex message operation logic (delete, resend, regenerate, edit, translate, etc.) in easy-to-use functions.
- **Simplification**: Allow components to call operation functions directly without interacting with Redux `dispatch` or Thunks.
- **Context Association**: All operations are associated with the provided `topic` object.

## How to Use

```typescript
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import type { Topic, Message, Assistant, Model } from '@renderer/types'

function MyComponent({ currentTopic, currentAssistant }: { currentTopic: Topic; currentAssistant: Assistant }) {
  const {
    deleteMessage,
    resendMessage,
    regenerateAssistantMessage,
    appendAssistantResponse,
    getTranslationUpdater,
    createTopicBranch,
  } = useMessageOperations(currentTopic)

  // Use the operation functions in your component
}
```

## Return Values

- **`deleteMessage(id: string)`**: Delete a single message by ID.
- **`deleteGroupMessages(askId: string)`**: Delete a group of messages associated with an `askId`.
- **`editMessage(messageId: string, updates: Partial<Message>)`**: Update partial attributes of a message.
- **`resendMessage(message: Message, assistant: Assistant)`**: Resend a user message, triggering regeneration of all associated assistant responses.
- **`resendUserMessageWithEdit(message: Message, editedContent: string, assistant: Assistant)`**: Resend after editing user message text.
- **`clearTopicMessages(_topicId?: string)`**: Clear all messages in the current (or specified) topic.
- **`createNewContext()`**: Emit a global event to clear display and prepare a new context.
- **`displayCount`**: Current `displayCount` value from Redux store.
- **`pauseMessages()`**: Abort ongoing message generation for the current topic.
- **`resumeMessage(message: Message, assistant: Assistant)`**: Resume/resend a user message.
- **`regenerateAssistantMessage(message: Message, assistant: Assistant)`**: Regenerate a specific assistant message response.
- **`appendAssistantResponse(existingAssistantMessage: Message, newModel: Model, assistant: Assistant)`**: Append a new assistant response using a different model.
- **`getTranslationUpdater(messageId, targetLanguage, sourceBlockId?, sourceLanguage?)`**: Get a function for progressively updating a translation block's content. Returns an async updater function or `null` on failure.
- **`createTopicBranch(sourceTopicId, branchPointIndex, newTopic)`**: Clone messages from a source topic to a new topic (which must already exist in Redux and DB).

## Related Hooks

- **`useTopicMessages(topic: Topic)`**: Get the message list for a topic.
- **`useTopicLoading(topic: Topic)`**: Get the loading state for a topic.

These hooks can be combined with `useMessageOperations` for convenient data access and operations within components.
