@lobehub/lobehub 2.0.0-next.78 → 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.
@@ -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
+ }
@@ -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
  });