@lobehub/chat 1.33.5 → 1.34.0

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.
Files changed (203) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/chat.json +7 -0
  4. package/locales/ar/common.json +2 -0
  5. package/locales/ar/models.json +24 -0
  6. package/locales/ar/setting.json +5 -0
  7. package/locales/ar/thread.json +5 -0
  8. package/locales/bg-BG/chat.json +7 -0
  9. package/locales/bg-BG/common.json +2 -0
  10. package/locales/bg-BG/models.json +24 -0
  11. package/locales/bg-BG/setting.json +5 -0
  12. package/locales/bg-BG/thread.json +5 -0
  13. package/locales/de-DE/chat.json +7 -0
  14. package/locales/de-DE/common.json +2 -0
  15. package/locales/de-DE/models.json +24 -0
  16. package/locales/de-DE/setting.json +5 -0
  17. package/locales/de-DE/thread.json +5 -0
  18. package/locales/en-US/chat.json +7 -0
  19. package/locales/en-US/common.json +2 -0
  20. package/locales/en-US/models.json +24 -0
  21. package/locales/en-US/setting.json +5 -0
  22. package/locales/en-US/thread.json +5 -0
  23. package/locales/es-ES/chat.json +7 -0
  24. package/locales/es-ES/common.json +2 -0
  25. package/locales/es-ES/models.json +24 -0
  26. package/locales/es-ES/setting.json +5 -0
  27. package/locales/es-ES/thread.json +5 -0
  28. package/locales/fa-IR/chat.json +7 -0
  29. package/locales/fa-IR/common.json +2 -0
  30. package/locales/fa-IR/models.json +24 -0
  31. package/locales/fa-IR/setting.json +5 -0
  32. package/locales/fa-IR/thread.json +5 -0
  33. package/locales/fr-FR/chat.json +7 -0
  34. package/locales/fr-FR/common.json +2 -0
  35. package/locales/fr-FR/models.json +24 -0
  36. package/locales/fr-FR/setting.json +5 -0
  37. package/locales/fr-FR/thread.json +5 -0
  38. package/locales/it-IT/chat.json +7 -0
  39. package/locales/it-IT/common.json +2 -0
  40. package/locales/it-IT/models.json +24 -0
  41. package/locales/it-IT/setting.json +5 -0
  42. package/locales/it-IT/thread.json +5 -0
  43. package/locales/ja-JP/chat.json +7 -0
  44. package/locales/ja-JP/common.json +2 -0
  45. package/locales/ja-JP/models.json +24 -0
  46. package/locales/ja-JP/setting.json +5 -0
  47. package/locales/ja-JP/thread.json +5 -0
  48. package/locales/ko-KR/chat.json +7 -0
  49. package/locales/ko-KR/common.json +2 -0
  50. package/locales/ko-KR/models.json +24 -0
  51. package/locales/ko-KR/setting.json +5 -0
  52. package/locales/ko-KR/thread.json +5 -0
  53. package/locales/nl-NL/chat.json +7 -0
  54. package/locales/nl-NL/common.json +2 -0
  55. package/locales/nl-NL/models.json +24 -0
  56. package/locales/nl-NL/setting.json +5 -0
  57. package/locales/nl-NL/thread.json +5 -0
  58. package/locales/pl-PL/chat.json +7 -0
  59. package/locales/pl-PL/common.json +2 -0
  60. package/locales/pl-PL/models.json +24 -0
  61. package/locales/pl-PL/setting.json +5 -0
  62. package/locales/pl-PL/thread.json +5 -0
  63. package/locales/pt-BR/chat.json +7 -0
  64. package/locales/pt-BR/common.json +2 -0
  65. package/locales/pt-BR/models.json +24 -0
  66. package/locales/pt-BR/setting.json +5 -0
  67. package/locales/pt-BR/thread.json +5 -0
  68. package/locales/ru-RU/chat.json +7 -0
  69. package/locales/ru-RU/common.json +2 -0
  70. package/locales/ru-RU/models.json +24 -0
  71. package/locales/ru-RU/setting.json +5 -0
  72. package/locales/ru-RU/thread.json +5 -0
  73. package/locales/tr-TR/chat.json +7 -0
  74. package/locales/tr-TR/common.json +2 -0
  75. package/locales/tr-TR/models.json +24 -0
  76. package/locales/tr-TR/setting.json +5 -0
  77. package/locales/tr-TR/thread.json +5 -0
  78. package/locales/vi-VN/chat.json +7 -0
  79. package/locales/vi-VN/common.json +2 -0
  80. package/locales/vi-VN/models.json +24 -0
  81. package/locales/vi-VN/setting.json +5 -0
  82. package/locales/vi-VN/thread.json +5 -0
  83. package/locales/zh-CN/chat.json +7 -0
  84. package/locales/zh-CN/common.json +2 -0
  85. package/locales/zh-CN/models.json +24 -0
  86. package/locales/zh-CN/setting.json +5 -0
  87. package/locales/zh-CN/thread.json +5 -0
  88. package/locales/zh-TW/chat.json +7 -0
  89. package/locales/zh-TW/common.json +2 -0
  90. package/locales/zh-TW/models.json +24 -0
  91. package/locales/zh-TW/setting.json +5 -0
  92. package/locales/zh-TW/thread.json +5 -0
  93. package/package.json +1 -1
  94. package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +2 -0
  95. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatHydration/index.tsx +11 -2
  96. package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/index.tsx +7 -9
  97. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/index.tsx +7 -2
  98. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/Thread.tsx +62 -0
  99. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/ThreadItem.tsx +68 -0
  100. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +62 -2
  101. package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +47 -0
  102. package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +3 -2
  103. package/src/app/(main)/chat/(workspace)/@portal/_layout/Mobile.tsx +47 -6
  104. package/src/app/(main)/chat/(workspace)/@topic/features/SkeletonList.tsx +3 -2
  105. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +10 -3
  106. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/FlatMode/index.tsx +1 -1
  107. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/Content.tsx +164 -0
  108. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/index.tsx +98 -0
  109. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicItem.tsx → TopicItem/index.tsx} +33 -22
  110. package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +12 -5
  111. package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +1 -2
  112. package/src/const/message.ts +2 -0
  113. package/src/const/settings/systemAgent.ts +1 -0
  114. package/src/database/server/migrations/0012_add_thread.sql +39 -0
  115. package/src/database/server/migrations/meta/0012_snapshot.json +3671 -0
  116. package/src/database/server/migrations/meta/_journal.json +7 -0
  117. package/src/database/server/models/_template.ts +2 -2
  118. package/src/database/server/models/message.ts +1 -0
  119. package/src/database/server/models/thread.ts +79 -0
  120. package/src/database/server/schemas/lobechat/message.ts +2 -1
  121. package/src/database/server/schemas/lobechat/relations.ts +13 -1
  122. package/src/database/server/schemas/lobechat/topic.ts +30 -1
  123. package/src/database/server/utils/idGenerator.ts +1 -0
  124. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +6 -4
  125. package/src/features/ChatInput/ActionBar/Token/index.tsx +24 -5
  126. package/src/features/ChatInput/ActionBar/config.ts +3 -2
  127. package/src/features/ChatInput/Desktop/index.tsx +15 -7
  128. package/src/features/ChatInput/Mobile/index.tsx +4 -4
  129. package/src/features/Conversation/Actions/Assistant.tsx +24 -5
  130. package/src/features/Conversation/Actions/User.tsx +21 -4
  131. package/src/features/Conversation/Actions/index.ts +1 -66
  132. package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/index.tsx +3 -1
  133. package/src/features/Conversation/Messages/{Tool/index.tsx → Assistant/ToolCallItem/Tool.tsx} +10 -11
  134. package/src/features/Conversation/Messages/Assistant/ToolCallItem/index.tsx +5 -3
  135. package/src/features/Conversation/Messages/Assistant/index.tsx +22 -14
  136. package/src/features/Conversation/Messages/index.ts +0 -2
  137. package/src/features/Conversation/components/AutoScroll.tsx +1 -1
  138. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +79 -5
  139. package/src/features/Conversation/components/ChatItem/InPortalThreadContext.ts +3 -0
  140. package/src/features/Conversation/components/ChatItem/index.tsx +16 -5
  141. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +9 -1
  142. package/src/features/Conversation/components/ThreadDivider/index.tsx +19 -0
  143. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +19 -4
  144. package/src/features/Portal/Thread/Chat/ChatInput/Footer.tsx +90 -0
  145. package/src/features/Portal/Thread/Chat/ChatInput/TextArea.tsx +30 -0
  146. package/src/features/Portal/Thread/Chat/ChatInput/index.tsx +66 -0
  147. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +50 -0
  148. package/src/features/Portal/Thread/Chat/ChatItem.tsx +62 -0
  149. package/src/features/Portal/Thread/Chat/ChatList.tsx +49 -0
  150. package/src/features/Portal/Thread/Chat/ThreadDivider/index.tsx +19 -0
  151. package/src/features/Portal/Thread/Chat/index.tsx +28 -0
  152. package/src/features/Portal/Thread/Header/Active.tsx +35 -0
  153. package/src/features/Portal/Thread/Header/New.tsx +37 -0
  154. package/src/features/Portal/Thread/Header/Title.tsx +18 -0
  155. package/src/features/Portal/Thread/Header/index.tsx +20 -0
  156. package/src/features/Portal/Thread/hook.ts +8 -0
  157. package/src/features/Portal/Thread/index.ts +12 -0
  158. package/src/features/Portal/router.tsx +2 -1
  159. package/src/hooks/useFetchTopics.ts +7 -1
  160. package/src/locales/default/chat.ts +8 -1
  161. package/src/locales/default/common.ts +3 -0
  162. package/src/locales/default/index.ts +2 -0
  163. package/src/locales/default/setting.ts +5 -0
  164. package/src/locales/default/thread.ts +5 -0
  165. package/src/server/routers/lambda/index.ts +2 -0
  166. package/src/server/routers/lambda/thread.ts +83 -0
  167. package/src/services/thread.ts +54 -0
  168. package/src/store/chat/initialState.ts +3 -0
  169. package/src/store/chat/selectors.ts +2 -1
  170. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +1 -1
  171. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +1 -1
  172. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +31 -8
  173. package/src/store/chat/slices/aiChat/actions/rag.ts +1 -1
  174. package/src/store/chat/slices/message/selectors.test.ts +3 -3
  175. package/src/store/chat/slices/message/selectors.ts +50 -29
  176. package/src/store/chat/slices/plugin/action.ts +26 -8
  177. package/src/store/chat/slices/portal/action.ts +1 -0
  178. package/src/store/chat/slices/portal/initialState.ts +1 -0
  179. package/src/store/chat/slices/portal/selectors/thread.ts +17 -0
  180. package/src/store/chat/slices/portal/selectors.ts +2 -0
  181. package/src/store/chat/slices/thread/action.ts +326 -0
  182. package/src/store/chat/slices/thread/initialState.ts +34 -0
  183. package/src/store/chat/slices/thread/reducer.ts +48 -0
  184. package/src/store/chat/slices/thread/selectors/index.ts +202 -0
  185. package/src/store/chat/slices/thread/selectors/util.ts +22 -0
  186. package/src/store/chat/slices/topic/action.ts +5 -1
  187. package/src/store/chat/store.ts +5 -2
  188. package/src/store/global/initialState.ts +4 -0
  189. package/src/store/global/selectors.ts +4 -0
  190. package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
  191. package/src/types/message/index.ts +17 -1
  192. package/src/types/topic/index.ts +1 -0
  193. package/src/types/topic/thread.ts +42 -0
  194. package/src/types/user/settings/systemAgent.ts +1 -0
  195. package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +0 -11
  196. package/src/app/(main)/chat/(workspace)/_layout/Mobile/PortalModal.tsx +0 -35
  197. /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/SendMore.tsx +0 -0
  198. /package/src/{features → app/(main)/chat/(workspace)/@conversation/features}/ChatInput/Desktop/Footer/ShortcutHint.tsx +0 -0
  199. /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{DefaultContent.tsx → TopicItem/DefaultContent.tsx} +0 -0
  200. /package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/{TopicContent.tsx → TopicItem/TopicContent.tsx} +0 -0
  201. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/PluginResultJSON.tsx +0 -0
  202. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/Settings.tsx +0 -0
  203. /package/src/features/Conversation/Messages/{Tool → Assistant/ToolCallItem}/Inspector/style.ts +0 -0
@@ -84,6 +84,13 @@
84
84
  "when": 1731138670427,
85
85
  "tag": "0011_add_topic_history_summary",
86
86
  "breakpoints": true
87
+ },
88
+ {
89
+ "idx": 12,
90
+ "version": "7",
91
+ "when": 1731858381716,
92
+ "tag": "0012_add_thread",
93
+ "breakpoints": true
87
94
  }
88
95
  ],
89
96
  "version": "6"
@@ -3,7 +3,7 @@ import { and, desc } from 'drizzle-orm/expressions';
3
3
 
4
4
  import { serverDB } from '@/database/server';
5
5
 
6
- import { NewSessionGroup, UserItem, sessionGroups } from '../schemas/lobechat';
6
+ import { NewSessionGroup, SessionGroupItem, sessionGroups } from '../schemas/lobechat';
7
7
 
8
8
  export class TemplateModel {
9
9
  private userId: string;
@@ -44,7 +44,7 @@ export class TemplateModel {
44
44
  });
45
45
  };
46
46
 
47
- async update(id: string, value: Partial<UserItem>) {
47
+ async update(id: string, value: Partial<SessionGroupItem>) {
48
48
  return serverDB
49
49
  .update(sessionGroups)
50
50
  .set({ ...value, updatedAt: new Date() })
@@ -69,6 +69,7 @@ export class MessageModel {
69
69
  updatedAt: messages.updatedAt,
70
70
 
71
71
  parentId: messages.parentId,
72
+ threadId: messages.threadId,
72
73
 
73
74
  tools: messages.tools,
74
75
  tool_call_id: messagePlugins.toolCallId,
@@ -0,0 +1,79 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { and, desc } from 'drizzle-orm/expressions';
3
+
4
+ import { serverDB } from '@/database/server';
5
+ import { CreateThreadParams, ThreadStatus } from '@/types/topic';
6
+
7
+ import { ThreadItem, threads } from '../schemas/lobechat';
8
+
9
+ const queryColumns = {
10
+ createdAt: threads.createdAt,
11
+ id: threads.id,
12
+ parentThreadId: threads.parentThreadId,
13
+ sourceMessageId: threads.sourceMessageId,
14
+ status: threads.status,
15
+ title: threads.title,
16
+ topicId: threads.topicId,
17
+ type: threads.type,
18
+ updatedAt: threads.updatedAt,
19
+ };
20
+
21
+ export class ThreadModel {
22
+ private userId: string;
23
+
24
+ constructor(userId: string) {
25
+ this.userId = userId;
26
+ }
27
+
28
+ create = async (params: CreateThreadParams) => {
29
+ // @ts-ignore
30
+ const [result] = await serverDB
31
+ .insert(threads)
32
+ .values({ ...params, status: ThreadStatus.Active, userId: this.userId })
33
+ .onConflictDoNothing()
34
+ .returning();
35
+
36
+ return result;
37
+ };
38
+
39
+ delete = async (id: string) => {
40
+ return serverDB.delete(threads).where(and(eq(threads.id, id), eq(threads.userId, this.userId)));
41
+ };
42
+
43
+ deleteAll = async () => {
44
+ return serverDB.delete(threads).where(eq(threads.userId, this.userId));
45
+ };
46
+
47
+ query = async () => {
48
+ const data = await serverDB
49
+ .select(queryColumns)
50
+ .from(threads)
51
+ .where(eq(threads.userId, this.userId))
52
+ .orderBy(desc(threads.updatedAt));
53
+
54
+ return data as ThreadItem[];
55
+ };
56
+
57
+ queryByTopicId = async (topicId: string) => {
58
+ const data = await serverDB
59
+ .select(queryColumns)
60
+ .from(threads)
61
+ .where(and(eq(threads.topicId, topicId), eq(threads.userId, this.userId)))
62
+ .orderBy(desc(threads.updatedAt));
63
+
64
+ return data as ThreadItem[];
65
+ };
66
+
67
+ findById = async (id: string) => {
68
+ return serverDB.query.threads.findFirst({
69
+ where: and(eq(threads.id, id), eq(threads.userId, this.userId)),
70
+ });
71
+ };
72
+
73
+ async update(id: string, value: Partial<ThreadItem>) {
74
+ return serverDB
75
+ .update(threads)
76
+ .set({ ...value, updatedAt: new Date() })
77
+ .where(and(eq(threads.id, id), eq(threads.userId, this.userId)));
78
+ }
79
+ }
@@ -18,7 +18,7 @@ import { agents } from './agent';
18
18
  import { files } from './file';
19
19
  import { chunks, embeddings } from './rag';
20
20
  import { sessions } from './session';
21
- import { topics } from './topic';
21
+ import { threads, topics } from './topic';
22
22
  import { users } from './user';
23
23
 
24
24
  // @ts-ignore
@@ -51,6 +51,7 @@ export const messages = pgTable(
51
51
  .notNull(),
52
52
  sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
53
53
  topicId: text('topic_id').references(() => topics.id, { onDelete: 'cascade' }),
54
+ threadId: text('thread_id').references(() => threads.id, { onDelete: 'cascade' }),
54
55
  // @ts-ignore
55
56
  parentId: text('parent_id').references(() => messages.id, { onDelete: 'set null' }),
56
57
  quotaId: text('quota_id').references(() => messages.id, { onDelete: 'set null' }),
@@ -8,7 +8,7 @@ import { files, knowledgeBases } from './file';
8
8
  import { messages, messagesFiles } from './message';
9
9
  import { unstructuredChunks } from './rag';
10
10
  import { sessionGroups, sessions } from './session';
11
- import { topics } from './topic';
11
+ import { threads, topics } from './topic';
12
12
 
13
13
  export const agentsToSessions = pgTable(
14
14
  'agents_to_sessions',
@@ -47,6 +47,13 @@ export const topicRelations = relations(topics, ({ one }) => ({
47
47
  }),
48
48
  }));
49
49
 
50
+ export const threadsRelations = relations(threads, ({ one }) => ({
51
+ sourceMessage: one(messages, {
52
+ fields: [threads.sourceMessageId],
53
+ references: [messages.id],
54
+ }),
55
+ }));
56
+
50
57
  export const messagesRelations = relations(messages, ({ many, one }) => ({
51
58
  filesToMessages: many(messagesFiles),
52
59
 
@@ -64,6 +71,11 @@ export const messagesRelations = relations(messages, ({ many, one }) => ({
64
71
  fields: [messages.topicId],
65
72
  references: [topics.id],
66
73
  }),
74
+
75
+ thread: one(threads, {
76
+ fields: [messages.threadId],
77
+ references: [threads.id],
78
+ }),
67
79
  }));
68
80
 
69
81
  export const agentsRelations = relations(agents, ({ many }) => ({
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
  import { boolean, jsonb, pgTable, text, unique } from 'drizzle-orm/pg-core';
3
+ import { createInsertSchema } from 'drizzle-zod';
3
4
 
4
5
  import { idGenerator } from '../../utils/idGenerator';
5
- import { timestamps } from './_helpers';
6
+ import { timestamps, timestamptz } from './_helpers';
6
7
  import { sessions } from './session';
7
8
  import { users } from './user';
8
9
 
@@ -30,3 +31,31 @@ export const topics = pgTable(
30
31
 
31
32
  export type NewTopic = typeof topics.$inferInsert;
32
33
  export type TopicItem = typeof topics.$inferSelect;
34
+
35
+ // @ts-ignore
36
+ export const threads = pgTable('threads', {
37
+ id: text('id')
38
+ .$defaultFn(() => idGenerator('threads', 16))
39
+ .primaryKey(),
40
+
41
+ title: text('title'),
42
+ type: text('type', { enum: ['continuation', 'standalone'] }).notNull(),
43
+ status: text('status', { enum: ['active', 'deprecated', 'archived'] }).default('active'),
44
+ topicId: text('topic_id')
45
+ .references(() => topics.id, { onDelete: 'cascade' })
46
+ .notNull(),
47
+ sourceMessageId: text('source_message_id').notNull(),
48
+ // @ts-ignore
49
+ parentThreadId: text('parent_thread_id').references(() => threads.id, { onDelete: 'set null' }),
50
+
51
+ userId: text('user_id')
52
+ .references(() => users.id, { onDelete: 'cascade' })
53
+ .notNull(),
54
+
55
+ lastActiveAt: timestamptz('last_active_at').defaultNow(),
56
+ ...timestamps,
57
+ });
58
+
59
+ export type NewThread = typeof threads.$inferInsert;
60
+ export type ThreadItem = typeof threads.$inferSelect;
61
+ export const insertThreadSchema = createInsertSchema(threads);
@@ -10,6 +10,7 @@ const prefixes = {
10
10
  plugins: 'plg',
11
11
  sessionGroups: 'sg',
12
12
  sessions: 'ssn',
13
+ threads: 'thd',
13
14
  topics: 'tpc',
14
15
  user: 'user',
15
16
  } as const;
@@ -10,7 +10,7 @@ import { useTokenCount } from '@/hooks/useTokenCount';
10
10
  import { useAgentStore } from '@/store/agent';
11
11
  import { agentSelectors } from '@/store/agent/selectors';
12
12
  import { useChatStore } from '@/store/chat';
13
- import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
13
+ import { topicSelectors } from '@/store/chat/selectors';
14
14
  import { useToolStore } from '@/store/tool';
15
15
  import { toolSelectors } from '@/store/tool/selectors';
16
16
  import { useUserStore } from '@/store/user';
@@ -18,13 +18,15 @@ import { modelProviderSelectors } from '@/store/user/selectors';
18
18
 
19
19
  import TokenProgress from './TokenProgress';
20
20
 
21
- const Token = memo(() => {
21
+ interface TokenTagProps {
22
+ total: string;
23
+ }
24
+ const Token = memo<TokenTagProps>(({ total: messageString }) => {
22
25
  const { t } = useTranslation(['chat', 'components']);
23
26
  const theme = useTheme();
24
27
 
25
- const [input, messageString, historySummary] = useChatStore((s) => [
28
+ const [input, historySummary] = useChatStore((s) => [
26
29
  s.inputMessage,
27
- chatSelectors.chatsMessageString(s),
28
30
  topicSelectors.currentActiveTopicSummary(s)?.content || '',
29
31
  ]);
30
32
 
@@ -1,18 +1,37 @@
1
1
  import dynamic from 'next/dynamic';
2
- import { memo } from 'react';
2
+ import { PropsWithChildren, memo } from 'react';
3
3
 
4
4
  import { useAgentStore } from '@/store/agent';
5
- import { agentSelectors } from '@/store/agent/slices/chat';
5
+ import { agentSelectors } from '@/store/agent/selectors';
6
+ import { useChatStore } from '@/store/chat';
7
+ import { chatSelectors, threadSelectors } from '@/store/chat/selectors';
6
8
  import { useUserStore } from '@/store/user';
7
9
  import { modelProviderSelectors } from '@/store/user/selectors';
8
10
 
9
11
  const LargeTokenContent = dynamic(() => import('./TokenTag'), { ssr: false });
10
12
 
11
- const Token = memo(() => {
13
+ const Token = memo<PropsWithChildren>(({ children }) => {
12
14
  const model = useAgentStore(agentSelectors.currentAgentModel);
13
15
  const showTag = useUserStore(modelProviderSelectors.isModelHasMaxToken(model));
14
16
 
15
- return showTag && <LargeTokenContent />;
17
+ return showTag && children;
16
18
  });
19
+ export const MainToken = memo(() => {
20
+ const total = useChatStore(chatSelectors.mainAIChatsMessageString);
17
21
 
18
- export default Token;
22
+ return (
23
+ <Token>
24
+ <LargeTokenContent total={total} />
25
+ </Token>
26
+ );
27
+ });
28
+
29
+ export const PortalToken = memo(() => {
30
+ const total = useChatStore(threadSelectors.portalDisplayChatsString);
31
+
32
+ return (
33
+ <Token>
34
+ <LargeTokenContent total={total} />
35
+ </Token>
36
+ );
37
+ });
@@ -4,7 +4,7 @@ import History from './History';
4
4
  import Knowledge from './Knowledge';
5
5
  import ModelSwitch from './ModelSwitch';
6
6
  import Temperature from './Temperature';
7
- import Token from './Token';
7
+ import { MainToken, PortalToken } from './Token';
8
8
  import Tools from './Tools';
9
9
  import Upload from './Upload';
10
10
 
@@ -13,10 +13,11 @@ export const actionMap = {
13
13
  fileUpload: Upload,
14
14
  history: History,
15
15
  knowledgeBase: Knowledge,
16
+ mainToken: MainToken,
16
17
  model: ModelSwitch,
18
+ portalToken: PortalToken,
17
19
  stt: STT,
18
20
  temperature: Temperature,
19
- token: Token,
20
21
  tools: Tools,
21
22
  } as const;
22
23
 
@@ -8,23 +8,31 @@ import { CHAT_TEXTAREA_HEIGHT, CHAT_TEXTAREA_MAX_HEIGHT } from '@/const/layoutTo
8
8
 
9
9
  import { ActionKeys } from '../ActionBar/config';
10
10
  import LocalFiles from './FilePreview';
11
- import Footer from './Footer';
12
11
  import Head from './Header';
13
12
 
13
+ export type FooterRender = (params: {
14
+ expand: boolean;
15
+ onExpandChange: (expand: boolean) => void;
16
+ }) => ReactNode;
17
+
14
18
  interface DesktopChatInputProps {
15
- footer?: {
16
- saveTopic?: boolean;
17
- shortcutHint?: boolean;
18
- };
19
19
  inputHeight: number;
20
20
  leftActions: ActionKeys[];
21
21
  onInputHeightChange?: (height: number) => void;
22
+ renderFooter: FooterRender;
22
23
  renderTextArea: (onSend: () => void) => ReactNode;
23
24
  rightActions: ActionKeys[];
24
25
  }
25
26
 
26
27
  const DesktopChatInput = memo<DesktopChatInputProps>(
27
- ({ leftActions, rightActions, footer, renderTextArea, inputHeight, onInputHeightChange }) => {
28
+ ({
29
+ leftActions,
30
+ rightActions,
31
+ renderTextArea,
32
+ inputHeight,
33
+ onInputHeightChange,
34
+ renderFooter,
35
+ }) => {
28
36
  const [expand, setExpand] = useState<boolean>(false);
29
37
 
30
38
  const onSend = useCallback(() => {
@@ -63,7 +71,7 @@ const DesktopChatInput = memo<DesktopChatInputProps>(
63
71
  setExpand={setExpand}
64
72
  />
65
73
  {renderTextArea(onSend)}
66
- <Footer expand={expand} setExpand={setExpand} {...footer} />
74
+ {renderFooter({ expand, onExpandChange: setExpand })}
67
75
  </Flexbox>
68
76
  </DraggablePanel>
69
77
  </>
@@ -16,17 +16,17 @@ import Files from './Files';
16
16
  import InputArea from './InputArea';
17
17
  import SendButton from './Send';
18
18
 
19
- const defaultLeftActions = [
19
+ const defaultLeftActions: ActionKeys[] = [
20
20
  'model',
21
21
  'fileUpload',
22
22
  'knowledgeBase',
23
23
  'temperature',
24
24
  'history',
25
25
  'tools',
26
- 'token',
27
- ] as ActionKeys[];
26
+ 'mainToken',
27
+ ];
28
28
 
29
- const defaultRightActions = ['clear'] as ActionKeys[];
29
+ const defaultRightActions: ActionKeys[] = ['clear'];
30
30
 
31
31
  const MobileChatInput = memo(() => {
32
32
  const theme = useTheme();
@@ -1,17 +1,36 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
3
+ import { memo, useContext, useMemo } from 'react';
3
4
 
5
+ import { useChatStore } from '@/store/chat';
6
+ import { threadSelectors } from '@/store/chat/selectors';
7
+
8
+ import { InPortalThreadContext } from '../components/ChatItem/InPortalThreadContext';
4
9
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
10
  import { RenderAction } from '../types';
6
11
  import { ErrorActionsBar } from './Error';
7
12
  import { useCustomActions } from './customAction';
8
13
 
9
- export const AssistantActionsBar: RenderAction = memo(({ id, onActionClick, error, tools }) => {
10
- const { regenerate, edit, delAndRegenerate, copy, divider, del } = useChatListActionsBar();
14
+ export const AssistantActionsBar: RenderAction = memo(({ onActionClick, error, tools, id }) => {
15
+ const [isThreadMode, hasThread] = useChatStore((s) => [
16
+ !!s.activeThreadId,
17
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
18
+ ]);
19
+
20
+ const { regenerate, edit, delAndRegenerate, copy, divider, del, branching } =
21
+ useChatListActionsBar({ hasThread });
22
+
11
23
  const { translate, tts } = useCustomActions();
12
24
  const hasTools = !!tools;
13
25
 
14
- if (id === 'default') return;
26
+ const inPortalThread = useContext(InPortalThreadContext);
27
+ const inThread = isThreadMode || inPortalThread;
28
+
29
+ const items = useMemo(() => {
30
+ if (hasTools) return [delAndRegenerate, copy];
31
+
32
+ return [edit, copy, inThread ? null : branching].filter(Boolean) as ActionIconGroupItems[];
33
+ }, [inThread, hasTools]);
15
34
 
16
35
  if (error) return <ErrorActionsBar onActionClick={onActionClick} />;
17
36
 
@@ -28,7 +47,7 @@ export const AssistantActionsBar: RenderAction = memo(({ id, onActionClick, erro
28
47
  delAndRegenerate,
29
48
  del,
30
49
  ]}
31
- items={[hasTools ? delAndRegenerate : edit, copy]}
50
+ items={items}
32
51
  onActionClick={onActionClick}
33
52
  type="ghost"
34
53
  />
@@ -1,18 +1,35 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
3
+ import { memo, useContext, useMemo } from 'react';
3
4
 
5
+ import { useChatStore } from '@/store/chat';
6
+ import { threadSelectors } from '@/store/chat/slices/thread/selectors';
7
+
8
+ import { InPortalThreadContext } from '../components/ChatItem/InPortalThreadContext';
4
9
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
10
  import { RenderAction } from '../types';
6
11
  import { useCustomActions } from './customAction';
7
12
 
8
- export const UserActionsBar: RenderAction = memo(({ onActionClick }) => {
9
- const { regenerate, edit, copy, divider, del } = useChatListActionsBar();
13
+ export const UserActionsBar: RenderAction = memo(({ onActionClick, id }) => {
14
+ const [isThreadMode, hasThread] = useChatStore((s) => [
15
+ !!s.activeThreadId,
16
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
17
+ ]);
18
+ const { regenerate, edit, copy, divider, del, branching } = useChatListActionsBar({ hasThread });
10
19
  const { translate, tts } = useCustomActions();
11
20
 
21
+ const inPortalThread = useContext(InPortalThreadContext);
22
+ const inThread = isThreadMode || inPortalThread;
23
+
24
+ const items = useMemo(
25
+ () => [regenerate, edit, inThread ? null : branching].filter(Boolean) as ActionIconGroupItems[],
26
+ [inThread],
27
+ );
28
+
12
29
  return (
13
30
  <ActionIconGroup
14
31
  dropdownMenu={[edit, copy, divider, tts, translate, divider, regenerate, del]}
15
- items={[regenerate, edit]}
32
+ items={items}
16
33
  onActionClick={onActionClick}
17
34
  type="ghost"
18
35
  />
@@ -1,11 +1,6 @@
1
- import { App } from 'antd';
2
- import { useCallback } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import { useChatStore } from '@/store/chat';
6
1
  import { MessageRoleType } from '@/types/message';
7
2
 
8
- import { OnActionsClick, RenderAction } from '../types';
3
+ import { RenderAction } from '../types';
9
4
  import { AssistantActionsBar } from './Assistant';
10
5
  import { DefaultActionsBar } from './Fallback';
11
6
  import { ToolActionsBar } from './Tool';
@@ -17,63 +12,3 @@ export const renderActions: Record<MessageRoleType, RenderAction> = {
17
12
  tool: ToolActionsBar,
18
13
  user: UserActionsBar,
19
14
  };
20
-
21
- export const useActionsClick = (): OnActionsClick => {
22
- const { t } = useTranslation('common');
23
- const [
24
- deleteMessage,
25
- regenerateMessage,
26
- translateMessage,
27
- ttsMessage,
28
- delAndRegenerateMessage,
29
- copyMessage,
30
- ] = useChatStore((s) => [
31
- s.deleteMessage,
32
- s.regenerateMessage,
33
- s.translateMessage,
34
- s.ttsMessage,
35
- s.delAndRegenerateMessage,
36
- s.copyMessage,
37
- ]);
38
- const { message } = App.useApp();
39
-
40
- return useCallback<OnActionsClick>(async (action, { id, content, error }) => {
41
- switch (action.key) {
42
- case 'copy': {
43
- await copyMessage(id, content);
44
- message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
45
- break;
46
- }
47
-
48
- case 'del': {
49
- deleteMessage(id);
50
- break;
51
- }
52
-
53
- case 'regenerate': {
54
- regenerateMessage(id);
55
- // if this message is an error message, we need to delete it
56
- if (error) deleteMessage(id);
57
- break;
58
- }
59
-
60
- case 'delAndRegenerate': {
61
- delAndRegenerateMessage(id);
62
- break;
63
- }
64
-
65
- case 'tts': {
66
- ttsMessage(id);
67
- break;
68
- }
69
- }
70
-
71
- if (action.keyPath.at(-1) === 'translate') {
72
- // click the menu item with translate item, the result is:
73
- // key: 'en-US'
74
- // keyPath: ['en-US','translate']
75
- const lang = action.keyPath[0];
76
- translateMessage(id, lang);
77
- }
78
- }, []);
79
- };
@@ -34,6 +34,7 @@ export interface InspectorProps {
34
34
  loading?: boolean;
35
35
  payload?: ChatPluginPayload;
36
36
  setShow?: (showRender: boolean) => void;
37
+ showPortal?: boolean;
37
38
  showRender?: boolean;
38
39
  }
39
40
 
@@ -47,6 +48,7 @@ const Inspector = memo<InspectorProps>(
47
48
  content,
48
49
  identifier = 'unknown',
49
50
  id,
51
+ showPortal = true,
50
52
  }) => {
51
53
  const { t } = useTranslation(['plugin', 'portal']);
52
54
  const { styles } = useStyles();
@@ -111,7 +113,7 @@ const Inspector = memo<InspectorProps>(
111
113
  </Flexbox>
112
114
 
113
115
  <Flexbox horizontal>
114
- {!isMobile && showRightAction && (
116
+ {!isMobile && showRightAction && showPortal && (
115
117
  <ActionIcon
116
118
  icon={BetweenVerticalStart}
117
119
  onClick={() => {
@@ -1,8 +1,8 @@
1
1
  import { Icon } from '@lobehub/ui';
2
- import { ConfigProvider, Empty, Skeleton } from 'antd';
2
+ import { ConfigProvider, Empty } from 'antd';
3
3
  import { useTheme } from 'antd-style';
4
4
  import { LucideSquareArrowLeft, LucideSquareArrowRight } from 'lucide-react';
5
- import { Suspense, memo, useContext, useState } from 'react';
5
+ import { memo, useContext, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Center, Flexbox } from 'react-layout-kit';
8
8
 
@@ -11,10 +11,14 @@ import { useChatStore } from '@/store/chat';
11
11
  import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
12
12
  import { ChatMessage } from '@/types/message';
13
13
 
14
- import Arguments from '../components/Arguments';
14
+ import Arguments from '../../components/Arguments';
15
15
  import Inspector from './Inspector';
16
16
 
17
- const Message = memo<ChatMessage>(({ id, content, pluginState, plugin }) => {
17
+ const Tool = memo<
18
+ ChatMessage & {
19
+ showPortal?: boolean;
20
+ }
21
+ >(({ id, content, pluginState, plugin, showPortal }) => {
18
22
  const [loading, isMessageToolUIOpen] = useChatStore((s) => [
19
23
  chatSelectors.isPluginApiInvoking(id)(s),
20
24
  chatPortalSelectors.isPluginUIOpen(id)(s),
@@ -35,6 +39,7 @@ const Message = memo<ChatMessage>(({ id, content, pluginState, plugin }) => {
35
39
  loading={loading}
36
40
  payload={plugin}
37
41
  setShow={setShow}
42
+ showPortal={showPortal}
38
43
  showRender={showRender}
39
44
  />
40
45
  {isMessageToolUIOpen ? (
@@ -69,10 +74,4 @@ const Message = memo<ChatMessage>(({ id, content, pluginState, plugin }) => {
69
74
  );
70
75
  });
71
76
 
72
- export const ToolMessage = memo<ChatMessage>((props) => (
73
- <Suspense
74
- fallback={<Skeleton.Button active style={{ height: 46, minWidth: 200, width: '100%' }} />}
75
- >
76
- <Message {...props} />
77
- </Suspense>
78
- ));
77
+ export default Tool;
@@ -13,8 +13,8 @@ import { chatSelectors } from '@/store/chat/selectors';
13
13
  import { pluginHelpers, useToolStore } from '@/store/tool';
14
14
  import { toolSelectors } from '@/store/tool/selectors';
15
15
 
16
- import { ToolMessage } from '../../Tool';
17
16
  import Arguments from '../../components/Arguments';
17
+ import ToolMessage from './Tool';
18
18
  import { useStyles } from './style';
19
19
 
20
20
  export interface InspectorProps {
@@ -24,13 +24,15 @@ export interface InspectorProps {
24
24
  identifier: string;
25
25
  index: number;
26
26
  messageId: string;
27
+ showPortal?: boolean;
27
28
  style?: CSSProperties;
28
29
  }
29
30
 
30
31
  const CallItem = memo<InspectorProps>(
31
- ({ arguments: requestArgs, apiName, messageId, id, index, identifier, style }) => {
32
+ ({ arguments: requestArgs, apiName, messageId, id, index, identifier, style, showPortal }) => {
32
33
  const { t } = useTranslation('plugin');
33
34
  const { styles } = useStyles();
35
+
34
36
  const [open, setOpen] = useState(false);
35
37
  const loading = useChatStore(chatSelectors.isToolCallStreaming(messageId, index));
36
38
  const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(id));
@@ -42,7 +44,7 @@ const CallItem = memo<InspectorProps>(
42
44
 
43
45
  // when tool calling stop streaming, we should show the tool message
44
46
  return !loading && toolMessage ? (
45
- <ToolMessage {...toolMessage} />
47
+ <ToolMessage {...toolMessage} showPortal={showPortal} />
46
48
  ) : (
47
49
  <Flexbox gap={8} style={style}>
48
50
  <Flexbox