@lobehub/lobehub 2.0.0-next.65 → 2.0.0-next.66

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 (58) hide show
  1. package/.github/workflows/claude-translator.yml +1 -0
  2. package/CHANGELOG.md +25 -0
  3. package/changelog/v1.json +9 -0
  4. package/locales/ar/chat.json +3 -0
  5. package/locales/bg-BG/chat.json +3 -0
  6. package/locales/de-DE/chat.json +3 -0
  7. package/locales/en-US/chat.json +3 -0
  8. package/locales/es-ES/chat.json +3 -0
  9. package/locales/fa-IR/chat.json +3 -0
  10. package/locales/fr-FR/chat.json +3 -0
  11. package/locales/it-IT/chat.json +3 -0
  12. package/locales/ja-JP/chat.json +3 -0
  13. package/locales/ko-KR/chat.json +3 -0
  14. package/locales/nl-NL/chat.json +3 -0
  15. package/locales/pl-PL/chat.json +3 -0
  16. package/locales/pt-BR/chat.json +3 -0
  17. package/locales/ru-RU/chat.json +3 -0
  18. package/locales/tr-TR/chat.json +3 -0
  19. package/locales/vi-VN/chat.json +3 -0
  20. package/locales/zh-CN/chat.json +3 -0
  21. package/locales/zh-TW/chat.json +3 -0
  22. package/package.json +5 -5
  23. package/packages/conversation-flow/src/__tests__/fixtures/index.ts +4 -8
  24. package/packages/conversation-flow/src/__tests__/fixtures/inputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +2 -1
  25. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/index.ts +8 -0
  26. package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +2 -4
  27. package/packages/conversation-flow/src/__tests__/fixtures/outputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +8 -8
  28. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/index.ts +8 -0
  29. package/packages/conversation-flow/src/__tests__/parse.test.ts +6 -6
  30. package/packages/conversation-flow/src/parse.ts +45 -1
  31. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +64 -0
  32. package/packages/database/package.json +2 -2
  33. package/packages/obervability-otel/package.json +1 -1
  34. package/packages/types/src/message/common/metadata.ts +8 -1
  35. package/packages/types/src/message/ui/chat.ts +1 -0
  36. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +1 -1
  37. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +1 -1
  38. package/src/app/market-auth-callback/layout.tsx +27 -3
  39. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
  40. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +15 -1
  41. package/src/features/Conversation/Messages/Assistant/CollapsedMessage.tsx +37 -0
  42. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +16 -9
  43. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +28 -6
  44. package/src/features/Conversation/Messages/Group/CollapsedMessage.tsx +37 -0
  45. package/src/features/Conversation/Messages/Group/{GroupChildren.tsx → Group.tsx} +18 -4
  46. package/src/features/Conversation/Messages/Group/index.tsx +4 -6
  47. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
  48. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
  49. package/src/libs/mcp/__tests__/index.test.ts +6 -6
  50. package/src/locales/default/chat.ts +3 -0
  51. package/src/store/chat/slices/message/actions/publicApi.ts +17 -0
  52. package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -1
  53. package/src/store/chat/slices/message/selectors/messageState.ts +7 -0
  54. package/src/store/chat/slices/translate/action.test.ts +26 -32
  55. package/src/store/chat/slices/translate/action.ts +3 -3
  56. /package/packages/conversation-flow/src/__tests__/fixtures/inputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  57. /package/packages/conversation-flow/src/__tests__/fixtures/outputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  58. /package/src/features/Conversation/Messages/Group/{GroupContext.tsx → GroupContext.ts} +0 -0
@@ -6,6 +6,8 @@ import {
6
6
  DownloadIcon,
7
7
  Edit,
8
8
  LanguagesIcon,
9
+ ListChevronsDownUp,
10
+ ListChevronsUpDown,
9
11
  ListRestart,
10
12
  Play,
11
13
  RotateCcw,
@@ -27,12 +29,14 @@ const translateStyle = css`
27
29
 
28
30
  interface ChatListActionsBar {
29
31
  branching: ActionIconGroupItemType;
32
+ collapse: ActionIconGroupItemType;
30
33
  continueGeneration: ActionIconGroupItemType;
31
34
  copy: ActionIconGroupItemType;
32
35
  del: ActionIconGroupItemType;
33
36
  delAndRegenerate: ActionIconGroupItemType;
34
37
  divider: { type: 'divider' };
35
38
  edit: ActionIconGroupItemType;
39
+ expand: ActionIconGroupItemType;
36
40
  export: ActionIconGroupItemType;
37
41
  regenerate: ActionIconGroupItemType;
38
42
  share: ActionIconGroupItemType;
@@ -58,6 +62,11 @@ export const useChatListActionsBar = ({
58
62
  key: 'branching',
59
63
  label: t('branching'),
60
64
  },
65
+ collapse: {
66
+ icon: ListChevronsDownUp,
67
+ key: 'collapse',
68
+ label: t('messageAction.collapse', { ns: 'chat' }),
69
+ },
61
70
  continueGeneration: {
62
71
  disabled: isContinuing,
63
72
  icon: ArrowDownFromLine,
@@ -93,6 +102,11 @@ export const useChatListActionsBar = ({
93
102
  key: 'edit',
94
103
  label: t('edit'),
95
104
  },
105
+ expand: {
106
+ icon: ListChevronsUpDown,
107
+ key: 'expand',
108
+ label: t('messageAction.expand', { ns: 'chat' }),
109
+ },
96
110
  export: {
97
111
  icon: DownloadIcon,
98
112
  key: 'export',
@@ -106,7 +106,7 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
106
106
  // 初始化 OIDC 客户端(仅在客户端)
107
107
  useEffect(() => {
108
108
  if (typeof window !== 'undefined') {
109
- const baseUrl = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'http://127.0.0.1:8787';
109
+ const baseUrl = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
110
110
  const desktopRedirectUri = new URL(MARKET_OIDC_ENDPOINTS.desktopCallback, baseUrl).toString();
111
111
 
112
112
  // 桌面端使用 Market 手动维护的 Web 回调,Web 端使用当前域名
@@ -21,16 +21,16 @@ describe('MCPClient', () => {
21
21
  await mcpClient.initialize();
22
22
  // Add a small delay to allow the server process to fully start (optional, but can help)
23
23
  await new Promise((resolve) => setTimeout(resolve, 100));
24
- });
24
+ }, 30000);
25
25
 
26
26
  afterEach(async () => {
27
27
  // Assume SDK client/transport handles process termination gracefully
28
28
  // If processes leak, more explicit cleanup might be needed here
29
- });
29
+ }, 30000);
30
30
 
31
31
  it('should create and initialize an instance with stdio transport', () => {
32
32
  expect(mcpClient).toBeInstanceOf(MCPClient);
33
- });
33
+ }, 30000);
34
34
 
35
35
  it('should list tools via stdio', async () => {
36
36
  const result = await mcpClient.listTools();
@@ -40,7 +40,7 @@ describe('MCPClient', () => {
40
40
 
41
41
  // Expect the tools defined in mock-sdk-server.ts
42
42
  expect(result).toMatchSnapshot();
43
- });
43
+ }, 30000);
44
44
 
45
45
  it('should call the "echo" tool via stdio', async () => {
46
46
  const toolName = 'echo';
@@ -52,7 +52,7 @@ describe('MCPClient', () => {
52
52
 
53
53
  const result = await mcpClient.callTool(toolName, toolArgs);
54
54
  expect(result).toEqual(expectedResult);
55
- });
55
+ }, 30000);
56
56
 
57
57
  it('should call the "add" tool via stdio', async () => {
58
58
  const toolName = 'add';
@@ -62,7 +62,7 @@ describe('MCPClient', () => {
62
62
  expect(result).toEqual({
63
63
  content: [{ type: 'text', text: 'The sum is: 12' }],
64
64
  });
65
- });
65
+ }, 30000);
66
66
  });
67
67
 
68
68
  // Error Handling tests remain the same...
@@ -18,6 +18,7 @@ export default {
18
18
  availableAgents: '可用助手',
19
19
  backToBottom: '跳转至当前',
20
20
  chatList: {
21
+ expandMessage: '展开消息',
21
22
  longMessageDetail: '查看详情',
22
23
  },
23
24
  clearCurrentMessages: '清空当前会话消息',
@@ -188,9 +189,11 @@ export default {
188
189
  },
189
190
 
190
191
  messageAction: {
192
+ collapse: '收起消息',
191
193
  continueGeneration: '继续生成',
192
194
  delAndRegenerate: '删除并重新生成',
193
195
  deleteDisabledByThreads: '存在子话题,不能删除',
196
+ expand: '展开消息',
194
197
  regenerate: '重新生成',
195
198
  },
196
199
 
@@ -43,6 +43,10 @@ export interface MessagePublicApiAction {
43
43
  updateMessageInput: (message: string) => void;
44
44
  modifyMessageContent: (id: string, content: string) => Promise<void>;
45
45
  toggleMessageEditing: (id: string, editing: boolean) => void;
46
+ /**
47
+ * Toggle message collapsed state
48
+ */
49
+ toggleMessageCollapsed: (id: string, collapsed?: boolean) => Promise<void>;
46
50
 
47
51
  // ===== Others ===== //
48
52
  copyMessage: (id: string, content: string) => Promise<void>;
@@ -241,4 +245,17 @@ export const messagePublicApi: StateCreator<
241
245
 
242
246
  await get().optimisticUpdateMessageContent(id, content);
243
247
  },
248
+
249
+ toggleMessageCollapsed: async (id, collapsed) => {
250
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
251
+ if (!message) return;
252
+
253
+ // 如果没有传入 collapsed,则取反当前状态
254
+ const nextCollapsed = collapsed ?? !message.metadata?.collapsed;
255
+
256
+ // 直接调用现有的 optimisticUpdateMessageMetadata
257
+ await get().optimisticUpdateMessageMetadata(id, {
258
+ collapsed: nextCollapsed,
259
+ });
260
+ },
244
261
  });
@@ -85,7 +85,7 @@ const activeDisplayMessages = (s: ChatStoreState): UIChatMessage[] => {
85
85
  /**
86
86
  * Get display message by ID (searches in messagesMap including assistantGroup children)
87
87
  */
88
- const getDisplayMessageById = (id: string) => (s: ChatStoreState) =>
88
+ export const getDisplayMessageById = (id: string) => (s: ChatStoreState) =>
89
89
  chatHelpers.getMessageById(activeDisplayMessages(s), id);
90
90
 
91
91
  const lastDisplayMessageId = (s: ChatStoreState) => {
@@ -1,6 +1,7 @@
1
1
  import type { ChatStoreState } from '../../../initialState';
2
2
  import { mainDisplayChatIDs } from './chat';
3
3
  import { getDbMessageByToolCallId } from './dbMessage';
4
+ import { getDisplayMessageById } from './displayMessage';
4
5
 
5
6
  const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
6
7
  const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
@@ -13,6 +14,11 @@ const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
13
14
  const isMessageInChatReasoning = (id: string) => (s: ChatStoreState) =>
14
15
  s.reasoningLoadingIds.includes(id);
15
16
 
17
+ const isMessageCollapsed = (id: string) => (s: ChatStoreState) => {
18
+ const message = getDisplayMessageById(id)(s);
19
+ return message?.metadata?.collapsed ?? false;
20
+ };
21
+
16
22
  const isPluginApiInvoking = (id: string) => (s: ChatStoreState) =>
17
23
  s.pluginApiLoadingIds.includes(id);
18
24
 
@@ -71,6 +77,7 @@ export const messageStateSelectors = {
71
77
  isHasMessageLoading,
72
78
  isInRAGFlow,
73
79
  isInToolsCalling,
80
+ isMessageCollapsed,
74
81
  isMessageContinuing,
75
82
  isMessageEditing,
76
83
  isMessageGenerating,
@@ -1,5 +1,3 @@
1
- import { chainLangDetect } from '@lobechat/prompts';
2
- import { chainTranslate } from '@lobechat/prompts';
3
1
  import { act, renderHook } from '@testing-library/react';
4
2
  import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
3
 
@@ -9,7 +7,7 @@ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
9
7
 
10
8
  import { useChatStore } from '../../store';
11
9
 
12
- // Mock messageService chatService
10
+ // Mock messageService and chatService
13
11
  vi.mock('@/services/message', () => ({
14
12
  messageService: {
15
13
  updateMessageTTS: vi.fn(),
@@ -24,27 +22,20 @@ vi.mock('@/services/chat', () => ({
24
22
  },
25
23
  }));
26
24
 
27
- vi.mock('@/chains/langDetect', () => ({
28
- chainLangDetect: vi.fn(),
29
- }));
30
-
31
- vi.mock('@/chains/translate', () => ({
32
- chainTranslate: vi.fn(),
25
+ vi.mock('@/store/user', () => ({
26
+ useUserStore: {
27
+ getState: vi.fn(() => ({})),
28
+ },
33
29
  }));
34
30
 
35
- // Mock supportLocales
36
- vi.mock('@/locales/options', () => ({
37
- supportLocales: ['en-US', 'zh-CN'],
31
+ vi.mock('@/store/user/selectors', () => ({
32
+ systemAgentSelectors: {
33
+ translation: vi.fn(() => ({})),
34
+ },
38
35
  }));
39
36
 
40
37
  beforeEach(() => {
41
38
  vi.clearAllMocks();
42
- useChatStore.setState(
43
- {
44
- // ... 初始状态
45
- },
46
- false,
47
- );
48
39
  });
49
40
 
50
41
  afterEach(() => {
@@ -53,26 +44,26 @@ afterEach(() => {
53
44
 
54
45
  describe('ChatEnhanceAction', () => {
55
46
  describe('translateMessage', () => {
56
- it('should translate a message to the target language and refresh messages', async () => {
57
- const { result } = renderHook(() => useChatStore());
47
+ it('should translate a message to the target language', async () => {
58
48
  const messageId = 'message-id';
59
49
  const targetLang = 'zh-CN';
60
50
  const messageContent = 'Hello World';
61
51
  const detectedLang = 'en-US';
52
+ const translatedText = '你好世界';
62
53
 
54
+ // Setup initial state
63
55
  act(() => {
64
56
  useChatStore.setState({
65
57
  activeId: 'session',
66
- messagesMap: {
58
+ dbMessagesMap: {
67
59
  [messageMapKey('session')]: [
68
60
  {
69
61
  id: messageId,
70
62
  content: messageContent,
71
63
  createdAt: Date.now(),
72
64
  updatedAt: Date.now(),
73
- role: 'user',
74
- sessionId: 'test',
75
- topicId: 'test',
65
+ role: 'assistant',
66
+ sessionId: 'session',
76
67
  meta: {},
77
68
  },
78
69
  ],
@@ -80,21 +71,24 @@ describe('ChatEnhanceAction', () => {
80
71
  });
81
72
  });
82
73
 
83
- (chatService.fetchPresetTaskResult as Mock).mockImplementation(({ params }) => {
84
- if (params === chainLangDetect(messageContent)) {
85
- return Promise.resolve(detectedLang);
86
- }
87
- if (params === chainTranslate(messageContent, targetLang)) {
88
- return Promise.resolve('Hola Mundo');
89
- }
90
- return Promise.resolve(undefined);
74
+ // First call for language detection
75
+ (chatService.fetchPresetTaskResult as Mock).mockImplementationOnce(async ({ onFinish }) => {
76
+ if (onFinish) await onFinish(detectedLang);
91
77
  });
92
78
 
79
+ // Second call for translation
80
+ (chatService.fetchPresetTaskResult as Mock).mockImplementationOnce(async ({ onFinish }) => {
81
+ if (onFinish) await onFinish(translatedText);
82
+ });
83
+
84
+ const { result } = renderHook(() => useChatStore());
85
+
93
86
  await act(async () => {
94
87
  await result.current.translateMessage(messageId, targetLang);
95
88
  });
96
89
 
97
90
  expect(messageService.updateMessageTranslate).toHaveBeenCalled();
91
+ expect(chatService.fetchPresetTaskResult).toHaveBeenCalledTimes(2);
98
92
  });
99
93
  });
100
94
 
@@ -1,16 +1,16 @@
1
1
  import { chainLangDetect, chainTranslate } from '@lobechat/prompts';
2
2
  import { ChatTranslate, TraceNameMap, TracePayload } from '@lobechat/types';
3
+ import { merge } from '@lobechat/utils';
3
4
  import { produce } from 'immer';
4
5
  import { StateCreator } from 'zustand/vanilla';
5
6
 
6
7
  import { supportLocales } from '@/locales/resources';
7
8
  import { chatService } from '@/services/chat';
8
9
  import { messageService } from '@/services/message';
9
- import { chatSelectors } from '@/store/chat/selectors';
10
+ import { dbMessageSelectors } from '@/store/chat/selectors';
10
11
  import { ChatStore } from '@/store/chat/store';
11
12
  import { useUserStore } from '@/store/user';
12
13
  import { systemAgentSelectors } from '@/store/user/selectors';
13
- import { merge } from '@/utils/merge';
14
14
  import { setNamespace } from '@/utils/storeDebug';
15
15
 
16
16
  const n = setNamespace('enhance');
@@ -43,7 +43,7 @@ export const chatTranslate: StateCreator<
43
43
  translateMessage: async (id, targetLang) => {
44
44
  const { internal_toggleChatLoading, updateMessageTranslate, internal_dispatchMessage } = get();
45
45
 
46
- const message = chatSelectors.getMessageById(id)(get());
46
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
47
47
  if (!message) return;
48
48
 
49
49
  // Get current agent for translation