@lobehub/lobehub 2.0.0-next.77 → 2.0.0-next.79
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 +42 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/types/instruction.ts +7 -2
- package/packages/database/src/utils/genWhere.test.ts +243 -0
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +1 -1
- package/src/app/[variants]/(main)/discover/(list)/mcp/features/Category/index.tsx +1 -1
- package/src/app/[variants]/(main)/discover/(list)/model/features/Category/index.tsx +1 -1
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +2 -17
- package/src/features/ModelSwitchPanel/index.tsx +0 -1
- package/src/store/chat/initialState.ts +4 -1
- package/src/store/chat/slices/operation/__tests__/actions.test.ts +353 -0
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +273 -0
- package/src/store/chat/slices/operation/actions.ts +451 -0
- package/src/store/chat/slices/operation/index.ts +4 -0
- package/src/store/chat/slices/operation/initialState.ts +44 -0
- package/src/store/chat/slices/operation/selectors.ts +246 -0
- package/src/store/chat/slices/operation/types.ts +134 -0
- package/src/store/chat/store.ts +4 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { ChatStoreState } from '@/store/chat/initialState';
|
|
2
|
+
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
3
|
+
|
|
4
|
+
import type { Operation, OperationType } from './types';
|
|
5
|
+
|
|
6
|
+
// === Basic Queries ===
|
|
7
|
+
/**
|
|
8
|
+
* Get all operations
|
|
9
|
+
*/
|
|
10
|
+
const getAllOperations = (s: ChatStoreState): Operation[] => {
|
|
11
|
+
return Object.values(s.operations);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get operations for current context (active session and topic)
|
|
16
|
+
*/
|
|
17
|
+
const getCurrentContextOperations = (s: ChatStoreState): Operation[] => {
|
|
18
|
+
const { activeId, activeTopicId } = s;
|
|
19
|
+
if (!activeId) return [];
|
|
20
|
+
|
|
21
|
+
const contextKey = messageMapKey(activeId, activeTopicId);
|
|
22
|
+
const operationIds = s.operationsByContext[contextKey] || [];
|
|
23
|
+
return operationIds.map((id) => s.operations[id]).filter(Boolean);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get all running operations
|
|
28
|
+
*/
|
|
29
|
+
const getRunningOperations = (s: ChatStoreState): Operation[] => {
|
|
30
|
+
return Object.values(s.operations).filter((op) => op.status === 'running');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get operation by ID
|
|
35
|
+
*/
|
|
36
|
+
const getOperationById =
|
|
37
|
+
(operationId: string) =>
|
|
38
|
+
(s: ChatStoreState): Operation | undefined => {
|
|
39
|
+
return s.operations[operationId];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get operation context from message ID
|
|
44
|
+
* Useful for automatic context retrieval
|
|
45
|
+
*/
|
|
46
|
+
const getOperationContextFromMessage =
|
|
47
|
+
(messageId: string) =>
|
|
48
|
+
(s: ChatStoreState): Operation['context'] | undefined => {
|
|
49
|
+
const operationId = s.messageOperationMap[messageId];
|
|
50
|
+
if (!operationId) return undefined;
|
|
51
|
+
|
|
52
|
+
const operation = s.operations[operationId];
|
|
53
|
+
return operation?.context;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get operations by message ID
|
|
58
|
+
*/
|
|
59
|
+
const getOperationsByMessage =
|
|
60
|
+
(messageId: string) =>
|
|
61
|
+
(s: ChatStoreState): Operation[] => {
|
|
62
|
+
const operationIds = s.operationsByMessage[messageId] || [];
|
|
63
|
+
return operationIds.map((id) => s.operations[id]).filter(Boolean);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get operations by type
|
|
68
|
+
*/
|
|
69
|
+
const getOperationsByType =
|
|
70
|
+
(type: OperationType) =>
|
|
71
|
+
(s: ChatStoreState): Operation[] => {
|
|
72
|
+
const operationIds = s.operationsByType[type] || [];
|
|
73
|
+
return operationIds.map((id) => s.operations[id]).filter(Boolean);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// === Status Checks ===
|
|
77
|
+
/**
|
|
78
|
+
* Check if there's any running operation
|
|
79
|
+
*/
|
|
80
|
+
const hasAnyRunningOperation = (s: ChatStoreState): boolean => {
|
|
81
|
+
return Object.values(s.operations).some((op) => op.status === 'running');
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if there's a running operation of specific type
|
|
86
|
+
*/
|
|
87
|
+
const hasRunningOperationType =
|
|
88
|
+
(type: OperationType) =>
|
|
89
|
+
(s: ChatStoreState): boolean => {
|
|
90
|
+
const operationIds = s.operationsByType[type] || [];
|
|
91
|
+
return operationIds.some((id) => {
|
|
92
|
+
const op = s.operations[id];
|
|
93
|
+
return op && op.status === 'running';
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if can interrupt (has running operations that can be cancelled)
|
|
99
|
+
*/
|
|
100
|
+
const canInterrupt = (s: ChatStoreState): boolean => {
|
|
101
|
+
const currentOps = getCurrentContextOperations(s);
|
|
102
|
+
return currentOps.some((op) => op.status === 'running');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if can send message (no blocking operations running)
|
|
107
|
+
*/
|
|
108
|
+
const canSendMessage = (s: ChatStoreState): boolean => {
|
|
109
|
+
// Cannot send if there's any running operation in current context
|
|
110
|
+
const currentOps = getCurrentContextOperations(s);
|
|
111
|
+
const hasRunningOp = currentOps.some((op) => op.status === 'running');
|
|
112
|
+
|
|
113
|
+
return !hasRunningOp;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// === UI Helpers ===
|
|
117
|
+
/**
|
|
118
|
+
* Get active operation types (for debugging/display)
|
|
119
|
+
*/
|
|
120
|
+
const getActiveOperationTypes = (s: ChatStoreState): OperationType[] => {
|
|
121
|
+
const runningOps = getRunningOperations(s);
|
|
122
|
+
const types = new Set(runningOps.map((op) => op.type));
|
|
123
|
+
return Array.from(types);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get current operation label for UI display
|
|
128
|
+
* Returns the label of the most recent running operation in current context
|
|
129
|
+
*/
|
|
130
|
+
const getCurrentOperationLabel = (s: ChatStoreState): string => {
|
|
131
|
+
const currentOps = getCurrentContextOperations(s);
|
|
132
|
+
const runningOps = currentOps.filter((op) => op.status === 'running');
|
|
133
|
+
|
|
134
|
+
if (runningOps.length === 0) return '';
|
|
135
|
+
|
|
136
|
+
// Get the most recent running operation
|
|
137
|
+
const latestOp = runningOps.reduce((latest, op) => {
|
|
138
|
+
return op.metadata.startTime > latest.metadata.startTime ? op : latest;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return latestOp.label || latestOp.type;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get current operation progress
|
|
146
|
+
* Returns the progress of the most recent running operation with progress info
|
|
147
|
+
*/
|
|
148
|
+
const getCurrentOperationProgress = (s: ChatStoreState): number | undefined => {
|
|
149
|
+
const currentOps = getCurrentContextOperations(s);
|
|
150
|
+
const runningOps = currentOps.filter((op) => op.status === 'running');
|
|
151
|
+
|
|
152
|
+
if (runningOps.length === 0) return undefined;
|
|
153
|
+
|
|
154
|
+
// Find the most recent operation with progress
|
|
155
|
+
const opsWithProgress = runningOps.filter((op) => op.metadata.progress);
|
|
156
|
+
|
|
157
|
+
if (opsWithProgress.length === 0) return undefined;
|
|
158
|
+
|
|
159
|
+
const latestOp = opsWithProgress.reduce((latest, op) => {
|
|
160
|
+
return op.metadata.startTime > latest.metadata.startTime ? op : latest;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return latestOp.metadata.progress?.percentage;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// === Backward Compatibility ===
|
|
167
|
+
/**
|
|
168
|
+
* Check if AI is generating (for backward compatibility)
|
|
169
|
+
* Equivalent to: hasRunningOperationType('generateAI')
|
|
170
|
+
*/
|
|
171
|
+
const isAIGenerating = (s: ChatStoreState): boolean => {
|
|
172
|
+
return hasRunningOperationType('generateAI')(s);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if continuing (for backward compatibility)
|
|
177
|
+
*/
|
|
178
|
+
const isContinuing = (s: ChatStoreState): boolean => {
|
|
179
|
+
return hasRunningOperationType('continue')(s);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if in RAG flow (for backward compatibility)
|
|
184
|
+
*/
|
|
185
|
+
const isInRAGFlow = (s: ChatStoreState): boolean => {
|
|
186
|
+
return hasRunningOperationType('rag')(s);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check if in search workflow (for backward compatibility)
|
|
191
|
+
*/
|
|
192
|
+
const isInSearchWorkflow = (s: ChatStoreState): boolean => {
|
|
193
|
+
return hasRunningOperationType('searchWorkflow')(s);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if a specific message is being processed
|
|
198
|
+
*/
|
|
199
|
+
const isMessageProcessing =
|
|
200
|
+
(messageId: string) =>
|
|
201
|
+
(s: ChatStoreState): boolean => {
|
|
202
|
+
const operations = getOperationsByMessage(messageId)(s);
|
|
203
|
+
return operations.some((op) => op.status === 'running');
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if regenerating (for backward compatibility)
|
|
208
|
+
*/
|
|
209
|
+
const isRegenerating = (s: ChatStoreState): boolean => {
|
|
210
|
+
return hasRunningOperationType('regenerate')(s);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if sending message (for backward compatibility)
|
|
215
|
+
* Equivalent to: hasRunningOperationType('sendMessage')
|
|
216
|
+
*/
|
|
217
|
+
const isSendingMessage = (s: ChatStoreState): boolean => {
|
|
218
|
+
return hasRunningOperationType('sendMessage')(s);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Operation Selectors
|
|
223
|
+
*/
|
|
224
|
+
export const operationSelectors = {
|
|
225
|
+
canInterrupt,
|
|
226
|
+
canSendMessage,
|
|
227
|
+
getActiveOperationTypes,
|
|
228
|
+
getAllOperations,
|
|
229
|
+
getCurrentContextOperations,
|
|
230
|
+
getCurrentOperationLabel,
|
|
231
|
+
getCurrentOperationProgress,
|
|
232
|
+
getOperationById,
|
|
233
|
+
getOperationContextFromMessage,
|
|
234
|
+
getOperationsByMessage,
|
|
235
|
+
getOperationsByType,
|
|
236
|
+
getRunningOperations,
|
|
237
|
+
hasAnyRunningOperation,
|
|
238
|
+
hasRunningOperationType,
|
|
239
|
+
isAIGenerating,
|
|
240
|
+
isContinuing,
|
|
241
|
+
isInRAGFlow,
|
|
242
|
+
isInSearchWorkflow,
|
|
243
|
+
isMessageProcessing,
|
|
244
|
+
isRegenerating,
|
|
245
|
+
isSendingMessage,
|
|
246
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* eslint-disable typescript-sort-keys/interface, sort-keys-fix/sort-keys-fix */
|
|
2
|
+
/**
|
|
3
|
+
* Operation Type Definitions
|
|
4
|
+
* Unified operation state management for all async operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Operation type enumeration - covers all async operations
|
|
9
|
+
*/
|
|
10
|
+
export type OperationType =
|
|
11
|
+
// === Message sending ===
|
|
12
|
+
| 'sendMessage' // Send message to server
|
|
13
|
+
| 'createTopic' // Auto create topic
|
|
14
|
+
|
|
15
|
+
// === AI generation ===
|
|
16
|
+
| 'generateAI' // AI generate response (entire agent runtime execution)
|
|
17
|
+
| 'reasoning' // AI reasoning process (child operation)
|
|
18
|
+
| 'regenerate' // Regenerate message
|
|
19
|
+
| 'continue' // Continue generation
|
|
20
|
+
|
|
21
|
+
// === RAG and retrieval ===
|
|
22
|
+
| 'rag' // RAG retrieval flow (child operation)
|
|
23
|
+
| 'searchWorkflow' // Search workflow
|
|
24
|
+
|
|
25
|
+
// === Tool calling ===
|
|
26
|
+
| 'toolCalling' // Tool calling (streaming, child operation)
|
|
27
|
+
| 'pluginApi' // Plugin API call
|
|
28
|
+
| 'builtinToolSearch' // Builtin tool: search
|
|
29
|
+
| 'builtinToolInterpreter' // Builtin tool: code interpreter
|
|
30
|
+
| 'builtinToolLocalSystem' // Builtin tool: local system
|
|
31
|
+
|
|
32
|
+
// === Group Chat ===
|
|
33
|
+
| 'supervisorDecision' // Supervisor decision
|
|
34
|
+
| 'groupAgentGenerate' // Group agent generate
|
|
35
|
+
|
|
36
|
+
// === Others ===
|
|
37
|
+
| 'topicSummary' // Topic summary
|
|
38
|
+
| 'historySummary'; // History summary
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Operation status
|
|
42
|
+
*/
|
|
43
|
+
export type OperationStatus =
|
|
44
|
+
| 'pending' // Waiting to start (not currently used)
|
|
45
|
+
| 'running' // Executing
|
|
46
|
+
| 'paused' // Paused (for user intervention scenarios)
|
|
47
|
+
| 'completed' // Successfully completed
|
|
48
|
+
| 'cancelled' // User cancelled
|
|
49
|
+
| 'failed'; // Execution failed
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Operation context - business entity associations
|
|
53
|
+
* Captured when Operation is created, never changes afterwards
|
|
54
|
+
*/
|
|
55
|
+
export interface OperationContext {
|
|
56
|
+
sessionId?: string; // Associated session ID (normal session or group)
|
|
57
|
+
topicId?: string | null; // Associated topic ID (null means default topic)
|
|
58
|
+
messageId?: string; // Associated message ID
|
|
59
|
+
threadId?: string; // Associated thread ID (Portal Thread)
|
|
60
|
+
groupId?: string; // Associated group ID (Group Chat)
|
|
61
|
+
agentId?: string; // Associated agent ID (specific agent in Group Chat)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Operation metadata
|
|
66
|
+
*/
|
|
67
|
+
export interface OperationMetadata {
|
|
68
|
+
// Progress information
|
|
69
|
+
progress?: {
|
|
70
|
+
current: number;
|
|
71
|
+
total: number;
|
|
72
|
+
percentage?: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Performance information
|
|
76
|
+
startTime: number;
|
|
77
|
+
endTime?: number;
|
|
78
|
+
duration?: number;
|
|
79
|
+
|
|
80
|
+
// Error information
|
|
81
|
+
error?: {
|
|
82
|
+
type: string;
|
|
83
|
+
message: string;
|
|
84
|
+
code?: string;
|
|
85
|
+
details?: any;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Cancel information
|
|
89
|
+
cancelReason?: string;
|
|
90
|
+
|
|
91
|
+
// Other metadata (extensible)
|
|
92
|
+
[key: string]: any;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Operation definition
|
|
97
|
+
*/
|
|
98
|
+
export interface Operation {
|
|
99
|
+
// === Basic information ===
|
|
100
|
+
id: string; // Unique operation ID (using nanoid)
|
|
101
|
+
type: OperationType; // Operation type
|
|
102
|
+
status: OperationStatus; // Operation status
|
|
103
|
+
|
|
104
|
+
// === Context (core: capture and fix business context) ===
|
|
105
|
+
context: OperationContext; // Associated entities, captured at creation
|
|
106
|
+
|
|
107
|
+
// === Control ===
|
|
108
|
+
abortController: AbortController; // Abort controller
|
|
109
|
+
|
|
110
|
+
// === Metadata ===
|
|
111
|
+
metadata: OperationMetadata;
|
|
112
|
+
|
|
113
|
+
// === Dependencies ===
|
|
114
|
+
parentOperationId?: string; // Parent operation ID (for operation nesting)
|
|
115
|
+
childOperationIds?: string[]; // Child operation IDs
|
|
116
|
+
|
|
117
|
+
// === UI display ===
|
|
118
|
+
label?: string; // Operation display label (for UI)
|
|
119
|
+
description?: string; // Operation description (for tooltip)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Operation filter for querying operations
|
|
124
|
+
*/
|
|
125
|
+
export interface OperationFilter {
|
|
126
|
+
type?: OperationType | OperationType[];
|
|
127
|
+
status?: OperationStatus | OperationStatus[];
|
|
128
|
+
sessionId?: string;
|
|
129
|
+
topicId?: string | null;
|
|
130
|
+
messageId?: string;
|
|
131
|
+
threadId?: string;
|
|
132
|
+
groupId?: string;
|
|
133
|
+
agentId?: string;
|
|
134
|
+
}
|
package/src/store/chat/store.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ChatAIChatAction, chatAiChat } from './slices/aiChat/actions';
|
|
|
16
16
|
import { ChatTTSAction, chatTTS } from './slices/tts/action';
|
|
17
17
|
import { ChatThreadAction, chatThreadMessage } from './slices/thread/action';
|
|
18
18
|
import { chatAiGroupChat, ChatGroupChatAction } from './slices/aiChat/actions/generateAIGroupChat';
|
|
19
|
+
import { OperationActions, operationActions } from './slices/operation/actions';
|
|
19
20
|
|
|
20
21
|
export interface ChatStoreAction
|
|
21
22
|
extends ChatMessageAction,
|
|
@@ -27,7 +28,8 @@ export interface ChatStoreAction
|
|
|
27
28
|
ChatTTSAction,
|
|
28
29
|
ChatPluginAction,
|
|
29
30
|
ChatBuiltinToolAction,
|
|
30
|
-
ChatPortalAction
|
|
31
|
+
ChatPortalAction,
|
|
32
|
+
OperationActions {}
|
|
31
33
|
|
|
32
34
|
export type ChatStore = ChatStoreAction & ChatStoreState;
|
|
33
35
|
|
|
@@ -46,6 +48,7 @@ const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...
|
|
|
46
48
|
...chatToolSlice(...params),
|
|
47
49
|
...chatPlugin(...params),
|
|
48
50
|
...chatPortalSlice(...params),
|
|
51
|
+
...operationActions(...params),
|
|
49
52
|
|
|
50
53
|
// cloud
|
|
51
54
|
});
|