@lobehub/lobehub 2.0.0-next.38 → 2.0.0-next.39

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 (103) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
  3. package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
  4. package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
  5. package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
  6. package/changelog/v1.json +5 -0
  7. package/locales/ar/auth.json +45 -1
  8. package/locales/bg-BG/auth.json +45 -1
  9. package/locales/de-DE/auth.json +45 -1
  10. package/locales/en-US/auth.json +45 -1
  11. package/locales/es-ES/auth.json +45 -1
  12. package/locales/fa-IR/auth.json +45 -1
  13. package/locales/fr-FR/auth.json +45 -1
  14. package/locales/it-IT/auth.json +45 -1
  15. package/locales/ja-JP/auth.json +45 -1
  16. package/locales/ko-KR/auth.json +45 -1
  17. package/locales/nl-NL/auth.json +45 -1
  18. package/locales/pl-PL/auth.json +45 -1
  19. package/locales/pt-BR/auth.json +45 -1
  20. package/locales/ru-RU/auth.json +45 -1
  21. package/locales/tr-TR/auth.json +45 -1
  22. package/locales/vi-VN/auth.json +45 -1
  23. package/locales/zh-CN/auth.json +45 -1
  24. package/locales/zh-TW/auth.json +45 -1
  25. package/package.json +1 -1
  26. package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
  27. package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
  28. package/packages/obervability-otel/package.json +3 -1
  29. package/packages/obervability-otel/src/api.ts +2 -0
  30. package/packages/obervability-otel/src/trpc/convention.ts +16 -0
  31. package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
  32. package/packages/obervability-otel/src/trpc/index.ts +62 -0
  33. package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
  34. package/packages/types/src/usage/usageRecord.ts +54 -0
  35. package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
  36. package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
  37. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
  38. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
  39. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
  40. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
  41. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
  42. package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
  43. package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
  44. package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
  45. package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
  46. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
  47. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
  48. package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
  49. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
  50. package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
  51. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
  52. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
  53. package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
  54. package/src/features/Conversation/Messages/Group/index.tsx +7 -2
  55. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
  56. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
  57. package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
  58. package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
  59. package/src/features/PluginsUI/Render/index.tsx +17 -0
  60. package/src/libs/mcp/client.ts +3 -2
  61. package/src/libs/mcp/types.ts +71 -0
  62. package/src/libs/trpc/lambda/index.ts +5 -2
  63. package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
  64. package/src/locales/default/auth.ts +44 -0
  65. package/src/locales/default/chat.ts +1 -0
  66. package/src/server/routers/desktop/mcp.ts +1 -3
  67. package/src/server/routers/lambda/index.ts +2 -0
  68. package/src/server/routers/lambda/usage.ts +36 -0
  69. package/src/server/routers/tools/mcp.ts +1 -3
  70. package/src/server/services/mcp/index.test.ts +28 -15
  71. package/src/server/services/mcp/index.ts +29 -18
  72. package/src/server/services/usage/index.test.ts +310 -0
  73. package/src/server/services/usage/index.ts +164 -0
  74. package/src/services/chat/contextEngineering.test.ts +4 -0
  75. package/src/services/mcp.test.ts +7 -1
  76. package/src/services/mcp.ts +13 -12
  77. package/src/services/usage.ts +13 -0
  78. package/src/store/chat/agents/createAgentExecutors.ts +2 -3
  79. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
  80. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
  81. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
  82. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
  83. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
  84. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  85. package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
  86. package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
  87. package/src/store/chat/slices/message/initialState.ts +5 -0
  88. package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
  89. package/src/store/chat/slices/message/selectors/chat.ts +0 -2
  90. package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
  91. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
  92. package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
  93. package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
  94. package/src/store/chat/slices/plugin/action.test.ts +4 -4
  95. package/src/store/chat/slices/plugin/actions/index.ts +39 -0
  96. package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
  97. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
  98. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
  99. package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
  100. package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
  101. package/src/store/chat/store.ts +1 -1
  102. package/src/store/global/initialState.ts +1 -0
  103. package/src/store/chat/slices/plugin/action.ts +0 -539
@@ -77,7 +77,7 @@ export interface StreamingExecutorAction {
77
77
  internal_execAgentRuntime: (params: {
78
78
  messages: UIChatMessage[];
79
79
  parentMessageId: string;
80
- parentMessageType: 'user' | 'assistant';
80
+ parentMessageType: 'user' | 'assistant' | 'tool';
81
81
  inSearchWorkflow?: boolean;
82
82
  /**
83
83
  * the RAG query content, should be embedding and used in the semantic search
@@ -85,6 +85,7 @@ export interface StreamingExecutorAction {
85
85
  ragQuery?: string;
86
86
  threadId?: string;
87
87
  inPortalThread?: boolean;
88
+ skipCreateFirstMessage?: boolean;
88
89
  traceId?: string;
89
90
  ragMetadata?: { ragQueryId: string; fileChunks: MessageSemanticSearchChunk[] };
90
91
  }) => Promise<void>;
@@ -137,7 +138,7 @@ export const streamingExecutor: StateCreator<
137
138
  let output = '';
138
139
  let thinking = '';
139
140
  let thinkingStartAt: number;
140
- let duration: number;
141
+ let duration: number | undefined;
141
142
  // to upload image
142
143
  const uploadTasks: Map<string, Promise<{ id?: string; url?: string }>> = new Map();
143
144
 
@@ -231,7 +232,9 @@ export const streamingExecutor: StateCreator<
231
232
  // update the content after fetch result
232
233
  await optimisticUpdateMessageContent(messageId, content, {
233
234
  toolCalls: parsedToolCalls,
234
- reasoning: !!reasoning ? { ...reasoning, duration } : undefined,
235
+ reasoning: !!reasoning
236
+ ? { ...reasoning, duration: duration && !isNaN(duration) ? duration : undefined }
237
+ : undefined,
235
238
  search: !!grounding?.citations ? grounding : undefined,
236
239
  imageList: finalImages.length > 0 ? finalImages : undefined,
237
240
  metadata: speed ? { ...usage, ...speed } : usage,
@@ -340,6 +343,10 @@ export const streamingExecutor: StateCreator<
340
343
  isFunctionCall = true;
341
344
  const isInChatReasoning = get().reasoningLoadingIds.includes(messageId);
342
345
  if (isInChatReasoning) {
346
+ if (!duration) {
347
+ duration = Date.now() - thinkingStartAt;
348
+ }
349
+
343
350
  internal_toggleChatReasoning(
344
351
  false,
345
352
  messageId,
@@ -456,8 +463,8 @@ export const streamingExecutor: StateCreator<
456
463
  get,
457
464
  messageKey,
458
465
  parentId: params.parentMessageId,
459
- parentMessageType,
460
466
  params,
467
+ skipCreateFirstMessage: params.skipCreateFirstMessage,
461
468
  }),
462
469
  });
463
470
 
@@ -465,7 +472,8 @@ export const streamingExecutor: StateCreator<
465
472
  let state = AgentRuntime.createInitialState({
466
473
  sessionId: activeId,
467
474
  messages,
468
- maxSteps: 20, // Prevent infinite loops
475
+ // Prevent infinite loops
476
+ maxSteps: 400,
469
477
  metadata: {
470
478
  sessionId: activeId,
471
479
  topicId: activeTopicId,
@@ -24,10 +24,10 @@ const mockStore = {
24
24
  internal_triggerLocalFileToolCalling: vi.fn(),
25
25
  optimisticUpdateMessageContent: vi.fn(),
26
26
  optimisticUpdateMessagePluginError: vi.fn(),
27
+ optimisticUpdatePluginArguments: vi.fn(),
28
+ optimisticUpdatePluginState: vi.fn(),
27
29
  set: mockSet,
28
30
  toggleLocalFileLoading: vi.fn(),
29
- updatePluginArguments: vi.fn(),
30
- updatePluginState: vi.fn(),
31
31
  } as unknown as ChatStore;
32
32
 
33
33
  const createStore = () => {
@@ -57,7 +57,7 @@ describe('localFileSlice', () => {
57
57
  await store.internal_triggerLocalFileToolCalling('test-id', mockService);
58
58
 
59
59
  expect(mockStore.toggleLocalFileLoading).toBeCalledWith('test-id', true);
60
- expect(mockStore.updatePluginState).toBeCalledWith('test-id', mockState);
60
+ expect(mockStore.optimisticUpdatePluginState).toBeCalledWith('test-id', mockState);
61
61
  expect(mockStore.optimisticUpdateMessageContent).toBeCalledWith(
62
62
  'test-id',
63
63
  JSON.stringify(mockContent),
@@ -32,10 +32,10 @@ describe('search actions', () => {
32
32
  searchLoading: {},
33
33
  optimisticUpdateMessageContent: vi.fn(),
34
34
  optimisticUpdateMessagePluginError: vi.fn(),
35
- updatePluginArguments: vi.fn(),
36
- updatePluginState: vi.fn(),
35
+ optimisticUpdatePluginArguments: vi.fn(),
36
+ optimisticUpdatePluginState: vi.fn(),
37
37
  optimisticCreateMessage: vi.fn(),
38
- internal_addToolToAssistantMessage: vi.fn(),
38
+ optimisticAddToolToAssistantMessage: vi.fn(),
39
39
  openToolUI: vi.fn(),
40
40
  });
41
41
  });
@@ -238,7 +238,7 @@ describe('search actions', () => {
238
238
  await triggerSearchAgain(messageId, query, { aiSummary: true });
239
239
  });
240
240
 
241
- expect(result.current.updatePluginArguments).toHaveBeenCalledWith(messageId, query);
241
+ expect(result.current.optimisticUpdatePluginArguments).toHaveBeenCalledWith(messageId, query);
242
242
  expect(spy).toHaveBeenCalledWith(messageId, query, true);
243
243
  });
244
244
  });
@@ -285,7 +285,7 @@ describe('search actions', () => {
285
285
  }),
286
286
  );
287
287
 
288
- expect(result.current.internal_addToolToAssistantMessage).toHaveBeenCalledWith(
288
+ expect(result.current.optimisticAddToolToAssistantMessage).toHaveBeenCalledWith(
289
289
  parentId,
290
290
  expect.objectContaining({
291
291
  identifier: 'search',
@@ -305,7 +305,7 @@ describe('search actions', () => {
305
305
  });
306
306
 
307
307
  expect(result.current.optimisticCreateMessage).not.toHaveBeenCalled();
308
- expect(result.current.internal_addToolToAssistantMessage).not.toHaveBeenCalled();
308
+ expect(result.current.optimisticAddToolToAssistantMessage).not.toHaveBeenCalled();
309
309
  });
310
310
  });
311
311
 
@@ -41,7 +41,7 @@ export const codeInterpreterSlice: StateCreator<
41
41
  python: async (id: string, params: CodeInterpreterParams) => {
42
42
  const {
43
43
  toggleInterpreterExecuting,
44
- updatePluginState,
44
+ optimisticUpdatePluginState,
45
45
  optimisticUpdateMessageContent,
46
46
  uploadInterpreterFiles,
47
47
  } = get();
@@ -83,7 +83,7 @@ export const codeInterpreterSlice: StateCreator<
83
83
  await optimisticUpdateMessageContent(id, JSON.stringify(result));
84
84
  }
85
85
  } catch (error) {
86
- updatePluginState(id, { error });
86
+ optimisticUpdatePluginState(id, { error });
87
87
  // 如果调用过程中出现了错误,不要触发 AI 消息
88
88
  return;
89
89
  } finally {
@@ -220,7 +220,7 @@ export const localSystemSlice: StateCreator<
220
220
  reSearchLocalFiles: async (id, params) => {
221
221
  get().toggleLocalFileLoading(id, true);
222
222
 
223
- await get().updatePluginArguments(id, params);
223
+ await get().optimisticUpdatePluginArguments(id, params);
224
224
 
225
225
  return get().searchLocalFiles(id, params);
226
226
  },
@@ -307,7 +307,7 @@ export const localSystemSlice: StateCreator<
307
307
  try {
308
308
  const { state, content } = await callingService();
309
309
  if (state) {
310
- await get().updatePluginState(id, state as any);
310
+ await get().optimisticUpdatePluginState(id, state as any);
311
311
  }
312
312
  await get().optimisticUpdateMessageContent(id, JSON.stringify(content));
313
313
  } catch (error) {
@@ -51,9 +51,9 @@ export const searchSlice: StateCreator<
51
51
  await optimisticUpdateMessageContent(id, content);
52
52
 
53
53
  if (success) {
54
- await get().updatePluginState(id, state);
54
+ await get().optimisticUpdatePluginState(id, state);
55
55
  } else {
56
- await get().internal_updatePluginError(id, error);
56
+ await get().optimisticUpdatePluginError(id, error);
57
57
  }
58
58
  get().toggleSearchLoading(id, false);
59
59
 
@@ -81,7 +81,7 @@ export const searchSlice: StateCreator<
81
81
  const message = chatSelectors.getMessageById(id)(get());
82
82
  if (!message || !message.plugin) return;
83
83
 
84
- const { internal_addToolToAssistantMessage, optimisticCreateMessage, openToolUI } = get();
84
+ const { optimisticAddToolToAssistantMessage, optimisticCreateMessage, openToolUI } = get();
85
85
  // 1. 创建一个新的 tool call message
86
86
  const newToolCallId = `tool_call_${nanoid()}`;
87
87
 
@@ -100,7 +100,7 @@ export const searchSlice: StateCreator<
100
100
  const addToolItem = async () => {
101
101
  if (!message.parentId || !message.plugin) return;
102
102
 
103
- await internal_addToolToAssistantMessage(message.parentId, {
103
+ await optimisticAddToolToAssistantMessage(message.parentId, {
104
104
  id: newToolCallId,
105
105
  ...message.plugin,
106
106
  });
@@ -124,7 +124,7 @@ export const searchSlice: StateCreator<
124
124
  const { content, success, error, state } = await runtime.search(params);
125
125
 
126
126
  if (success) {
127
- await get().updatePluginState(id, state);
127
+ await get().optimisticUpdatePluginState(id, state);
128
128
  } else {
129
129
  if ((error as Error).message === SEARCH_SEARXNG_NOT_CONFIG) {
130
130
  await get().optimisticUpdateMessagePluginError(id, {
@@ -164,7 +164,7 @@ export const searchSlice: StateCreator<
164
164
 
165
165
  triggerSearchAgain: async (id, data, options) => {
166
166
  get().toggleSearchLoading(id, true);
167
- await get().updatePluginArguments(id, data);
167
+ await get().optimisticUpdatePluginArguments(id, data);
168
168
 
169
169
  await get().search(id, data, options?.aiSummary);
170
170
  },
@@ -31,6 +31,7 @@ export interface MessagePublicApiAction {
31
31
  */
32
32
  clearMessage: () => Promise<void>;
33
33
  deleteMessage: (id: string) => Promise<void>;
34
+ deleteDBMessage: (id: string) => Promise<void>;
34
35
  deleteToolMessage: (id: string) => Promise<void>;
35
36
  clearAllMessages: () => Promise<void>;
36
37
 
@@ -135,13 +136,30 @@ export const messagePublicApi: StateCreator<
135
136
  }
136
137
  },
137
138
 
139
+ deleteDBMessage: async (id) => {
140
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
141
+ if (!message) return;
142
+
143
+ let ids = [message.id];
144
+
145
+ get().internal_dispatchMessage({ type: 'deleteMessages', ids });
146
+ const result = await messageService.removeMessages(ids, {
147
+ sessionId: get().activeId,
148
+ topicId: get().activeTopicId,
149
+ });
150
+
151
+ if (result?.success && result.messages) {
152
+ get().replaceMessages(result.messages);
153
+ }
154
+ },
155
+
138
156
  deleteToolMessage: async (id) => {
139
157
  const message = dbMessageSelectors.getDbMessageById(id)(get());
140
158
  if (!message || message.role !== 'tool') return;
141
159
 
142
160
  const removeToolInAssistantMessage = async () => {
143
161
  if (!message.parentId) return;
144
- await get().internal_removeToolToAssistantMessage(message.parentId, message.tool_call_id);
162
+ await get().optimisticRemoveToolFromAssistantMessage(message.parentId, message.tool_call_id);
145
163
  };
146
164
 
147
165
  await Promise.all([
@@ -15,6 +15,10 @@ export interface ChatMessageState {
15
15
  * Derived from session.type, used for caching to avoid repeated lookups
16
16
  */
17
17
  activeSessionType?: 'agent' | 'group';
18
+ /**
19
+ * is the message is continuing generation (used for disable continue button)
20
+ */
21
+ continuingIds: string[];
18
22
  /**
19
23
  * Raw messages from database (flat structure)
20
24
  */
@@ -73,6 +77,7 @@ export interface ChatMessageState {
73
77
  export const initialMessageState: ChatMessageState = {
74
78
  activeId: 'inbox',
75
79
  activeSessionType: undefined,
80
+ continuingIds: [],
76
81
  dbMessagesMap: {},
77
82
  groupAgentMaps: {},
78
83
  groupMaps: {},