Skip to content

Commit 035994f

Browse files
arvinxxclaude
andauthored
♻️ refactor: refactor chat message model to speed up (lobehub#10053)
* refactor new chat message * ♻️ refactor: Unify message creation methods into single `internal_createMessage` ## Changes ### Method Consolidation - Merged `internal_createMessage` and `internal_createNewMessage` into a single unified method - All message creation now returns `{ id: string, messages: UIChatMessage[] }` - Eliminated redundant API calls by always using `createNewMessage` backend endpoint ### Updated Call Sites (11 locations) **Store Actions:** - `addAIMessage` & `addUserMessage` - Added result validation **AI Chat:** - `generateAIChat.ts` - Extract `result.id` from response - `generateAIChatV2.ts` - Renamed from `internal_createNewMessage` to `internal_createMessage` **Group Chat:** - `generateAIGroupChat.ts` - Extract `result.id` in 3 locations **Thread & Tools:** - `thread/action.ts` - Extract `result.id` - `builtinTool/actions/search.ts` - Extract `result.id` - `plugin/action.ts` - Extract `result.id` ### Test Updates - Updated mocks to return `{ id, messages }` structure - `thread/action.test.ts` - 4 mock updates - `plugin/action.test.ts` - 2 mock updates ## Benefits - **Performance**: All message creation now uses single-request pattern - **Consistency**: Unified return type across all creation flows - **Maintainability**: Single method to maintain instead of two similar ones ## Testing - ✅ Type check: 0 errors - ✅ Unit tests: 175/175 passed - message/action.test.ts: 33/33 - plugin/action.test.ts: 26/26 - thread/action.test.ts: 39/39 - generateAIChat.test.ts: 41/41 - generateAIChatV2.test.ts: 36/36 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ♻️ refactor: optimize message update operations to reduce API calls Optimized two message update operations to reduce network requests: 1. **updatePluginState**: Modified to return updated messages - Backend: `MessageModel.updatePluginState` now accepts options and returns `UpdateMessageResult` - Router: Added `sessionId`, `topicId`, and `useGroup` parameters - Frontend: Service layer passes lab preferences, store uses `replaceMessages` instead of `refreshMessages` - Reduction: 2 requests → 1 request 2. **message.update**: Added `groupAssistantMessages` support - Service: `updateMessage` now passes `useGroup` flag based on lab preferences - Backend model already had infrastructure for returning messages - Reduction: Ensures consistent 1-request pattern Tests passing (26/26 plugin tests, 14/14 integration tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ♻️ refactor: optimize all internal message methods to reduce API calls Optimized 6 internal message methods and 3 error handling scenarios to reduce API calls from 2 requests (update + refresh) to 1 request (update with messages returned): **Internal methods optimized:** - internal_updateMessagePluginError - internal_updateMessageRAG - internal_deleteMessage - internal_refreshToUpdateMessageTools - internal_updatePluginError **Error handling optimized:** - internal_callPluginApi error scenarios (2 locations) - invokeStandaloneTypePlugin invalid settings **Changes:** - Backend: Updated message routers to accept sessionId/topicId/useGroup and return messages - Service: Added getUseGroupPreference() getter to simplify lab preference checks - Service: Updated methods to use getter and return UpdateMessageResult - Store: Changed from refreshMessages() to replaceMessages(result.messages) - Tests: Updated 4 plugin tests to verify replaceMessages instead of refreshMessages **Performance impact:** Each optimized method now makes 1 request instead of 2, reducing network overhead and improving UI responsiveness. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ♻️ refactor: optimize message deletion to reduce API calls and fix group message children deletion **Problem 1 - Group message children not deleted:** - When deleting a `role: 'group'` message, children messages (linked via `parentId`) were not deleted - Tool result messages were also not included in deletion **Problem 2 - Delete operations using refresh pattern:** - `deleteMessage`, `clearMessage`, `clearAllMessages` all used refreshMessages after deletion - This resulted in 2 requests: delete + refresh **Solutions:** 1. **Enhanced deleteMessage in UI layer:** - Added logic to find all children messages via `parentId` for group role messages - Combined with existing tool message deletion logic - All related message IDs are collected and passed to backend in one call - Business logic stays in UI layer, model layer remains simple 2. **Optimized delete operations:** - Backend: `removeMessages` now accepts sessionId/topicId/useGroup and returns messages - Service: `removeMessages` updated to pass options and return UpdateMessageResult - Store: `deleteMessage` now uses replaceMessages with returned data (2 requests → 1 request) - Store: `clearMessage` and `clearAllMessages` directly replace with empty array 3. **Updated tests:** - Fixed 4 tests to verify replaceMessages instead of refreshMessages - Added mock for service to return messages in delete operations - All 33 message action tests passing - All 14 integration tests passing **Performance impact:** - deleteMessage: 2 requests → 1 request - clearMessage/clearAllMessages: 1 delete + 1 refresh → 1 delete + direct clear 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ✅ test: add comprehensive tests for group message deletion with children Added 2 new test cases to verify group message deletion behavior: 1. **Basic group message with children deletion:** - Verifies that deleting a `role: 'group'` message also deletes all children (via `parentId`) - Tests that unrelated messages are preserved 2. **Group message with children that have tool calls:** - Verifies that deleting a group message also deletes: - The group message itself - All children messages (via `parentId`) - Tool result messages from children (via `tool_call_id`) - Ensures complete cleanup of the entire message tree **Implementation enhancement:** - Updated `deleteMessage` to also collect and delete tool results from children messages - Ensures no orphaned tool result messages remain after group deletion **Test results:** - All 35 message action tests passing (2 new tests added) - Verifies complete cascading deletion of group message trees 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c7b7998 commit 035994f

File tree

19 files changed

+644
-314
lines changed

19 files changed

+644
-314
lines changed

packages/database/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"peerDependencies": {
2525
"@electric-sql/pglite": "^0.2.17",
2626
"dayjs": ">=1.11.18",
27-
"drizzle-orm": ">=0.44.6",
27+
"drizzle-orm": ">=0.44.7",
2828
"nanoid": ">=5.1.5",
2929
"pg": ">=8.16.3"
3030
}

packages/database/src/models/message.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ export class MessageModel {
603603
id: string,
604604
{ imageList, ...message }: Partial<UpdateMessageParams>,
605605
options?: {
606+
groupAssistantMessages?: boolean;
606607
postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
607608
sessionId?: string | null;
608609
topicId?: string | null;
@@ -633,6 +634,7 @@ export class MessageModel {
633634
topicId: options.topicId,
634635
},
635636
{
637+
groupAssistantMessages: options.groupAssistantMessages ?? false,
636638
postProcessUrl: options.postProcessUrl,
637639
},
638640
);
@@ -660,16 +662,41 @@ export class MessageModel {
660662
.where(and(eq(messages.userId, this.userId), eq(messages.id, id)));
661663
};
662664

663-
updatePluginState = async (id: string, state: Record<string, any>) => {
665+
updatePluginState = async (
666+
id: string,
667+
state: Record<string, any>,
668+
options?: {
669+
groupAssistantMessages?: boolean;
670+
postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
671+
sessionId?: string | null;
672+
topicId?: string | null;
673+
},
674+
): Promise<UpdateMessageResult> => {
664675
const item = await this.db.query.messagePlugins.findFirst({
665676
where: eq(messagePlugins.id, id),
666677
});
667678
if (!item) throw new Error('Plugin not found');
668679

669-
return this.db
680+
await this.db
670681
.update(messagePlugins)
671682
.set({ state: merge(item.state || {}, state) })
672683
.where(eq(messagePlugins.id, id));
684+
685+
// Return updated messages if sessionId or topicId is provided
686+
if (options?.sessionId !== undefined || options?.topicId !== undefined) {
687+
const messageList = await this.query(
688+
{
689+
sessionId: options.sessionId,
690+
topicId: options.topicId,
691+
},
692+
{
693+
groupAssistantMessages: options.groupAssistantMessages ?? false,
694+
postProcessUrl: options.postProcessUrl,
695+
},
696+
);
697+
return { messages: messageList, success: true };
698+
}
699+
return { success: true };
673700
};
674701

675702
updateMessagePlugin = async (id: string, value: Partial<MessagePluginItem>) => {

packages/types/src/message/ui/params.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -120,55 +120,6 @@ const ChatPluginPayloadSchema = z.object({
120120
type: z.string(),
121121
});
122122

123-
export const CreateMessageParamsSchema = z
124-
.object({
125-
content: z.string(),
126-
role: UIMessageRoleTypeSchema,
127-
sessionId: z.string().nullable().optional(),
128-
error: ChatMessageErrorSchema.nullable().optional(),
129-
fileChunks: z.array(SemanticSearchChunkSchema).optional(),
130-
files: z.array(z.string()).optional(),
131-
fromModel: z.string().optional(),
132-
fromProvider: z.string().optional(),
133-
groupId: z.string().nullable().optional(),
134-
targetId: z.string().nullable().optional(),
135-
threadId: z.string().nullable().optional(),
136-
topicId: z.string().nullable().optional(),
137-
traceId: z.string().optional(),
138-
// Allow additional fields from UIChatMessage (many can be null)
139-
agentId: z.string().optional(),
140-
children: z.any().optional(),
141-
chunksList: z.any().optional(),
142-
createdAt: z.number().optional(),
143-
extra: z.any().optional(),
144-
favorite: z.boolean().optional(),
145-
fileList: z.any().optional(),
146-
id: z.string().optional(),
147-
imageList: z.any().optional(),
148-
meta: z.any().optional(),
149-
metadata: z.any().nullable().optional(),
150-
model: z.string().nullable().optional(),
151-
observationId: z.string().optional(),
152-
parentId: z.string().optional(),
153-
performance: z.any().optional(),
154-
plugin: z.any().optional(),
155-
pluginError: z.any().optional(),
156-
pluginState: z.any().optional(),
157-
provider: z.string().nullable().optional(),
158-
quotaId: z.string().optional(),
159-
ragQuery: z.string().nullable().optional(),
160-
ragQueryId: z.string().nullable().optional(),
161-
reasoning: z.any().optional(),
162-
search: z.any().optional(),
163-
tool_call_id: z.string().optional(),
164-
toolCalls: z.any().optional(),
165-
tools: z.any().optional(),
166-
translate: z.any().optional(),
167-
tts: z.any().optional(),
168-
updatedAt: z.number().optional(),
169-
})
170-
.passthrough();
171-
172123
export const CreateNewMessageParamsSchema = z
173124
.object({
174125
// Required fields

0 commit comments

Comments
 (0)