@lobehub/lobehub 2.1.13 → 2.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -532,7 +532,14 @@
532
532
  "when": 1769362978088,
533
533
  "tag": "0075_add_user_memory_persona",
534
534
  "breakpoints": true
535
+ },
536
+ {
537
+ "idx": 76,
538
+ "version": "7",
539
+ "when": 1770179814971,
540
+ "tag": "0076_add_message_group_index",
541
+ "breakpoints": true
535
542
  }
536
543
  ],
537
544
  "version": "6"
538
- }
545
+ }
@@ -149,6 +149,7 @@ export const messages = pgTable(
149
149
  index('messages_thread_id_idx').on(table.threadId),
150
150
  index('messages_agent_id_idx').on(table.agentId),
151
151
  index('messages_group_id_idx').on(table.groupId),
152
+ index('messages_message_group_id_idx').on(table.messageGroupId),
152
153
  ],
153
154
  );
154
155
 
@@ -33,7 +33,7 @@ const Nav = memo(() => {
33
33
  const switchTopic = useChatStore((s) => s.switchTopic);
34
34
  const [openNewTopicOrSaveTopic] = useChatStore((s) => [s.openNewTopicOrSaveTopic]);
35
35
 
36
- const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
36
+ const { mutate } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
37
37
  const handleNewTopic = () => {
38
38
  // If in agent sub-route, navigate back to agent chat first
39
39
  if (isProfileActive && agentId) {
@@ -46,7 +46,6 @@ const Nav = memo(() => {
46
46
  <Flexbox gap={1} paddingInline={4}>
47
47
  <NavItem
48
48
  icon={MessageSquarePlusIcon}
49
- loading={isValidating}
50
49
  onClick={handleNewTopic}
51
50
  title={tTopic('actions.addNewTopic')}
52
51
  />
@@ -10,9 +10,10 @@ import {
10
10
  Tabs,
11
11
  type TabsProps,
12
12
  } from '@lobehub/ui';
13
+ import { App } from 'antd';
13
14
  import { createStaticStyles, cx } from 'antd-style';
14
15
  import isEqual from 'fast-deep-equal';
15
- import { ChevronDown, ChevronUp, History, Sparkles } from 'lucide-react';
16
+ import { ChevronDown, ChevronUp, History, Sparkles, Undo2 } from 'lucide-react';
16
17
  import { memo, useCallback, useMemo, useState } from 'react';
17
18
  import { useTranslation } from 'react-i18next';
18
19
 
@@ -66,6 +67,7 @@ export interface CompressedGroupMessageProps {
66
67
 
67
68
  const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
68
69
  const { t } = useTranslation('chat');
70
+ const { modal } = App.useApp();
69
71
  const [activeTab, setActiveTab] = useState<string>(() => getStoredTab(id));
70
72
 
71
73
  const handleTabChange = useCallback(
@@ -80,6 +82,16 @@ const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
80
82
  const toggleCompressedGroupExpanded = useConversationStore(
81
83
  (s) => s.toggleCompressedGroupExpanded,
82
84
  );
85
+ const cancelCompression = useConversationStore((s) => s.cancelCompression);
86
+
87
+ const handleCancelCompression = useCallback(() => {
88
+ modal.confirm({
89
+ centered: true,
90
+ content: t('compression.cancelConfirm'),
91
+ onOk: () => cancelCompression(id),
92
+ title: t('compression.cancel'),
93
+ });
94
+ }, [id, cancelCompression, modal, t]);
83
95
 
84
96
  const content = message?.content;
85
97
  const rawCompressedMessages = (message as UIChatMessage)?.compressedMessages;
@@ -145,11 +157,19 @@ const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
145
157
  onChange={handleTabChange}
146
158
  variant={'rounded'}
147
159
  />
148
- <ActionIcon
149
- icon={expanded ? ChevronUp : ChevronDown}
150
- onClick={() => toggleCompressedGroupExpanded(id)}
151
- size={'small'}
152
- />
160
+ <Flexbox gap={4} horizontal>
161
+ <ActionIcon
162
+ icon={Undo2}
163
+ onClick={handleCancelCompression}
164
+ size={'small'}
165
+ title={t('compression.cancel')}
166
+ />
167
+ <ActionIcon
168
+ icon={expanded ? ChevronUp : ChevronDown}
169
+ onClick={() => toggleCompressedGroupExpanded(id)}
170
+ size={'small'}
171
+ />
172
+ </Flexbox>
153
173
  </Flexbox>
154
174
  )}
155
175
  {!showContent ? null : activeTab === 'summary' ? (
@@ -13,6 +13,11 @@ import { dataSelectors } from '../../data/selectors';
13
13
  * Handles message state operations like loading, collapsed, etc.
14
14
  */
15
15
  export interface MessageStateAction {
16
+ /**
17
+ * Cancel compression and restore original messages
18
+ */
19
+ cancelCompression: (id: string) => Promise<void>;
20
+
16
21
  /**
17
22
  * Copy message content to clipboard
18
23
  */
@@ -50,6 +55,26 @@ export const messageStateSlice: StateCreator<
50
55
  [],
51
56
  MessageStateAction
52
57
  > = (set, get) => ({
58
+ cancelCompression: async (id) => {
59
+ const message = dataSelectors.getDisplayMessageById(id)(get());
60
+ if (!message || message.role !== 'compressedGroup') return;
61
+
62
+ const { context, replaceMessages } = get();
63
+ if (!context.agentId || !context.topicId) return;
64
+
65
+ // Call service to cancel compression
66
+ const { messages } = await messageService.cancelCompression({
67
+ agentId: context.agentId,
68
+ groupId: context.groupId,
69
+ messageGroupId: id,
70
+ threadId: context.threadId,
71
+ topicId: context.topicId,
72
+ });
73
+
74
+ // Replace messages with restored original messages
75
+ replaceMessages(messages);
76
+ },
77
+
53
78
  copyMessage: async (id, content) => {
54
79
  const { hooks } = get();
55
80
 
@@ -41,6 +41,9 @@ export default {
41
41
  'chatList.longMessageDetail': 'View Details',
42
42
  'clearCurrentMessages': 'Clear current session messages',
43
43
  'compressedHistory': 'Compressed History',
44
+ 'compression.cancel': 'Uncompress',
45
+ 'compression.cancelConfirm':
46
+ 'Are you sure you want to uncompress? This will restore the original messages.',
44
47
  'compression.history': 'History',
45
48
  'compression.summary': 'Summary',
46
49
  'confirmClearCurrentMessages':
@@ -48,7 +48,32 @@ export const messageRouter = router({
48
48
  return ctx.messageService.addFilesToMessage(id, fileIds, resolved);
49
49
  }),
50
50
 
51
- count: messageProcedure
51
+ /**
52
+ * Cancel compression by deleting the compression group and restoring original messages
53
+ */
54
+ cancelCompression: messageProcedure
55
+ .input(
56
+ z.object({
57
+ agentId: z.string(),
58
+ groupId: z.string().nullable().optional(),
59
+ messageGroupId: z.string(),
60
+ threadId: z.string().nullable().optional(),
61
+ topicId: z.string(),
62
+ }),
63
+ )
64
+ .mutation(async ({ input, ctx }) => {
65
+ const { messageGroupId, agentId, groupId, threadId, topicId } = input;
66
+
67
+ return ctx.messageService.cancelCompression(messageGroupId, {
68
+ agentId,
69
+ groupId,
70
+ threadId,
71
+ topicId,
72
+ });
73
+ }),
74
+
75
+
76
+ count: messageProcedure
52
77
  .input(
53
78
  z
54
79
  .object({
@@ -62,7 +87,9 @@ export const messageRouter = router({
62
87
  return ctx.messageModel.count(input);
63
88
  }),
64
89
 
65
- countWords: messageProcedure
90
+
91
+
92
+ countWords: messageProcedure
66
93
  .input(
67
94
  z
68
95
  .object({
@@ -76,12 +103,13 @@ export const messageRouter = router({
76
103
  return ctx.messageModel.countWords(input);
77
104
  }),
78
105
 
79
- /**
106
+
107
+ /**
80
108
  * Create a compression group for old messages
81
109
  * Creates a placeholder group, marks messages as compressed
82
110
  * Returns messages to summarize for frontend AI generation
83
111
  */
84
- createCompressionGroup: messageProcedure
112
+ createCompressionGroup: messageProcedure
85
113
  .input(
86
114
  z.object({
87
115
  agentId: z.string(),
@@ -102,7 +130,9 @@ export const messageRouter = router({
102
130
  });
103
131
  }),
104
132
 
105
- createMessage: messageProcedure
133
+
134
+
135
+ createMessage: messageProcedure
106
136
  .input(CreateNewMessageParamsSchema)
107
137
  .mutation(async ({ input, ctx }) => {
108
138
  // If there's no agentId but has sessionId, resolve agentId from sessionId
@@ -115,10 +145,11 @@ export const messageRouter = router({
115
145
  return ctx.messageService.createMessage({ ...input, agentId } as any);
116
146
  }),
117
147
 
148
+
118
149
  /**
119
150
  * Finalize compression by updating the group with generated summary
120
151
  */
121
- finalizeCompression: messageProcedure
152
+ finalizeCompression: messageProcedure
122
153
  .input(
123
154
  z.object({
124
155
  agentId: z.string(),
@@ -359,4 +359,23 @@ export class MessageService {
359
359
 
360
360
  return { messages };
361
361
  }
362
+
363
+ /**
364
+ * Cancel compression by deleting the compression group and restoring original messages
365
+ *
366
+ * @param messageGroupId - The compression group ID to cancel
367
+ * @param context - Query options for returning updated messages
368
+ */
369
+ async cancelCompression(
370
+ messageGroupId: string,
371
+ context: QueryOptions,
372
+ ): Promise<{ messages: UIChatMessage[]; success: boolean }> {
373
+ // Delete compression group (this also unmarks messages)
374
+ await this.compressionRepository.deleteCompressionGroup(messageGroupId);
375
+
376
+ // Query updated messages
377
+ const messages = await this.messageModel.query(context, this.getQueryOptions());
378
+
379
+ return { messages, success: true };
380
+ }
362
381
  }
@@ -276,6 +276,20 @@ export class MessageService {
276
276
  messages: (result.messages || []) as unknown as UIChatMessage[],
277
277
  };
278
278
  };
279
+
280
+ /**
281
+ * Cancel compression by deleting the compression group and restoring original messages
282
+ */
283
+ cancelCompression = async (params: {
284
+ agentId: string;
285
+ groupId?: string | null;
286
+ messageGroupId: string;
287
+ threadId?: string | null;
288
+ topicId: string;
289
+ }): Promise<{ messages: UIChatMessage[] }> => {
290
+ const result = await lambdaClient.message.cancelCompression.mutate(params);
291
+ return { messages: (result.messages || []) as unknown as UIChatMessage[] };
292
+ };
279
293
  }
280
294
 
281
295
  export const messageService = new MessageService();