@lobehub/lobehub 2.0.0-next.35 → 2.0.0-next.36
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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/next.config.ts +5 -6
- package/package.json +2 -2
- package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +112 -77
- package/packages/agent-runtime/src/core/runtime.ts +63 -18
- package/packages/agent-runtime/src/types/generalAgent.ts +55 -0
- package/packages/agent-runtime/src/types/index.ts +1 -0
- package/packages/agent-runtime/src/types/instruction.ts +10 -3
- package/packages/const/src/user.ts +0 -1
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +8 -6
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +12 -12
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +249 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +4 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +260 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +481 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +5 -1
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +4 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +407 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +18 -2
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +25 -3
- package/packages/conversation-flow/src/__tests__/parse.test.ts +12 -0
- package/packages/conversation-flow/src/index.ts +1 -1
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +112 -34
- package/packages/conversation-flow/src/types/flatMessageList.ts +0 -12
- package/packages/conversation-flow/src/{types.ts → types/index.ts} +3 -14
- package/packages/database/src/models/message.ts +18 -19
- package/packages/types/src/aiChat.ts +2 -0
- package/packages/types/src/importer.ts +2 -2
- package/packages/types/src/message/ui/chat.ts +17 -1
- package/packages/types/src/message/ui/extra.ts +2 -2
- package/packages/types/src/message/ui/params.ts +2 -2
- package/packages/types/src/user/preference.ts +0 -4
- package/packages/utils/src/tokenizer/index.ts +3 -11
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +3 -3
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +6 -6
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/Content.tsx +5 -3
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
- package/src/app/[variants]/(main)/labs/page.tsx +0 -9
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
- package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
- package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
- package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
- package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
- package/src/features/Conversation/Error/index.tsx +0 -5
- package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
- package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
- package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
- package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
- package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
- package/src/features/Conversation/Messages/Default.tsx +1 -0
- package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
- package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
- package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
- package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
- package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
- package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
- package/src/features/Conversation/Messages/Group/index.tsx +2 -1
- package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
- package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
- package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
- package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
- package/src/features/Conversation/Messages/User/index.tsx +43 -44
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +3 -3
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
- package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
- package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
- package/src/hooks/useHotkeys/chatScope.ts +15 -7
- package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
- package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
- package/src/server/routers/lambda/aiChat.ts +3 -2
- package/src/server/routers/lambda/message.ts +8 -16
- package/src/server/services/message/__tests__/index.test.ts +29 -39
- package/src/server/services/message/index.ts +41 -36
- package/src/services/electron/desktopNotification.ts +6 -6
- package/src/services/electron/file.ts +6 -6
- package/src/services/file/ClientS3/index.ts +8 -8
- package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
- package/src/services/message/index.ts +21 -15
- package/src/services/upload.ts +11 -11
- package/src/services/utils/abortableRequest.test.ts +161 -0
- package/src/services/utils/abortableRequest.ts +67 -0
- package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
- package/src/store/chat/agents/createAgentExecutors.ts +395 -0
- package/src/store/chat/helpers.test.ts +0 -99
- package/src/store/chat/helpers.ts +0 -11
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
- package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
- package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
- package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
- package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
- package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
- package/src/store/chat/slices/message/action.test.ts +79 -68
- package/src/store/chat/slices/message/actions/index.ts +39 -0
- package/src/store/chat/slices/message/actions/internals.ts +77 -0
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
- package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
- package/src/store/chat/slices/message/actions/query.ts +120 -0
- package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
- package/src/store/chat/slices/message/initialState.ts +13 -0
- package/src/store/chat/slices/message/reducer.test.ts +48 -370
- package/src/store/chat/slices/message/reducer.ts +17 -81
- package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
- package/src/store/chat/slices/message/selectors/chat.ts +78 -242
- package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
- package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
- package/src/store/chat/slices/plugin/action.test.ts +62 -64
- package/src/store/chat/slices/plugin/action.ts +34 -28
- package/src/store/chat/slices/thread/action.test.ts +28 -31
- package/src/store/chat/slices/thread/action.ts +13 -10
- package/src/store/chat/slices/thread/selectors/index.ts +8 -6
- package/src/store/chat/slices/topic/reducer.ts +11 -3
- package/src/store/chat/store.ts +1 -1
- package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
- package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
- package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
- package/packages/database/src/utils/groupMessages.ts +0 -361
- package/packages/utils/src/tokenizer/client.ts +0 -35
- package/packages/utils/src/tokenizer/estimated.ts +0 -4
- package/packages/utils/src/tokenizer/server.ts +0 -11
- package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
- package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
- package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
- package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
- package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
- package/src/store/chat/slices/message/action.ts +0 -629
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
+
// Disable the auto sort key eslint rule to make the code more logic and readable
|
|
3
|
+
import { AgentRuntime, type AgentRuntimeContext } from '@lobechat/agent-runtime';
|
|
4
|
+
import { isDesktop } from '@lobechat/const';
|
|
5
|
+
import { knowledgeBaseQAPrompts } from '@lobechat/prompts';
|
|
6
|
+
import {
|
|
7
|
+
ChatImageItem,
|
|
8
|
+
ChatToolPayload,
|
|
9
|
+
MessageToolCall,
|
|
10
|
+
ModelUsage,
|
|
11
|
+
TraceNameMap,
|
|
12
|
+
UIChatMessage,
|
|
13
|
+
} from '@lobechat/types';
|
|
14
|
+
import type { MessageSemanticSearchChunk } from '@lobechat/types';
|
|
15
|
+
import debug from 'debug';
|
|
16
|
+
import { t } from 'i18next';
|
|
17
|
+
import { throttle } from 'lodash-es';
|
|
18
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
19
|
+
|
|
20
|
+
import { chatService } from '@/services/chat';
|
|
21
|
+
import { messageService } from '@/services/message';
|
|
22
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
|
23
|
+
import { getAgentStoreState } from '@/store/agent/store';
|
|
24
|
+
import { GeneralChatAgent } from '@/store/chat/agents/GeneralChatAgent';
|
|
25
|
+
import { createAgentExecutors } from '@/store/chat/agents/createAgentExecutors';
|
|
26
|
+
import { ChatStore } from '@/store/chat/store';
|
|
27
|
+
import { getFileStoreState } from '@/store/file/store';
|
|
28
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
29
|
+
|
|
30
|
+
import { topicSelectors } from '../../../selectors';
|
|
31
|
+
import { messageMapKey } from '../../../utils/messageMapKey';
|
|
32
|
+
|
|
33
|
+
const n = setNamespace('ai');
|
|
34
|
+
const log = debug('lobe-store:streaming-executor');
|
|
35
|
+
|
|
36
|
+
interface ProcessMessageParams {
|
|
37
|
+
traceId?: string;
|
|
38
|
+
isWelcomeQuestion?: boolean;
|
|
39
|
+
inSearchWorkflow?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* the RAG query content, should be embedding and used in the semantic search
|
|
42
|
+
*/
|
|
43
|
+
ragQuery?: string;
|
|
44
|
+
threadId?: string;
|
|
45
|
+
inPortalThread?: boolean;
|
|
46
|
+
|
|
47
|
+
groupId?: string;
|
|
48
|
+
agentId?: string;
|
|
49
|
+
agentConfig?: any; // Agent configuration for group chat agents
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Core streaming execution actions for AI chat
|
|
54
|
+
*/
|
|
55
|
+
export interface StreamingExecutorAction {
|
|
56
|
+
/**
|
|
57
|
+
* Retrieves an AI-generated chat message from the backend service with streaming
|
|
58
|
+
*/
|
|
59
|
+
internal_fetchAIChatMessage: (input: {
|
|
60
|
+
messages: UIChatMessage[];
|
|
61
|
+
messageId: string;
|
|
62
|
+
params?: ProcessMessageParams;
|
|
63
|
+
model: string;
|
|
64
|
+
provider: string;
|
|
65
|
+
}) => Promise<{
|
|
66
|
+
isFunctionCall: boolean;
|
|
67
|
+
tools?: ChatToolPayload[];
|
|
68
|
+
tool_calls?: MessageToolCall[];
|
|
69
|
+
content: string;
|
|
70
|
+
traceId?: string;
|
|
71
|
+
usage?: ModelUsage;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Executes the core processing logic for AI messages
|
|
75
|
+
* including preprocessing and postprocessing steps
|
|
76
|
+
*/
|
|
77
|
+
internal_execAgentRuntime: (params: {
|
|
78
|
+
messages: UIChatMessage[];
|
|
79
|
+
parentMessageId: string;
|
|
80
|
+
parentMessageType: 'user' | 'assistant';
|
|
81
|
+
inSearchWorkflow?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* the RAG query content, should be embedding and used in the semantic search
|
|
84
|
+
*/
|
|
85
|
+
ragQuery?: string;
|
|
86
|
+
threadId?: string;
|
|
87
|
+
inPortalThread?: boolean;
|
|
88
|
+
traceId?: string;
|
|
89
|
+
ragMetadata?: { ragQueryId: string; fileChunks: MessageSemanticSearchChunk[] };
|
|
90
|
+
}) => Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const streamingExecutor: StateCreator<
|
|
94
|
+
ChatStore,
|
|
95
|
+
[['zustand/devtools', never]],
|
|
96
|
+
[],
|
|
97
|
+
StreamingExecutorAction
|
|
98
|
+
> = (set, get) => ({
|
|
99
|
+
internal_fetchAIChatMessage: async ({ messages, messageId, params, provider, model }) => {
|
|
100
|
+
const {
|
|
101
|
+
internal_toggleChatLoading,
|
|
102
|
+
refreshMessages,
|
|
103
|
+
optimisticUpdateMessageContent,
|
|
104
|
+
internal_dispatchMessage,
|
|
105
|
+
internal_toggleToolCallingStreaming,
|
|
106
|
+
internal_toggleChatReasoning,
|
|
107
|
+
} = get();
|
|
108
|
+
|
|
109
|
+
const abortController = internal_toggleChatLoading(
|
|
110
|
+
true,
|
|
111
|
+
messageId,
|
|
112
|
+
n('generateMessage(start)', { messageId, messages }),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const agentConfig =
|
|
116
|
+
params?.agentConfig || agentSelectors.currentAgentConfig(getAgentStoreState());
|
|
117
|
+
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
|
118
|
+
|
|
119
|
+
// ================================== //
|
|
120
|
+
// messages uniformly preprocess //
|
|
121
|
+
// ================================== //
|
|
122
|
+
// 4. handle max_tokens
|
|
123
|
+
agentConfig.params.max_tokens = chatConfig.enableMaxTokens
|
|
124
|
+
? agentConfig.params.max_tokens
|
|
125
|
+
: undefined;
|
|
126
|
+
|
|
127
|
+
// 5. handle reasoning_effort
|
|
128
|
+
agentConfig.params.reasoning_effort = chatConfig.enableReasoningEffort
|
|
129
|
+
? agentConfig.params.reasoning_effort
|
|
130
|
+
: undefined;
|
|
131
|
+
|
|
132
|
+
let isFunctionCall = false;
|
|
133
|
+
let tools: ChatToolPayload[] | undefined;
|
|
134
|
+
let tool_calls: MessageToolCall[] | undefined;
|
|
135
|
+
let finalUsage;
|
|
136
|
+
let msgTraceId: string | undefined;
|
|
137
|
+
let output = '';
|
|
138
|
+
let thinking = '';
|
|
139
|
+
let thinkingStartAt: number;
|
|
140
|
+
let duration: number;
|
|
141
|
+
// to upload image
|
|
142
|
+
const uploadTasks: Map<string, Promise<{ id?: string; url?: string }>> = new Map();
|
|
143
|
+
|
|
144
|
+
// Throttle tool_calls updates to prevent excessive re-renders (max once per 300ms)
|
|
145
|
+
const throttledUpdateToolCalls = throttle(
|
|
146
|
+
(toolCalls: any[]) => {
|
|
147
|
+
internal_dispatchMessage({
|
|
148
|
+
id: messageId,
|
|
149
|
+
type: 'updateMessage',
|
|
150
|
+
value: { tools: get().internal_transformToolCalls(toolCalls) },
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
300,
|
|
154
|
+
{ leading: true, trailing: true },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const historySummary = chatConfig.enableCompressHistory
|
|
158
|
+
? topicSelectors.currentActiveTopicSummary(get())
|
|
159
|
+
: undefined;
|
|
160
|
+
await chatService.createAssistantMessageStream({
|
|
161
|
+
abortController,
|
|
162
|
+
params: {
|
|
163
|
+
messages,
|
|
164
|
+
model,
|
|
165
|
+
provider,
|
|
166
|
+
...agentConfig.params,
|
|
167
|
+
plugins: agentConfig.plugins,
|
|
168
|
+
},
|
|
169
|
+
historySummary: historySummary?.content,
|
|
170
|
+
trace: {
|
|
171
|
+
traceId: params?.traceId,
|
|
172
|
+
sessionId: get().activeId,
|
|
173
|
+
topicId: get().activeTopicId,
|
|
174
|
+
traceName: TraceNameMap.Conversation,
|
|
175
|
+
},
|
|
176
|
+
onErrorHandle: async (error) => {
|
|
177
|
+
await messageService.updateMessageError(messageId, error);
|
|
178
|
+
await refreshMessages();
|
|
179
|
+
},
|
|
180
|
+
onFinish: async (
|
|
181
|
+
content,
|
|
182
|
+
{ traceId, observationId, toolCalls, reasoning, grounding, usage, speed },
|
|
183
|
+
) => {
|
|
184
|
+
// if there is traceId, update it
|
|
185
|
+
if (traceId) {
|
|
186
|
+
msgTraceId = traceId;
|
|
187
|
+
messageService.updateMessage(messageId, {
|
|
188
|
+
traceId,
|
|
189
|
+
observationId: observationId ?? undefined,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 等待所有图片上传完成
|
|
194
|
+
let finalImages: ChatImageItem[] = [];
|
|
195
|
+
|
|
196
|
+
if (uploadTasks.size > 0) {
|
|
197
|
+
try {
|
|
198
|
+
// 等待所有上传任务完成
|
|
199
|
+
const uploadResults = await Promise.all(uploadTasks.values());
|
|
200
|
+
|
|
201
|
+
// 使用上传后的 S3 URL 替换原始图像数据
|
|
202
|
+
finalImages = uploadResults.filter((i) => !!i.url) as ChatImageItem[];
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('Error waiting for image uploads:', error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let parsedToolCalls = toolCalls;
|
|
209
|
+
if (parsedToolCalls && parsedToolCalls.length > 0) {
|
|
210
|
+
// Flush any pending throttled updates before finalizing
|
|
211
|
+
throttledUpdateToolCalls.flush();
|
|
212
|
+
internal_toggleToolCallingStreaming(messageId, undefined);
|
|
213
|
+
|
|
214
|
+
tools = get().internal_transformToolCalls(parsedToolCalls);
|
|
215
|
+
tool_calls = toolCalls;
|
|
216
|
+
|
|
217
|
+
parsedToolCalls = parsedToolCalls.map((item) => ({
|
|
218
|
+
...item,
|
|
219
|
+
function: {
|
|
220
|
+
...item.function,
|
|
221
|
+
arguments: !!item.function.arguments ? item.function.arguments : '{}',
|
|
222
|
+
},
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
isFunctionCall = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
finalUsage = usage;
|
|
229
|
+
internal_toggleChatReasoning(false, messageId, n('toggleChatReasoning/false') as string);
|
|
230
|
+
|
|
231
|
+
// update the content after fetch result
|
|
232
|
+
await optimisticUpdateMessageContent(messageId, content, {
|
|
233
|
+
toolCalls: parsedToolCalls,
|
|
234
|
+
reasoning: !!reasoning ? { ...reasoning, duration } : undefined,
|
|
235
|
+
search: !!grounding?.citations ? grounding : undefined,
|
|
236
|
+
imageList: finalImages.length > 0 ? finalImages : undefined,
|
|
237
|
+
metadata: speed ? { ...usage, ...speed } : usage,
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
onMessageHandle: async (chunk) => {
|
|
241
|
+
switch (chunk.type) {
|
|
242
|
+
case 'grounding': {
|
|
243
|
+
// if there is no citations, then stop
|
|
244
|
+
if (
|
|
245
|
+
!chunk.grounding ||
|
|
246
|
+
!chunk.grounding.citations ||
|
|
247
|
+
chunk.grounding.citations.length <= 0
|
|
248
|
+
)
|
|
249
|
+
return;
|
|
250
|
+
|
|
251
|
+
internal_dispatchMessage({
|
|
252
|
+
id: messageId,
|
|
253
|
+
type: 'updateMessage',
|
|
254
|
+
value: {
|
|
255
|
+
search: {
|
|
256
|
+
citations: chunk.grounding.citations,
|
|
257
|
+
searchQueries: chunk.grounding.searchQueries,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'base64_image': {
|
|
265
|
+
internal_dispatchMessage({
|
|
266
|
+
id: messageId,
|
|
267
|
+
type: 'updateMessage',
|
|
268
|
+
value: {
|
|
269
|
+
imageList: chunk.images.map((i) => ({ id: i.id, url: i.data, alt: i.id })),
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
const image = chunk.image;
|
|
273
|
+
|
|
274
|
+
const task = getFileStoreState()
|
|
275
|
+
.uploadBase64FileWithProgress(image.data)
|
|
276
|
+
.then((value) => ({
|
|
277
|
+
id: value?.id,
|
|
278
|
+
url: value?.url,
|
|
279
|
+
alt: value?.filename || value?.id,
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
uploadTasks.set(image.id, task);
|
|
283
|
+
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case 'text': {
|
|
288
|
+
output += chunk.text;
|
|
289
|
+
|
|
290
|
+
// if there is no duration, it means the end of reasoning
|
|
291
|
+
if (!duration) {
|
|
292
|
+
duration = Date.now() - thinkingStartAt;
|
|
293
|
+
|
|
294
|
+
const isInChatReasoning = get().reasoningLoadingIds.includes(messageId);
|
|
295
|
+
if (isInChatReasoning) {
|
|
296
|
+
internal_toggleChatReasoning(
|
|
297
|
+
false,
|
|
298
|
+
messageId,
|
|
299
|
+
n('toggleChatReasoning/false') as string,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
internal_dispatchMessage({
|
|
305
|
+
id: messageId,
|
|
306
|
+
type: 'updateMessage',
|
|
307
|
+
value: {
|
|
308
|
+
content: output,
|
|
309
|
+
reasoning: !!thinking ? { content: thinking, duration } : undefined,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
case 'reasoning': {
|
|
316
|
+
// if there is no thinkingStartAt, it means the start of reasoning
|
|
317
|
+
if (!thinkingStartAt) {
|
|
318
|
+
thinkingStartAt = Date.now();
|
|
319
|
+
internal_toggleChatReasoning(
|
|
320
|
+
true,
|
|
321
|
+
messageId,
|
|
322
|
+
n('toggleChatReasoning/true') as string,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
thinking += chunk.text;
|
|
327
|
+
|
|
328
|
+
internal_dispatchMessage({
|
|
329
|
+
id: messageId,
|
|
330
|
+
type: 'updateMessage',
|
|
331
|
+
value: { reasoning: { content: thinking } },
|
|
332
|
+
});
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// is this message is just a tool call
|
|
337
|
+
case 'tool_calls': {
|
|
338
|
+
internal_toggleToolCallingStreaming(messageId, chunk.isAnimationActives);
|
|
339
|
+
throttledUpdateToolCalls(chunk.tool_calls);
|
|
340
|
+
isFunctionCall = true;
|
|
341
|
+
const isInChatReasoning = get().reasoningLoadingIds.includes(messageId);
|
|
342
|
+
if (isInChatReasoning) {
|
|
343
|
+
internal_toggleChatReasoning(
|
|
344
|
+
false,
|
|
345
|
+
messageId,
|
|
346
|
+
n('toggleChatReasoning/false') as string,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
internal_toggleChatLoading(false, messageId, n('generateMessage(end)') as string);
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
isFunctionCall,
|
|
358
|
+
traceId: msgTraceId,
|
|
359
|
+
content: output,
|
|
360
|
+
tools,
|
|
361
|
+
usage: finalUsage,
|
|
362
|
+
tool_calls,
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
internal_execAgentRuntime: async (params) => {
|
|
367
|
+
const { messages: originalMessages, parentMessageId, parentMessageType } = params;
|
|
368
|
+
|
|
369
|
+
log(
|
|
370
|
+
'[internal_execAgentRuntime] start, parentMessageId: %s,parentMessageType: %s, messages count: %d',
|
|
371
|
+
parentMessageId,
|
|
372
|
+
parentMessageType,
|
|
373
|
+
originalMessages.length,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const { activeId, activeTopicId } = get();
|
|
377
|
+
const messageKey = messageMapKey(activeId, activeTopicId);
|
|
378
|
+
|
|
379
|
+
// Create a new array to avoid modifying the original messages
|
|
380
|
+
let messages = [...originalMessages];
|
|
381
|
+
|
|
382
|
+
const agentStoreState = getAgentStoreState();
|
|
383
|
+
const agentConfigData = agentSelectors.currentAgentConfig(agentStoreState);
|
|
384
|
+
const { chatConfig } = agentConfigData;
|
|
385
|
+
|
|
386
|
+
// Use current agent config
|
|
387
|
+
const model = agentConfigData.model;
|
|
388
|
+
const provider = agentConfigData.provider;
|
|
389
|
+
|
|
390
|
+
// ===========================================
|
|
391
|
+
// Step 1: RAG Preprocessing (if enabled)
|
|
392
|
+
// ===========================================
|
|
393
|
+
if (params.ragQuery && parentMessageType === 'user') {
|
|
394
|
+
const userMessageId = parentMessageId;
|
|
395
|
+
log('[internal_execAgentRuntime] RAG preprocessing start');
|
|
396
|
+
|
|
397
|
+
// Get relevant chunks from semantic search
|
|
398
|
+
const {
|
|
399
|
+
chunks,
|
|
400
|
+
queryId: ragQueryId,
|
|
401
|
+
rewriteQuery,
|
|
402
|
+
} = await get().internal_retrieveChunks(
|
|
403
|
+
userMessageId,
|
|
404
|
+
params.ragQuery,
|
|
405
|
+
// Skip the last message content when building context
|
|
406
|
+
messages.map((m) => m.content).slice(0, messages.length - 1),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
log('[internal_execAgentRuntime] RAG chunks retrieved: %d chunks', chunks.length);
|
|
410
|
+
|
|
411
|
+
const lastMsg = messages.pop() as UIChatMessage;
|
|
412
|
+
|
|
413
|
+
// Build RAG context and append to user query
|
|
414
|
+
const knowledgeBaseQAContext = knowledgeBaseQAPrompts({
|
|
415
|
+
chunks,
|
|
416
|
+
userQuery: lastMsg.content,
|
|
417
|
+
rewriteQuery,
|
|
418
|
+
knowledge: agentSelectors.currentEnabledKnowledge(agentStoreState),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
messages.push({
|
|
422
|
+
...lastMsg,
|
|
423
|
+
content: (lastMsg.content + '\n\n' + knowledgeBaseQAContext).trim(),
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Update assistant message with RAG metadata
|
|
427
|
+
const fileChunks: MessageSemanticSearchChunk[] = chunks.map((c) => ({
|
|
428
|
+
id: c.id,
|
|
429
|
+
similarity: c.similarity,
|
|
430
|
+
}));
|
|
431
|
+
|
|
432
|
+
if (fileChunks.length > 0) {
|
|
433
|
+
// Note: RAG metadata will be updated after assistant message is created by call_llm executor
|
|
434
|
+
// Store RAG data temporarily in params for later use
|
|
435
|
+
params.ragMetadata = { ragQueryId: ragQueryId!, fileChunks };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
log('[internal_execAgentRuntime] RAG preprocessing completed');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ===========================================
|
|
442
|
+
// Step 3: Create and Execute Agent Runtime
|
|
443
|
+
// ===========================================
|
|
444
|
+
log('[internal_execAgentRuntime] Creating agent runtime');
|
|
445
|
+
|
|
446
|
+
const agent = new GeneralChatAgent({
|
|
447
|
+
agentConfig: { maxSteps: 1000 },
|
|
448
|
+
sessionId: `${messageKey}/${params.parentMessageId}`,
|
|
449
|
+
modelRuntimeConfig: {
|
|
450
|
+
model,
|
|
451
|
+
provider: provider!,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
const runtime = new AgentRuntime(agent, {
|
|
455
|
+
executors: createAgentExecutors({
|
|
456
|
+
get,
|
|
457
|
+
messageKey,
|
|
458
|
+
parentId: params.parentMessageId,
|
|
459
|
+
parentMessageType,
|
|
460
|
+
params,
|
|
461
|
+
}),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Create initial state
|
|
465
|
+
let state = AgentRuntime.createInitialState({
|
|
466
|
+
sessionId: activeId,
|
|
467
|
+
messages,
|
|
468
|
+
maxSteps: 20, // Prevent infinite loops
|
|
469
|
+
metadata: {
|
|
470
|
+
sessionId: activeId,
|
|
471
|
+
topicId: activeTopicId,
|
|
472
|
+
threadId: params.threadId,
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Initial context - use 'init' phase since state already contains messages
|
|
477
|
+
let nextContext: AgentRuntimeContext = {
|
|
478
|
+
phase: 'init',
|
|
479
|
+
payload: { model, provider, parentMessageId: params.parentMessageId },
|
|
480
|
+
session: {
|
|
481
|
+
sessionId: activeId,
|
|
482
|
+
messageCount: messages.length,
|
|
483
|
+
status: state.status,
|
|
484
|
+
stepCount: 0,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
log(
|
|
489
|
+
'[internal_execAgentRuntime] Agent runtime loop start, initial phase: %s',
|
|
490
|
+
nextContext.phase,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
// Execute the agent runtime loop
|
|
494
|
+
let stepCount = 0;
|
|
495
|
+
while (state.status !== 'done' && state.status !== 'error') {
|
|
496
|
+
stepCount++;
|
|
497
|
+
log(
|
|
498
|
+
'[internal_execAgentRuntime][step-%d]: phase=%s, status=%s',
|
|
499
|
+
stepCount,
|
|
500
|
+
nextContext.phase,
|
|
501
|
+
state.status,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const result = await runtime.step(state, nextContext);
|
|
505
|
+
|
|
506
|
+
log(
|
|
507
|
+
'[internal_execAgentRuntime] Step %d completed, events: %d, newStatus=%s',
|
|
508
|
+
stepCount,
|
|
509
|
+
result.events.length,
|
|
510
|
+
result.newState.status,
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Handle completion and error events
|
|
514
|
+
for (const event of result.events) {
|
|
515
|
+
if (event.type === 'done') {
|
|
516
|
+
log('[internal_execAgentRuntime] Received done event, syncing to database');
|
|
517
|
+
// Sync final state to database
|
|
518
|
+
const finalMessages = get().messagesMap[messageKey] || [];
|
|
519
|
+
get().replaceMessages(finalMessages);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (event.type === 'error') {
|
|
523
|
+
log('[internal_execAgentRuntime] Received error event: %o', event.error);
|
|
524
|
+
// Find the assistant message to update error
|
|
525
|
+
const currentMessages = get().messagesMap[messageKey] || [];
|
|
526
|
+
const assistantMessage = currentMessages.findLast((m) => m.role === 'assistant');
|
|
527
|
+
if (assistantMessage) {
|
|
528
|
+
await messageService.updateMessageError(assistantMessage.id, event.error);
|
|
529
|
+
}
|
|
530
|
+
const finalMessages = get().messagesMap[messageKey] || [];
|
|
531
|
+
get().replaceMessages(finalMessages);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
state = result.newState;
|
|
536
|
+
|
|
537
|
+
// If no nextContext, stop execution
|
|
538
|
+
if (!result.nextContext) {
|
|
539
|
+
log('[internal_execAgentRuntime] No next context, stopping loop');
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
nextContext = result.nextContext;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
log(
|
|
547
|
+
'[internal_execAgentRuntime] Agent runtime loop finished, final status: %s, total steps: %d',
|
|
548
|
+
state.status,
|
|
549
|
+
stepCount,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
// Update RAG metadata if available
|
|
553
|
+
if (params.ragMetadata) {
|
|
554
|
+
const finalMessages = get().messagesMap[messageKey] || [];
|
|
555
|
+
const assistantMessage = finalMessages.findLast((m) => m.role === 'assistant');
|
|
556
|
+
if (assistantMessage) {
|
|
557
|
+
await get().optimisticUpdateMessageRAG(assistantMessage.id, params.ragMetadata);
|
|
558
|
+
log('[internal_execAgentRuntime] RAG metadata updated for assistant message');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
log('[internal_execAgentRuntime] completed');
|
|
563
|
+
|
|
564
|
+
// Desktop notification (if not in tools calling mode)
|
|
565
|
+
if (isDesktop) {
|
|
566
|
+
try {
|
|
567
|
+
const messageKey = `${activeId}_${activeTopicId ?? null}`;
|
|
568
|
+
const finalMessages = get().messagesMap[messageKey] || [];
|
|
569
|
+
const lastAssistant = finalMessages.findLast((m) => m.role === 'assistant');
|
|
570
|
+
|
|
571
|
+
// Only show notification if there's content and no tools
|
|
572
|
+
if (lastAssistant?.content && !lastAssistant?.tools) {
|
|
573
|
+
const { desktopNotificationService } = await import(
|
|
574
|
+
'@/services/electron/desktopNotification'
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
await desktopNotificationService.showNotification({
|
|
578
|
+
body: lastAssistant.content,
|
|
579
|
+
title: t('notification.finishChatGeneration', { ns: 'electron' }),
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('Desktop notification error:', error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Summary history if context messages is larger than historyCount
|
|
588
|
+
const historyCount = agentChatConfigSelectors.historyCount(agentStoreState);
|
|
589
|
+
|
|
590
|
+
if (
|
|
591
|
+
agentChatConfigSelectors.enableHistoryCount(agentStoreState) &&
|
|
592
|
+
chatConfig.enableCompressHistory &&
|
|
593
|
+
messages.length > historyCount
|
|
594
|
+
) {
|
|
595
|
+
// after generation: [u1,a1,u2,a2,u3,a3]
|
|
596
|
+
// but the `messages` is still: [u1,a1,u2,a2,u3]
|
|
597
|
+
// So if historyCount=2, we need to summary [u1,a1,u2,a2]
|
|
598
|
+
// because user find UI is [u1,a1,u2,a2 | u3,a3]
|
|
599
|
+
const historyMessages = messages.slice(0, -historyCount + 1);
|
|
600
|
+
|
|
601
|
+
await get().internal_summaryHistory(historyMessages);
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import isEqual from 'fast-deep-equal';
|
|
2
|
+
import { produce } from 'immer';
|
|
3
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
4
|
+
|
|
5
|
+
import { ChatStore } from '@/store/chat/store';
|
|
6
|
+
import { Action } from '@/utils/storeDebug';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Manages loading states during streaming operations
|
|
10
|
+
*/
|
|
11
|
+
export interface StreamingStatesAction {
|
|
12
|
+
/**
|
|
13
|
+
* Toggles the loading state for AI message generation, managing the UI feedback
|
|
14
|
+
*/
|
|
15
|
+
internal_toggleChatLoading: (
|
|
16
|
+
loading: boolean,
|
|
17
|
+
id?: string,
|
|
18
|
+
action?: Action,
|
|
19
|
+
) => AbortController | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Toggles the loading state for AI message reasoning, managing the UI feedback
|
|
22
|
+
*/
|
|
23
|
+
internal_toggleChatReasoning: (
|
|
24
|
+
loading: boolean,
|
|
25
|
+
id?: string,
|
|
26
|
+
action?: string,
|
|
27
|
+
) => AbortController | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Toggles the loading state for messages in tools calling
|
|
30
|
+
*/
|
|
31
|
+
internal_toggleMessageInToolsCalling: (
|
|
32
|
+
loading: boolean,
|
|
33
|
+
id?: string,
|
|
34
|
+
action?: Action,
|
|
35
|
+
) => AbortController | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Toggles the loading state for search workflow
|
|
38
|
+
*/
|
|
39
|
+
internal_toggleSearchWorkflow: (loading: boolean, id?: string) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Controls the streaming state of tool calling processes, updating the UI accordingly
|
|
42
|
+
*/
|
|
43
|
+
internal_toggleToolCallingStreaming: (id: string, streaming: boolean[] | undefined) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const streamingStates: StateCreator<
|
|
47
|
+
ChatStore,
|
|
48
|
+
[['zustand/devtools', never]],
|
|
49
|
+
[],
|
|
50
|
+
StreamingStatesAction
|
|
51
|
+
> = (set, get) => ({
|
|
52
|
+
internal_toggleChatLoading: (loading, id, action) => {
|
|
53
|
+
return get().internal_toggleLoadingArrays('chatLoadingIds', loading, id, action);
|
|
54
|
+
},
|
|
55
|
+
internal_toggleChatReasoning: (loading, id, action) => {
|
|
56
|
+
return get().internal_toggleLoadingArrays('reasoningLoadingIds', loading, id, action);
|
|
57
|
+
},
|
|
58
|
+
internal_toggleMessageInToolsCalling: (loading, id) => {
|
|
59
|
+
return get().internal_toggleLoadingArrays('messageInToolsCallingIds', loading, id);
|
|
60
|
+
},
|
|
61
|
+
internal_toggleSearchWorkflow: (loading, id) => {
|
|
62
|
+
return get().internal_toggleLoadingArrays('searchWorkflowLoadingIds', loading, id);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
internal_toggleToolCallingStreaming: (id, streaming) => {
|
|
66
|
+
const previous = get().toolCallingStreamIds;
|
|
67
|
+
const next = produce(previous, (draft) => {
|
|
68
|
+
if (!!streaming) {
|
|
69
|
+
draft[id] = streaming;
|
|
70
|
+
} else {
|
|
71
|
+
delete draft[id];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (isEqual(previous, next)) return;
|
|
76
|
+
|
|
77
|
+
set(
|
|
78
|
+
{ toolCallingStreamIds: next },
|
|
79
|
+
|
|
80
|
+
false,
|
|
81
|
+
`toggleToolCallingStreaming/${!!streaming ? 'start' : 'end'}`,
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
});
|