@memberjunction/server 2.111.1 → 2.112.0
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/dist/agents/skip-agent.d.ts +4 -4
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +808 -951
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts +1 -1
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +53 -43
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/apolloServer/index.js +1 -1
- package/dist/auth/AuthProviderFactory.d.ts +1 -1
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
- package/dist/auth/AuthProviderFactory.js +1 -3
- package/dist/auth/AuthProviderFactory.js.map +1 -1
- package/dist/auth/BaseAuthProvider.d.ts +1 -1
- package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/BaseAuthProvider.js +3 -2
- package/dist/auth/BaseAuthProvider.js.map +1 -1
- package/dist/auth/IAuthProvider.d.ts +1 -1
- package/dist/auth/IAuthProvider.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +6 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.js +1 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/newUsers.d.ts +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +7 -7
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
- package/dist/auth/providers/Auth0Provider.js +1 -1
- package/dist/auth/providers/Auth0Provider.js.map +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
- package/dist/auth/providers/CognitoProvider.js +3 -6
- package/dist/auth/providers/CognitoProvider.js.map +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
- package/dist/auth/providers/GoogleProvider.js +1 -1
- package/dist/auth/providers/GoogleProvider.js.map +1 -1
- package/dist/auth/providers/MSALProvider.d.ts +1 -1
- package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
- package/dist/auth/providers/MSALProvider.js +1 -1
- package/dist/auth/providers/MSALProvider.js.map +1 -1
- package/dist/auth/providers/OktaProvider.d.ts +1 -1
- package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
- package/dist/auth/providers/OktaProvider.js +1 -1
- package/dist/auth/providers/OktaProvider.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -10
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +9 -7
- package/dist/context.js.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
- package/dist/generated/generated.d.ts +648 -648
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2986 -1133
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
- package/dist/generic/KeyInputOutputTypes.js +1 -1
- package/dist/generic/KeyInputOutputTypes.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +15 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +15 -15
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +2 -2
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +28 -30
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +2 -2
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +60 -50
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +36 -38
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +43 -40
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +1 -1
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +1 -1
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -1
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +1 -1
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +5 -5
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +8 -6
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +3 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +11 -11
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/ReportResolver.js +1 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +27 -28
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +31 -31
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +9 -9
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +15 -14
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +48 -44
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +7 -7
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +12 -12
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +1 -1
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +14 -16
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +23 -25
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +17 -21
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +4 -6
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +1 -1
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +30 -30
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/package.json +36 -37
- package/src/agents/skip-agent.ts +1067 -1200
- package/src/agents/skip-sdk.ts +877 -851
- package/src/apolloServer/index.ts +2 -2
- package/src/auth/AuthProviderFactory.ts +8 -14
- package/src/auth/BaseAuthProvider.ts +5 -4
- package/src/auth/IAuthProvider.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +9 -2
- package/src/auth/index.ts +31 -26
- package/src/auth/initializeProviders.ts +3 -3
- package/src/auth/newUsers.ts +166 -134
- package/src/auth/providers/Auth0Provider.ts +5 -5
- package/src/auth/providers/CognitoProvider.ts +7 -10
- package/src/auth/providers/GoogleProvider.ts +4 -5
- package/src/auth/providers/MSALProvider.ts +5 -5
- package/src/auth/providers/OktaProvider.ts +6 -7
- package/src/config.ts +63 -54
- package/src/context.ts +42 -30
- package/src/entitySubclasses/entityPermissions.server.ts +3 -3
- package/src/generated/generated.ts +48130 -39930
- package/src/generic/KeyInputOutputTypes.ts +3 -6
- package/src/generic/ResolverBase.ts +119 -78
- package/src/generic/RunViewResolver.ts +27 -23
- package/src/index.ts +66 -42
- package/src/resolvers/ActionResolver.ts +46 -57
- package/src/resolvers/AskSkipResolver.ts +607 -533
- package/src/resolvers/ComponentRegistryResolver.ts +547 -562
- package/src/resolvers/CreateQueryResolver.ts +683 -655
- package/src/resolvers/DatasetResolver.ts +5 -6
- package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
- package/src/resolvers/EntityRecordNameResolver.ts +9 -5
- package/src/resolvers/EntityResolver.ts +9 -7
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +4 -4
- package/src/resolvers/GetDataContextDataResolver.ts +106 -118
- package/src/resolvers/GetDataResolver.ts +194 -205
- package/src/resolvers/MergeRecordsResolver.ts +5 -5
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/QueryResolver.ts +95 -78
- package/src/resolvers/ReportResolver.ts +2 -2
- package/src/resolvers/RunAIAgentResolver.ts +818 -828
- package/src/resolvers/RunAIPromptResolver.ts +693 -709
- package/src/resolvers/RunTemplateResolver.ts +105 -103
- package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
- package/src/resolvers/SyncDataResolver.ts +386 -352
- package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
- package/src/resolvers/TaskResolver.ts +110 -115
- package/src/resolvers/TransactionGroupResolver.ts +143 -138
- package/src/resolvers/UserFavoriteResolver.ts +17 -8
- package/src/resolvers/UserViewResolver.ts +17 -12
- package/src/rest/EntityCRUDHandler.ts +291 -268
- package/src/rest/RESTEndpointHandler.ts +782 -776
- package/src/rest/ViewOperationsHandler.ts +191 -195
- package/src/scheduler/LearningCycleScheduler.ts +8 -52
- package/src/services/ScheduledJobsService.ts +129 -132
- package/src/services/TaskOrchestrator.ts +792 -776
- package/src/types.ts +15 -9
- package/src/util.ts +112 -109
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
LogError,
|
|
4
|
+
LogStatus,
|
|
5
|
+
Metadata,
|
|
6
|
+
RunView,
|
|
7
|
+
UserInfo,
|
|
8
|
+
CompositeKey,
|
|
9
|
+
EntityFieldInfo,
|
|
10
|
+
EntityInfo,
|
|
11
|
+
EntityRelationshipInfo,
|
|
12
|
+
EntitySaveOptions,
|
|
13
|
+
EntityDeleteOptions,
|
|
14
|
+
IMetadataProvider,
|
|
15
|
+
} from '@memberjunction/global';
|
|
3
16
|
import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
|
|
4
17
|
import { BehaviorSubject } from 'rxjs';
|
|
5
18
|
import { take } from 'rxjs/operators';
|
|
@@ -52,9 +65,18 @@ import {
|
|
|
52
65
|
DataContextEntity,
|
|
53
66
|
DataContextItemEntity,
|
|
54
67
|
UserNotificationEntity,
|
|
55
|
-
AIAgentEntityExtended
|
|
68
|
+
AIAgentEntityExtended,
|
|
56
69
|
} from '@memberjunction/core-entities';
|
|
57
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
apiKey as callbackAPIKey,
|
|
72
|
+
AskSkipInfo,
|
|
73
|
+
baseUrl,
|
|
74
|
+
publicUrl,
|
|
75
|
+
configInfo,
|
|
76
|
+
graphqlPort,
|
|
77
|
+
graphqlRootPath,
|
|
78
|
+
mj_core_schema,
|
|
79
|
+
} from '../config.js';
|
|
58
80
|
import mssql from 'mssql';
|
|
59
81
|
|
|
60
82
|
import { registerEnumType } from 'type-graphql';
|
|
@@ -84,12 +106,15 @@ const SKIP_API_ENDPOINTS = {
|
|
|
84
106
|
*/
|
|
85
107
|
class ActiveConversationStreams {
|
|
86
108
|
private static instance: ActiveConversationStreams;
|
|
87
|
-
private streams: Map<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
private streams: Map<
|
|
110
|
+
string,
|
|
111
|
+
{
|
|
112
|
+
lastStatus: string;
|
|
113
|
+
lastUpdate: Date;
|
|
114
|
+
startTime: Date; // When processing actually started
|
|
115
|
+
sessionIds: Set<string>; // Track which sessions are listening
|
|
116
|
+
}
|
|
117
|
+
> = new Map();
|
|
93
118
|
|
|
94
119
|
private constructor() {}
|
|
95
120
|
|
|
@@ -114,7 +139,7 @@ class ActiveConversationStreams {
|
|
|
114
139
|
lastStatus: status,
|
|
115
140
|
lastUpdate: now,
|
|
116
141
|
startTime: now, // Track when processing started
|
|
117
|
-
sessionIds: sessionId ? new Set([sessionId]) : new Set()
|
|
142
|
+
sessionIds: sessionId ? new Set([sessionId]) : new Set(),
|
|
118
143
|
});
|
|
119
144
|
}
|
|
120
145
|
}
|
|
@@ -140,7 +165,7 @@ class ActiveConversationStreams {
|
|
|
140
165
|
lastStatus: 'Processing...',
|
|
141
166
|
lastUpdate: now,
|
|
142
167
|
startTime: now, // Track when processing started
|
|
143
|
-
sessionIds: new Set([sessionId])
|
|
168
|
+
sessionIds: new Set([sessionId]),
|
|
144
169
|
});
|
|
145
170
|
}
|
|
146
171
|
}
|
|
@@ -152,7 +177,7 @@ class ActiveConversationStreams {
|
|
|
152
177
|
isActive(conversationId: string): boolean {
|
|
153
178
|
const stream = this.streams.get(conversationId);
|
|
154
179
|
if (!stream) return false;
|
|
155
|
-
|
|
180
|
+
|
|
156
181
|
// Consider a stream inactive if no update in last 5 minutes
|
|
157
182
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
|
158
183
|
return stream.lastUpdate > fiveMinutesAgo;
|
|
@@ -170,19 +195,19 @@ class ActiveConversationStreams {
|
|
|
170
195
|
cleanupStaleStreams() {
|
|
171
196
|
const now = new Date();
|
|
172
197
|
const staleThreshold = new Date(now.getTime() - 30 * 60 * 1000); // 30 minutes
|
|
173
|
-
|
|
198
|
+
|
|
174
199
|
const staleConversations: string[] = [];
|
|
175
200
|
this.streams.forEach((stream, conversationId) => {
|
|
176
201
|
if (stream.lastUpdate < staleThreshold) {
|
|
177
202
|
staleConversations.push(conversationId);
|
|
178
203
|
}
|
|
179
204
|
});
|
|
180
|
-
|
|
181
|
-
staleConversations.forEach(conversationId => {
|
|
205
|
+
|
|
206
|
+
staleConversations.forEach((conversationId) => {
|
|
182
207
|
this.streams.delete(conversationId);
|
|
183
208
|
LogStatus(`Cleaned up stale stream for conversation ${conversationId}`);
|
|
184
209
|
});
|
|
185
|
-
|
|
210
|
+
|
|
186
211
|
if (staleConversations.length > 0) {
|
|
187
212
|
LogStatus(`Cleaned up ${staleConversations.length} stale conversation streams`);
|
|
188
213
|
}
|
|
@@ -192,9 +217,12 @@ class ActiveConversationStreams {
|
|
|
192
217
|
const activeStreams = ActiveConversationStreams.getInstance();
|
|
193
218
|
|
|
194
219
|
// Set up periodic cleanup of stale streams (every 10 minutes)
|
|
195
|
-
setInterval(
|
|
196
|
-
|
|
197
|
-
|
|
220
|
+
setInterval(
|
|
221
|
+
() => {
|
|
222
|
+
activeStreams.cleanupStaleStreams();
|
|
223
|
+
},
|
|
224
|
+
10 * 60 * 1000
|
|
225
|
+
);
|
|
198
226
|
|
|
199
227
|
@ObjectType()
|
|
200
228
|
class ReattachConversationResponse {
|
|
@@ -283,11 +311,11 @@ export class CycleDetailsType {
|
|
|
283
311
|
/** Unique identifier for the learning cycle */
|
|
284
312
|
@Field(() => String)
|
|
285
313
|
LearningCycleId: string;
|
|
286
|
-
|
|
314
|
+
|
|
287
315
|
/** ISO timestamp when the cycle started */
|
|
288
316
|
@Field(() => String)
|
|
289
317
|
StartTime: string;
|
|
290
|
-
|
|
318
|
+
|
|
291
319
|
/** Duration of the cycle in minutes */
|
|
292
320
|
@Field(() => Number)
|
|
293
321
|
RunningForMinutes: number;
|
|
@@ -302,15 +330,15 @@ export class RunningOrganizationType {
|
|
|
302
330
|
/** Identifier of the organization running the cycle */
|
|
303
331
|
@Field(() => String)
|
|
304
332
|
OrganizationId: string;
|
|
305
|
-
|
|
333
|
+
|
|
306
334
|
/** Unique identifier for the learning cycle */
|
|
307
335
|
@Field(() => String)
|
|
308
336
|
LearningCycleId: string;
|
|
309
|
-
|
|
337
|
+
|
|
310
338
|
/** ISO timestamp when the cycle started */
|
|
311
339
|
@Field(() => String)
|
|
312
340
|
StartTime: string;
|
|
313
|
-
|
|
341
|
+
|
|
314
342
|
/** Duration the cycle has been running in minutes */
|
|
315
343
|
@Field(() => Number)
|
|
316
344
|
RunningForMinutes: number;
|
|
@@ -325,11 +353,11 @@ export class LearningCycleStatusType {
|
|
|
325
353
|
/** Whether the scheduler process is currently active */
|
|
326
354
|
@Field(() => Boolean)
|
|
327
355
|
IsSchedulerRunning: boolean;
|
|
328
|
-
|
|
356
|
+
|
|
329
357
|
/** ISO timestamp of the last time the scheduler ran a cycle */
|
|
330
358
|
@Field(() => String, { nullable: true })
|
|
331
359
|
LastRunTime: string;
|
|
332
|
-
|
|
360
|
+
|
|
333
361
|
/** List of organizations that are currently running learning cycles */
|
|
334
362
|
@Field(() => [RunningOrganizationType], { nullable: true })
|
|
335
363
|
RunningOrganizations: RunningOrganizationType[];
|
|
@@ -344,15 +372,15 @@ export class StopLearningCycleResultType {
|
|
|
344
372
|
/** Whether the stop operation succeeded */
|
|
345
373
|
@Field(() => Boolean)
|
|
346
374
|
Success: boolean;
|
|
347
|
-
|
|
375
|
+
|
|
348
376
|
/** Descriptive message about the result of the stop operation */
|
|
349
377
|
@Field(() => String)
|
|
350
378
|
Message: string;
|
|
351
|
-
|
|
379
|
+
|
|
352
380
|
/** Whether the cycle was actually running when the stop was attempted */
|
|
353
381
|
@Field(() => Boolean)
|
|
354
382
|
WasRunning: boolean;
|
|
355
|
-
|
|
383
|
+
|
|
356
384
|
/** Details about the cycle that was stopped (if any) */
|
|
357
385
|
@Field(() => CycleDetailsType, { nullable: true })
|
|
358
386
|
CycleDetails: CycleDetailsType;
|
|
@@ -379,7 +407,7 @@ export class StopLearningCycleResultType {
|
|
|
379
407
|
// // LogStatus('Skip AI Learning Cycles not enabled in configuration');
|
|
380
408
|
// return;
|
|
381
409
|
// }
|
|
382
|
-
|
|
410
|
+
|
|
383
411
|
// // Check if we have a valid endpoint when cycles are enabled
|
|
384
412
|
// const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
|
|
385
413
|
// (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
|
|
@@ -387,19 +415,18 @@ export class StopLearningCycleResultType {
|
|
|
387
415
|
// LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no Learning Cycle API endpoint is configured');
|
|
388
416
|
// return;
|
|
389
417
|
// }
|
|
390
|
-
|
|
418
|
+
|
|
391
419
|
// const dataSources = event.args.dataSources;
|
|
392
420
|
// if (dataSources && dataSources.length > 0) {
|
|
393
421
|
// // Initialize the scheduler
|
|
394
422
|
// const scheduler = LearningCycleScheduler.Instance;
|
|
395
|
-
|
|
423
|
+
|
|
396
424
|
// // Set the data sources for the scheduler
|
|
397
425
|
// scheduler.setDataSources(dataSources);
|
|
398
|
-
|
|
426
|
+
|
|
399
427
|
// // Default is 60 minutes, if the interval is not set in the config, use 60 minutes
|
|
400
428
|
// const interval = skipConfigInfo.learningCycleIntervalInMinutes ?? 60;
|
|
401
429
|
|
|
402
|
-
|
|
403
430
|
// if (skipConfigInfo.learningCycleRunUponStartup) {
|
|
404
431
|
// // If configured to run immediately, run the learning cycle
|
|
405
432
|
// LogStatus('Skip API Learning Cycle: Run Upon Startup is enabled, running learning cycle immediately');
|
|
@@ -439,32 +466,32 @@ export class StopLearningCycleResultType {
|
|
|
439
466
|
*/
|
|
440
467
|
type BaseSkipRequest = {
|
|
441
468
|
/** Entity metadata to send to Skip */
|
|
442
|
-
entities: SkipEntityInfo[]
|
|
469
|
+
entities: SkipEntityInfo[];
|
|
443
470
|
/** Query metadata to send to Skip */
|
|
444
|
-
queries: SkipQueryInfo[]
|
|
471
|
+
queries: SkipQueryInfo[];
|
|
445
472
|
/** Agent notes to send to Skip */
|
|
446
|
-
notes: SkipAPIAgentNote[]
|
|
473
|
+
notes: SkipAPIAgentNote[];
|
|
447
474
|
/** Note type definitions to send to Skip */
|
|
448
|
-
noteTypes: SkipAPIAgentNoteType[]
|
|
475
|
+
noteTypes: SkipAPIAgentNoteType[];
|
|
449
476
|
/** Agent requests to send to Skip */
|
|
450
|
-
requests: SkipAPIAgentRequest[]
|
|
477
|
+
requests: SkipAPIAgentRequest[];
|
|
451
478
|
/** Access token for authorizing Skip to call back to MemberJunction */
|
|
452
|
-
accessToken: GetDataAccessToken
|
|
479
|
+
accessToken: GetDataAccessToken;
|
|
453
480
|
/** Organization identifier */
|
|
454
|
-
organizationID: string
|
|
481
|
+
organizationID: string;
|
|
455
482
|
/** Additional organization-specific information */
|
|
456
|
-
organizationInfo: any
|
|
483
|
+
organizationInfo: any;
|
|
457
484
|
/** API keys for various AI services to be used by Skip */
|
|
458
|
-
apiKeys: SkipAPIRequestAPIKey[]
|
|
485
|
+
apiKeys: SkipAPIRequestAPIKey[];
|
|
459
486
|
/** URL of the calling server for callback purposes */
|
|
460
|
-
callingServerURL: string
|
|
487
|
+
callingServerURL: string;
|
|
461
488
|
/** API key for the calling server */
|
|
462
|
-
callingServerAPIKey: string
|
|
489
|
+
callingServerAPIKey: string;
|
|
463
490
|
/** Access token for the calling server */
|
|
464
|
-
callingServerAccessToken: string
|
|
491
|
+
callingServerAccessToken: string;
|
|
465
492
|
/** Email of the user making the request */
|
|
466
|
-
userEmail: string
|
|
467
|
-
}
|
|
493
|
+
userEmail: string;
|
|
494
|
+
};
|
|
468
495
|
/**
|
|
469
496
|
* Resolver for Skip AI interactions
|
|
470
497
|
* Handles conversations with Skip, learning cycles, and related operations.
|
|
@@ -474,14 +501,14 @@ type BaseSkipRequest = {
|
|
|
474
501
|
export class AskSkipResolver {
|
|
475
502
|
/** Default name for new conversations */
|
|
476
503
|
private static _defaultNewChatName = 'New Chat';
|
|
477
|
-
|
|
504
|
+
|
|
478
505
|
/** Maximum number of historical messages to include in a conversation context */
|
|
479
506
|
private static _maxHistoricalMessages = 30;
|
|
480
507
|
|
|
481
508
|
/**
|
|
482
509
|
* Handles a chat interaction with Skip about a specific data record
|
|
483
510
|
* Allows users to ask questions about a particular entity record
|
|
484
|
-
*
|
|
511
|
+
*
|
|
485
512
|
* @param UserQuestion The question or message from the user
|
|
486
513
|
* @param ConversationId ID of an existing conversation, or empty for a new conversation
|
|
487
514
|
* @param EntityName The name of the entity the record belongs to
|
|
@@ -509,11 +536,7 @@ export class AskSkipResolver {
|
|
|
509
536
|
// now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
|
|
510
537
|
let messages: SkipMessage[] = [];
|
|
511
538
|
if (ConversationId && ConversationId.length > 0) {
|
|
512
|
-
messages = await this.LoadConversationDetailsIntoSkipMessages(
|
|
513
|
-
dataSource,
|
|
514
|
-
ConversationId,
|
|
515
|
-
AskSkipResolver._maxHistoricalMessages
|
|
516
|
-
);
|
|
539
|
+
messages = await this.LoadConversationDetailsIntoSkipMessages(dataSource, ConversationId, AskSkipResolver._maxHistoricalMessages);
|
|
517
540
|
}
|
|
518
541
|
|
|
519
542
|
const md = GetReadWriteProvider(providers);
|
|
@@ -555,7 +578,20 @@ export class AskSkipResolver {
|
|
|
555
578
|
}
|
|
556
579
|
}
|
|
557
580
|
|
|
558
|
-
const input = await this.buildSkipChatAPIRequest(
|
|
581
|
+
const input = await this.buildSkipChatAPIRequest(
|
|
582
|
+
messages,
|
|
583
|
+
ConversationId,
|
|
584
|
+
dataContext,
|
|
585
|
+
'chat_with_a_record',
|
|
586
|
+
false,
|
|
587
|
+
false,
|
|
588
|
+
false,
|
|
589
|
+
false,
|
|
590
|
+
user,
|
|
591
|
+
dataSource,
|
|
592
|
+
false,
|
|
593
|
+
false
|
|
594
|
+
);
|
|
559
595
|
messages.push({
|
|
560
596
|
content: UserQuestion,
|
|
561
597
|
role: 'user',
|
|
@@ -568,7 +604,7 @@ export class AskSkipResolver {
|
|
|
568
604
|
// /**
|
|
569
605
|
// * Executes a Skip learning cycle
|
|
570
606
|
// * Learning cycles allow Skip to analyze conversations and improve its knowledge and capabilities
|
|
571
|
-
// *
|
|
607
|
+
// *
|
|
572
608
|
// * @param dataSource Database connection
|
|
573
609
|
// * @param userPayload Information about the authenticated user
|
|
574
610
|
// * @param ForceEntityRefresh Whether to force a refresh of entity metadata
|
|
@@ -591,7 +627,7 @@ export class AskSkipResolver {
|
|
|
591
627
|
// requestChanges: []
|
|
592
628
|
// };
|
|
593
629
|
// }
|
|
594
|
-
|
|
630
|
+
|
|
595
631
|
// // Check if we have a valid endpoint when cycles are enabled
|
|
596
632
|
// const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
|
|
597
633
|
// (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
|
|
@@ -605,20 +641,20 @@ export class AskSkipResolver {
|
|
|
605
641
|
// requestChanges: []
|
|
606
642
|
// };
|
|
607
643
|
// }
|
|
608
|
-
|
|
644
|
+
|
|
609
645
|
// const startTime = new Date();
|
|
610
646
|
// // First, get the user from the cache
|
|
611
647
|
// const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
|
|
612
648
|
// if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
613
649
|
|
|
614
650
|
// // if already configured this does nothing, just makes sure we're configured
|
|
615
|
-
// await AIEngine.Instance.Config(false, user);
|
|
651
|
+
// await AIEngine.Instance.Config(false, user);
|
|
616
652
|
|
|
617
653
|
// // Check if this organization is already running a learning cycle using their organization ID
|
|
618
654
|
// const organizationId = skipConfigInfo.orgID;
|
|
619
655
|
// const scheduler = LearningCycleScheduler.Instance;
|
|
620
656
|
// const runningStatus = scheduler.isOrganizationRunningCycle(organizationId);
|
|
621
|
-
|
|
657
|
+
|
|
622
658
|
// if (runningStatus.isRunning) {
|
|
623
659
|
// LogStatus(`Learning cycle already in progress for organization ${organizationId}, started at ${runningStatus.startTime.toISOString()}`);
|
|
624
660
|
// return {
|
|
@@ -659,7 +695,7 @@ export class AskSkipResolver {
|
|
|
659
695
|
|
|
660
696
|
// // Register this organization as running a learning cycle
|
|
661
697
|
// scheduler.registerRunningCycle(organizationId, learningCycleId);
|
|
662
|
-
|
|
698
|
+
|
|
663
699
|
// try {
|
|
664
700
|
// // Build the request to Skip learning API
|
|
665
701
|
// LogStatus(`Building Skip Learning API request`);
|
|
@@ -699,29 +735,29 @@ export class AskSkipResolver {
|
|
|
699
735
|
// if (!(await learningCycleEntity.Save())) {
|
|
700
736
|
// LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
|
|
701
737
|
// }
|
|
702
|
-
|
|
738
|
+
|
|
703
739
|
// return response;
|
|
704
740
|
// }
|
|
705
|
-
// }
|
|
741
|
+
// }
|
|
706
742
|
// catch (error) {
|
|
707
743
|
// // Make sure to update the learning cycle record as failed
|
|
708
744
|
// learningCycleEntity.Status = 'Failed';
|
|
709
745
|
// learningCycleEntity.EndedAt = new Date();
|
|
710
|
-
|
|
746
|
+
|
|
711
747
|
// try {
|
|
712
748
|
// await learningCycleEntity.Save();
|
|
713
|
-
// }
|
|
749
|
+
// }
|
|
714
750
|
// catch (saveError) {
|
|
715
751
|
// LogError(`Failed to update learning cycle record after error: ${saveError}`);
|
|
716
752
|
// }
|
|
717
|
-
|
|
753
|
+
|
|
718
754
|
// // Re-throw the original error
|
|
719
755
|
// throw error;
|
|
720
756
|
// }
|
|
721
757
|
// finally {
|
|
722
758
|
// // Unregister the cycle/organizationId safely
|
|
723
759
|
// try {
|
|
724
|
-
// scheduler.unregisterRunningCycle(organizationId);
|
|
760
|
+
// scheduler.unregisterRunningCycle(organizationId);
|
|
725
761
|
// }
|
|
726
762
|
// catch (error) {
|
|
727
763
|
// LogError(`Failed to unregister organization ${organizationId} from running cycles: ${error}`);
|
|
@@ -732,7 +768,7 @@ export class AskSkipResolver {
|
|
|
732
768
|
// /**
|
|
733
769
|
// * Handles the HTTP POST request to the Skip learning cycle API
|
|
734
770
|
// * Sends the learning cycle request and processes the response
|
|
735
|
-
// *
|
|
771
|
+
// *
|
|
736
772
|
// * @param input The learning cycle request payload
|
|
737
773
|
// * @param user User context for the request
|
|
738
774
|
// * @param learningCycleId ID of the current learning cycle
|
|
@@ -740,8 +776,8 @@ export class AskSkipResolver {
|
|
|
740
776
|
// * @returns Response from the Skip learning cycle API
|
|
741
777
|
// */
|
|
742
778
|
// protected async handleSimpleSkipLearningPostRequest(
|
|
743
|
-
// input: SkipAPILearningCycleRequest,
|
|
744
|
-
// user: UserInfo,
|
|
779
|
+
// input: SkipAPILearningCycleRequest,
|
|
780
|
+
// user: UserInfo,
|
|
745
781
|
// learningCycleId: string,
|
|
746
782
|
// agentID: string,
|
|
747
783
|
// userPayload: UserPayload
|
|
@@ -773,7 +809,7 @@ export class AskSkipResolver {
|
|
|
773
809
|
// // if (apiResponse.requestChanges && apiResponse.requestChanges.length > 0) {
|
|
774
810
|
// // await this.processLearningCycleRequestChanges(apiResponse.requestChanges, user);
|
|
775
811
|
// // }
|
|
776
|
-
|
|
812
|
+
|
|
777
813
|
// return apiResponse;
|
|
778
814
|
// } else {
|
|
779
815
|
// return {
|
|
@@ -791,7 +827,7 @@ export class AskSkipResolver {
|
|
|
791
827
|
/**
|
|
792
828
|
* Handles the HTTP POST request to the Skip chat API
|
|
793
829
|
* Sends the chat request and processes the response
|
|
794
|
-
*
|
|
830
|
+
*
|
|
795
831
|
* @param input The chat request payload
|
|
796
832
|
* @param convoEntity The conversation entity object
|
|
797
833
|
* @param convoDetailEntity The conversation detail entity object
|
|
@@ -817,9 +853,10 @@ export class AskSkipResolver {
|
|
|
817
853
|
if (response && response.length > 0) {
|
|
818
854
|
// the last object in the response array is the final response from the Skip API
|
|
819
855
|
const apiResponse = <SkipAPIResponse>response[response.length - 1].value;
|
|
820
|
-
const AIMessageConversationDetailID =
|
|
821
|
-
|
|
822
|
-
|
|
856
|
+
const AIMessageConversationDetailID =
|
|
857
|
+
createAIMessageConversationDetail && convoEntity
|
|
858
|
+
? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user, userPayload)
|
|
859
|
+
: '';
|
|
823
860
|
// const apiResponse = <SkipAPIResponse>response.data;
|
|
824
861
|
LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
|
|
825
862
|
return {
|
|
@@ -836,7 +873,7 @@ export class AskSkipResolver {
|
|
|
836
873
|
if (convoEntity) {
|
|
837
874
|
await this.setConversationStatus(convoEntity, 'Available', userPayload);
|
|
838
875
|
}
|
|
839
|
-
|
|
876
|
+
|
|
840
877
|
return {
|
|
841
878
|
Success: false,
|
|
842
879
|
Status: 'Error',
|
|
@@ -852,152 +889,157 @@ export class AskSkipResolver {
|
|
|
852
889
|
if (convoEntity) {
|
|
853
890
|
await this.setConversationStatus(convoEntity, 'Available', userPayload);
|
|
854
891
|
}
|
|
855
|
-
|
|
892
|
+
|
|
856
893
|
// Log the error for debugging
|
|
857
894
|
LogError(`Error in handleSimpleSkipChatPostRequest: ${error}`);
|
|
858
|
-
|
|
895
|
+
|
|
859
896
|
// Re-throw the error to propagate it up the stack
|
|
860
897
|
throw error;
|
|
861
898
|
}
|
|
862
899
|
}
|
|
863
900
|
|
|
864
|
-
// /**
|
|
865
|
-
// * Processes note changes received from the Skip API learning cycle
|
|
866
|
-
// * Applies changes to agent notes based on the learning cycle response
|
|
867
|
-
// *
|
|
868
|
-
// * @param noteChanges Changes to agent notes
|
|
869
|
-
// * @param agentID ID of the Skip agent
|
|
870
|
-
// * @param user User context for the request
|
|
871
|
-
// * @returns Promise that resolves when processing is complete
|
|
872
|
-
// */
|
|
873
|
-
// protected async processLearningCycleNoteChanges(
|
|
874
|
-
// noteChanges: SkipLearningCycleNoteChange[],
|
|
875
|
-
// agentID: string,
|
|
876
|
-
// user: UserInfo,
|
|
877
|
-
// userPayload: UserPayload
|
|
878
|
-
// ): Promise<void> {
|
|
879
|
-
// const md = new Metadata();
|
|
880
|
-
|
|
881
|
-
// // Filter out any operations on "Human" notes
|
|
882
|
-
// const validNoteChanges = noteChanges.filter(change => {
|
|
883
|
-
// // Check if the note is of type "Human"
|
|
884
|
-
// if (change.note.agentNoteType === "Human") {
|
|
885
|
-
// LogStatus(`WARNING: Ignoring ${change.changeType} operation on Human note with ID ${change.note.id}. Human notes cannot be modified by the
|
|
886
|
-
// learning cycle.`);
|
|
887
|
-
// return false;
|
|
888
|
-
// }
|
|
889
|
-
// return true;
|
|
890
|
-
// });
|
|
891
|
-
|
|
892
|
-
// // Process all valid note changes in parallel
|
|
893
|
-
// await Promise.all(validNoteChanges.map(async (change) => {
|
|
894
|
-
// try {
|
|
895
|
-
// if (change.changeType === 'add' || change.changeType === 'update') {
|
|
896
|
-
// await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
|
|
897
|
-
// } else if (change.changeType === 'delete') {
|
|
898
|
-
// await this.processDeleteSkipNote(change, user, userPayload);
|
|
899
|
-
// }
|
|
900
|
-
// } catch (e) {
|
|
901
|
-
// LogError(`Error processing note change: ${e}`);
|
|
902
|
-
// }
|
|
903
|
-
// }));
|
|
904
|
-
// }
|
|
901
|
+
// /**
|
|
902
|
+
// * Processes note changes received from the Skip API learning cycle
|
|
903
|
+
// * Applies changes to agent notes based on the learning cycle response
|
|
904
|
+
// *
|
|
905
|
+
// * @param noteChanges Changes to agent notes
|
|
906
|
+
// * @param agentID ID of the Skip agent
|
|
907
|
+
// * @param user User context for the request
|
|
908
|
+
// * @returns Promise that resolves when processing is complete
|
|
909
|
+
// */
|
|
910
|
+
// protected async processLearningCycleNoteChanges(
|
|
911
|
+
// noteChanges: SkipLearningCycleNoteChange[],
|
|
912
|
+
// agentID: string,
|
|
913
|
+
// user: UserInfo,
|
|
914
|
+
// userPayload: UserPayload
|
|
915
|
+
// ): Promise<void> {
|
|
916
|
+
// const md = new Metadata();
|
|
917
|
+
|
|
918
|
+
// // Filter out any operations on "Human" notes
|
|
919
|
+
// const validNoteChanges = noteChanges.filter(change => {
|
|
920
|
+
// // Check if the note is of type "Human"
|
|
921
|
+
// if (change.note.agentNoteType === "Human") {
|
|
922
|
+
// LogStatus(`WARNING: Ignoring ${change.changeType} operation on Human note with ID ${change.note.id}. Human notes cannot be modified by the
|
|
923
|
+
// learning cycle.`);
|
|
924
|
+
// return false;
|
|
925
|
+
// }
|
|
926
|
+
// return true;
|
|
927
|
+
// });
|
|
905
928
|
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
// const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
|
|
920
|
-
|
|
921
|
-
// if (change.changeType === 'update') {
|
|
922
|
-
// // Load existing note
|
|
923
|
-
// const loadResult = await noteEntity.Load(change.note.id);
|
|
924
|
-
// if (!loadResult) {
|
|
925
|
-
// LogError(`Could not load note with ID ${change.note.id}`);
|
|
926
|
-
// return false;
|
|
927
|
-
// }
|
|
928
|
-
// } else {
|
|
929
|
-
// // Create a new note
|
|
930
|
-
// noteEntity.NewRecord();
|
|
931
|
-
// noteEntity.AgentID = agentID;
|
|
932
|
-
// }
|
|
933
|
-
// noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI'); // always set to AI
|
|
934
|
-
// noteEntity.Note = change.note.note;
|
|
935
|
-
// noteEntity.Type = change.note.type;
|
|
929
|
+
// // Process all valid note changes in parallel
|
|
930
|
+
// await Promise.all(validNoteChanges.map(async (change) => {
|
|
931
|
+
// try {
|
|
932
|
+
// if (change.changeType === 'add' || change.changeType === 'update') {
|
|
933
|
+
// await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
|
|
934
|
+
// } else if (change.changeType === 'delete') {
|
|
935
|
+
// await this.processDeleteSkipNote(change, user, userPayload);
|
|
936
|
+
// }
|
|
937
|
+
// } catch (e) {
|
|
938
|
+
// LogError(`Error processing note change: ${e}`);
|
|
939
|
+
// }
|
|
940
|
+
// }));
|
|
941
|
+
// }
|
|
936
942
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
943
|
+
// /**
|
|
944
|
+
// * Processes an add or update operation for a Skip agent note
|
|
945
|
+
// * Creates a new note or updates an existing one based on the change type
|
|
946
|
+
// *
|
|
947
|
+
// * @param change The note change information
|
|
948
|
+
// * @param agentID ID of the Skip agent
|
|
949
|
+
// * @param user User context for the operation
|
|
950
|
+
// * @returns Whether the operation was successful
|
|
951
|
+
// */
|
|
952
|
+
// protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
|
|
953
|
+
// try {
|
|
954
|
+
// // Get the note entity object
|
|
955
|
+
// const md = new Metadata();
|
|
956
|
+
// const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
|
|
957
|
+
|
|
958
|
+
// if (change.changeType === 'update') {
|
|
959
|
+
// // Load existing note
|
|
960
|
+
// const loadResult = await noteEntity.Load(change.note.id);
|
|
961
|
+
// if (!loadResult) {
|
|
962
|
+
// LogError(`Could not load note with ID ${change.note.id}`);
|
|
963
|
+
// return false;
|
|
964
|
+
// }
|
|
965
|
+
// } else {
|
|
966
|
+
// // Create a new note
|
|
967
|
+
// noteEntity.NewRecord();
|
|
968
|
+
// noteEntity.AgentID = agentID;
|
|
969
|
+
// }
|
|
970
|
+
// noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI'); // always set to AI
|
|
971
|
+
// noteEntity.Note = change.note.note;
|
|
972
|
+
// noteEntity.Type = change.note.type;
|
|
940
973
|
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
// return false;
|
|
945
|
-
// }
|
|
974
|
+
// if (change.note.type === 'User') {
|
|
975
|
+
// noteEntity.UserID = change.note.userId;
|
|
976
|
+
// }
|
|
946
977
|
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
//
|
|
950
|
-
//
|
|
951
|
-
//
|
|
952
|
-
// }
|
|
978
|
+
// // Save the note
|
|
979
|
+
// if (!(await noteEntity.Save())) {
|
|
980
|
+
// LogError(`Error saving AI Agent Note: ${noteEntity.LatestResult.Error}`);
|
|
981
|
+
// return false;
|
|
982
|
+
// }
|
|
953
983
|
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
|
|
961
|
-
//
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
//
|
|
965
|
-
//
|
|
966
|
-
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
//
|
|
976
|
-
|
|
977
|
-
//
|
|
978
|
-
//
|
|
979
|
-
// return false;
|
|
980
|
-
// }
|
|
981
|
-
|
|
982
|
-
// //
|
|
983
|
-
// if (
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
//
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
//
|
|
984
|
+
// return true;
|
|
985
|
+
// } catch (e) {
|
|
986
|
+
// LogError(`Error processing note change: ${e}`);
|
|
987
|
+
// return false;
|
|
988
|
+
// }
|
|
989
|
+
// }
|
|
990
|
+
|
|
991
|
+
// /**
|
|
992
|
+
// * Processes a delete operation for a Skip agent note
|
|
993
|
+
// * Removes the specified note from the database
|
|
994
|
+
// *
|
|
995
|
+
// * @param change The note change information
|
|
996
|
+
// * @param user User context for the operation
|
|
997
|
+
// * @returns Whether the deletion was successful
|
|
998
|
+
// */
|
|
999
|
+
// protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
|
|
1000
|
+
// // Get the note entity object
|
|
1001
|
+
// const md = new Metadata();
|
|
1002
|
+
// const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
|
|
1003
|
+
|
|
1004
|
+
// // Load the note first
|
|
1005
|
+
// const loadResult = await noteEntity.Load(change.note.id);
|
|
1006
|
+
|
|
1007
|
+
// if (!loadResult) {
|
|
1008
|
+
// LogError(`Could not load note with ID ${change.note.id} for deletion`);
|
|
1009
|
+
// return false;
|
|
1010
|
+
// }
|
|
1011
|
+
|
|
1012
|
+
// // Double-check if the loaded note is of type "Human"
|
|
1013
|
+
// if (change.note.agentNoteType === "Human") {
|
|
1014
|
+
// LogStatus(`WARNING: Ignoring delete operation on Human note with ID ${change.note.id}. Human notes cannot be deleted by the learning
|
|
1015
|
+
// cycle.`);
|
|
1016
|
+
// return false;
|
|
1017
|
+
// }
|
|
1018
|
+
|
|
1019
|
+
// // Proceed with deletion
|
|
1020
|
+
// if (!(await noteEntity.Delete())) {
|
|
1021
|
+
// LogError(`Error deleting AI Agent Note: ${noteEntity.LatestResult.Error}`);
|
|
1022
|
+
// return false;
|
|
1023
|
+
// }
|
|
1024
|
+
|
|
1025
|
+
// return true;
|
|
1026
|
+
// }
|
|
990
1027
|
|
|
991
1028
|
/**
|
|
992
1029
|
* Creates a conversation detail entry for an AI message
|
|
993
1030
|
* Stores the AI response in the conversation history
|
|
994
|
-
*
|
|
1031
|
+
*
|
|
995
1032
|
* @param apiResponse The response from the Skip API
|
|
996
1033
|
* @param conversationID ID of the conversation
|
|
997
1034
|
* @param user User context for the operation
|
|
998
1035
|
* @returns ID of the created conversation detail, or empty string if creation failed
|
|
999
1036
|
*/
|
|
1000
|
-
protected async CreateAIMessageConversationDetail(
|
|
1037
|
+
protected async CreateAIMessageConversationDetail(
|
|
1038
|
+
apiResponse: SkipAPIResponse,
|
|
1039
|
+
conversationID: string,
|
|
1040
|
+
user: UserInfo,
|
|
1041
|
+
userPayload: UserPayload
|
|
1042
|
+
): Promise<string> {
|
|
1001
1043
|
const md = new Metadata();
|
|
1002
1044
|
const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
|
|
1003
1045
|
convoDetailEntityAI.NewRecord();
|
|
@@ -1023,7 +1065,7 @@ export class AskSkipResolver {
|
|
|
1023
1065
|
/**
|
|
1024
1066
|
* Builds the base Skip API request with common fields and data
|
|
1025
1067
|
* Creates the foundation for both chat and learning cycle requests
|
|
1026
|
-
*
|
|
1068
|
+
*
|
|
1027
1069
|
* @param contextUser The user making the request
|
|
1028
1070
|
* @param dataSource The data source to use
|
|
1029
1071
|
* @param includeEntities Whether to include entities in the request
|
|
@@ -1052,7 +1094,7 @@ export class AskSkipResolver {
|
|
|
1052
1094
|
const queries = includeQueries ? this.BuildSkipQueries() : [];
|
|
1053
1095
|
//const {notes, noteTypes} = includeNotes ? await this.BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) : {notes: [], noteTypes: []};
|
|
1054
1096
|
const requests = includeRequests ? await this.BuildSkipRequests(contextUser) : [];
|
|
1055
|
-
|
|
1097
|
+
|
|
1056
1098
|
// Setup access token if needed
|
|
1057
1099
|
let accessToken: GetDataAccessToken;
|
|
1058
1100
|
if (includeCallBackKeyAndAccessToken) {
|
|
@@ -1061,39 +1103,35 @@ export class AskSkipResolver {
|
|
|
1061
1103
|
userEmail: contextUser.Email,
|
|
1062
1104
|
userName: contextUser.Name,
|
|
1063
1105
|
userID: contextUser.ID,
|
|
1064
|
-
...additionalTokenInfo
|
|
1106
|
+
...additionalTokenInfo,
|
|
1065
1107
|
};
|
|
1066
|
-
|
|
1067
|
-
accessToken = registerAccessToken(
|
|
1068
|
-
undefined,
|
|
1069
|
-
1000 * 60 * 10 /*10 minutes*/,
|
|
1070
|
-
tokenInfo
|
|
1071
|
-
);
|
|
1108
|
+
|
|
1109
|
+
accessToken = registerAccessToken(undefined, 1000 * 60 * 10 /*10 minutes*/, tokenInfo);
|
|
1072
1110
|
}
|
|
1073
|
-
|
|
1111
|
+
|
|
1074
1112
|
return {
|
|
1075
1113
|
entities,
|
|
1076
1114
|
queries,
|
|
1077
1115
|
notes: undefined,
|
|
1078
1116
|
noteTypes: undefined,
|
|
1079
1117
|
userEmail: contextUser.Email,
|
|
1080
|
-
requests,
|
|
1118
|
+
requests,
|
|
1081
1119
|
accessToken,
|
|
1082
1120
|
organizationID: skipConfigInfo.orgID,
|
|
1083
1121
|
organizationInfo: configInfo?.askSkip?.organizationInfo,
|
|
1084
1122
|
apiKeys: this.buildSkipAPIKeys(),
|
|
1085
1123
|
// Favors public URL for conciseness or when behind a proxy for local development
|
|
1086
1124
|
// otherwise uses base URL and GraphQL port/path from configuration
|
|
1087
|
-
callingServerURL: accessToken ?
|
|
1125
|
+
callingServerURL: accessToken ? publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}` : undefined,
|
|
1088
1126
|
callingServerAPIKey: accessToken ? callbackAPIKey : undefined,
|
|
1089
|
-
callingServerAccessToken: accessToken ? accessToken.Token : undefined
|
|
1127
|
+
callingServerAccessToken: accessToken ? accessToken.Token : undefined,
|
|
1090
1128
|
};
|
|
1091
1129
|
}
|
|
1092
1130
|
|
|
1093
1131
|
/**
|
|
1094
1132
|
* Builds the learning API request for Skip
|
|
1095
1133
|
* Creates a request specific to the learning cycle operation
|
|
1096
|
-
*
|
|
1134
|
+
*
|
|
1097
1135
|
* @param learningCycleId ID of the current learning cycle
|
|
1098
1136
|
* @param lastLearningCycleDate Date of the last completed learning cycle
|
|
1099
1137
|
* @param includeEntities Whether to include entities in the request
|
|
@@ -1130,7 +1168,7 @@ export class AskSkipResolver {
|
|
|
1130
1168
|
forceEntitiesRefresh,
|
|
1131
1169
|
includeCallBackKeyAndAccessToken
|
|
1132
1170
|
);
|
|
1133
|
-
|
|
1171
|
+
|
|
1134
1172
|
// Get data specific to learning cycle
|
|
1135
1173
|
const newConversations = await this.BuildSkipLearningCycleNewConversations(lastLearningCycleDate, dataSource, contextUser);
|
|
1136
1174
|
|
|
@@ -1146,7 +1184,7 @@ export class AskSkipResolver {
|
|
|
1146
1184
|
notes: baseRequest.notes,
|
|
1147
1185
|
noteTypes: baseRequest.noteTypes,
|
|
1148
1186
|
requests: baseRequest.requests,
|
|
1149
|
-
apiKeys: baseRequest.apiKeys
|
|
1187
|
+
apiKeys: baseRequest.apiKeys,
|
|
1150
1188
|
};
|
|
1151
1189
|
|
|
1152
1190
|
return input;
|
|
@@ -1155,7 +1193,7 @@ export class AskSkipResolver {
|
|
|
1155
1193
|
/**
|
|
1156
1194
|
* Loads the conversations that have been updated or added since the last learning cycle
|
|
1157
1195
|
* These are used to train Skip and improve its understanding
|
|
1158
|
-
*
|
|
1196
|
+
*
|
|
1159
1197
|
* @param lastLearningCycleDate The date of the last learning cycle
|
|
1160
1198
|
* @param dataSource Database connection
|
|
1161
1199
|
* @param contextUser User context for the request
|
|
@@ -1170,31 +1208,35 @@ export class AskSkipResolver {
|
|
|
1170
1208
|
const rv = new RunView();
|
|
1171
1209
|
|
|
1172
1210
|
// Get all conversations with a conversation detail that has been updated (modified or added) since the last learning cycle
|
|
1173
|
-
const conversationsSinceLastLearningCycle = await rv.RunView<ConversationEntity>(
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1211
|
+
const conversationsSinceLastLearningCycle = await rv.RunView<ConversationEntity>(
|
|
1212
|
+
{
|
|
1213
|
+
EntityName: 'Conversations',
|
|
1214
|
+
ExtraFilter: `ID IN (SELECT ConversationID FROM __mj.vwConversationDetails WHERE __mj_UpdatedAt >= '${lastLearningCycleDate.toISOString()}')`,
|
|
1215
|
+
ResultType: 'entity_object',
|
|
1216
|
+
},
|
|
1217
|
+
contextUser
|
|
1218
|
+
);
|
|
1178
1219
|
|
|
1179
1220
|
if (!conversationsSinceLastLearningCycle.Success || conversationsSinceLastLearningCycle.Results.length === 0) {
|
|
1180
1221
|
return [];
|
|
1181
1222
|
}
|
|
1182
1223
|
|
|
1183
|
-
// Now we map the conversations to SkipConversations and return
|
|
1184
|
-
return await Promise.all(
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1224
|
+
// Now we map the conversations to SkipConversations and return
|
|
1225
|
+
return await Promise.all(
|
|
1226
|
+
conversationsSinceLastLearningCycle.Results.map(async (c) => {
|
|
1227
|
+
return {
|
|
1228
|
+
id: c.ID,
|
|
1229
|
+
name: c.Name,
|
|
1230
|
+
userId: c.UserID,
|
|
1231
|
+
user: c.User,
|
|
1232
|
+
description: c.Description,
|
|
1233
|
+
messages: await this.LoadConversationDetailsIntoSkipMessages(dataSource, c.ID),
|
|
1234
|
+
createdAt: c.__mj_CreatedAt,
|
|
1235
|
+
updatedAt: c.__mj_UpdatedAt,
|
|
1236
|
+
};
|
|
1237
|
+
})
|
|
1238
|
+
);
|
|
1239
|
+
} catch (e) {
|
|
1198
1240
|
LogError(`Error loading conversations since last learning cycle: ${e}`);
|
|
1199
1241
|
return [];
|
|
1200
1242
|
}
|
|
@@ -1203,13 +1245,11 @@ export class AskSkipResolver {
|
|
|
1203
1245
|
/**
|
|
1204
1246
|
* Builds an array of agent requests
|
|
1205
1247
|
* These are requests that have been made to the AI agent
|
|
1206
|
-
*
|
|
1248
|
+
*
|
|
1207
1249
|
* @param contextUser User context for loading the requests
|
|
1208
1250
|
* @returns Array of agent request objects
|
|
1209
1251
|
*/
|
|
1210
|
-
protected async BuildSkipRequests(
|
|
1211
|
-
contextUser: UserInfo
|
|
1212
|
-
): Promise<SkipAPIAgentRequest[]> {
|
|
1252
|
+
protected async BuildSkipRequests(contextUser: UserInfo): Promise<SkipAPIAgentRequest[]> {
|
|
1213
1253
|
try {
|
|
1214
1254
|
const md = new Metadata();
|
|
1215
1255
|
const requestEntity = await md.GetEntityObject<AIAgentRequestEntity>('AI Agent Requests', contextUser);
|
|
@@ -1229,13 +1269,12 @@ export class AskSkipResolver {
|
|
|
1229
1269
|
responseByUserId: r.ResponseByUserID,
|
|
1230
1270
|
responseByUser: r.ResponseByUser,
|
|
1231
1271
|
respondedAt: r.RespondedAt,
|
|
1232
|
-
comments: r.Comments,
|
|
1272
|
+
comments: r.Comments,
|
|
1233
1273
|
createdAt: r.__mj_CreatedAt,
|
|
1234
1274
|
updatedAt: r.__mj_UpdatedAt,
|
|
1235
1275
|
};
|
|
1236
1276
|
});
|
|
1237
1277
|
return requests;
|
|
1238
|
-
|
|
1239
1278
|
} catch (e) {
|
|
1240
1279
|
LogError(`Error loading requests: ${e}`);
|
|
1241
1280
|
return [];
|
|
@@ -1245,7 +1284,7 @@ export class AskSkipResolver {
|
|
|
1245
1284
|
/**
|
|
1246
1285
|
* Gets the date of the last complete learning cycle for the Skip agent
|
|
1247
1286
|
* Used to determine which data to include in the next learning cycle
|
|
1248
|
-
*
|
|
1287
|
+
*
|
|
1249
1288
|
* @param agentID ID of the Skip agent
|
|
1250
1289
|
* @param user User context for the query
|
|
1251
1290
|
* @returns Date of the last complete learning cycle, or epoch if none exists
|
|
@@ -1254,20 +1293,22 @@ export class AskSkipResolver {
|
|
|
1254
1293
|
const md = new Metadata();
|
|
1255
1294
|
const rv = new RunView();
|
|
1256
1295
|
|
|
1257
|
-
const lastLearningCycleRV = await rv.RunView<AIAgentLearningCycleEntity>(
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1296
|
+
const lastLearningCycleRV = await rv.RunView<AIAgentLearningCycleEntity>(
|
|
1297
|
+
{
|
|
1298
|
+
EntityName: 'AI Agent Learning Cycles',
|
|
1299
|
+
ExtraFilter: `AgentID = '${agentID}' AND Status = 'Complete'`,
|
|
1300
|
+
ResultType: 'entity_object',
|
|
1301
|
+
OrderBy: 'StartedAt DESC',
|
|
1302
|
+
MaxRows: 1,
|
|
1303
|
+
},
|
|
1304
|
+
user
|
|
1305
|
+
);
|
|
1264
1306
|
|
|
1265
1307
|
const lastLearningCycle = lastLearningCycleRV.Results[0];
|
|
1266
1308
|
|
|
1267
1309
|
if (lastLearningCycle) {
|
|
1268
1310
|
return lastLearningCycle.StartedAt;
|
|
1269
|
-
}
|
|
1270
|
-
else {
|
|
1311
|
+
} else {
|
|
1271
1312
|
// if no lerarning cycle found, return the epoch date
|
|
1272
1313
|
return new Date(0);
|
|
1273
1314
|
}
|
|
@@ -1276,7 +1317,7 @@ export class AskSkipResolver {
|
|
|
1276
1317
|
/**
|
|
1277
1318
|
* Builds the chat API request for Skip
|
|
1278
1319
|
* Creates a request specific to a chat interaction
|
|
1279
|
-
*
|
|
1320
|
+
*
|
|
1280
1321
|
* @param messages Array of messages in the conversation
|
|
1281
1322
|
* @param conversationId ID of the conversation
|
|
1282
1323
|
* @param dataContext Data context associated with the conversation
|
|
@@ -1310,7 +1351,7 @@ export class AskSkipResolver {
|
|
|
1310
1351
|
conversationId,
|
|
1311
1352
|
requestPhase,
|
|
1312
1353
|
};
|
|
1313
|
-
|
|
1354
|
+
|
|
1314
1355
|
// Get base request data
|
|
1315
1356
|
const baseRequest = await this.buildBaseSkipRequest(
|
|
1316
1357
|
contextUser,
|
|
@@ -1334,41 +1375,48 @@ export class AskSkipResolver {
|
|
|
1334
1375
|
conversationID: conversationId.toString(),
|
|
1335
1376
|
dataContext: <DataContext>CopyScalarsAndArrays(dataContext), // we are casting this to DataContext as we're pushing this to the Skip API, and we don't want to send the real DataContext object, just a copy of the scalar and array properties
|
|
1336
1377
|
requestPhase,
|
|
1337
|
-
artifacts: artifacts
|
|
1378
|
+
artifacts: artifacts,
|
|
1338
1379
|
};
|
|
1339
|
-
|
|
1380
|
+
|
|
1340
1381
|
return input;
|
|
1341
1382
|
}
|
|
1342
1383
|
|
|
1343
1384
|
/**
|
|
1344
1385
|
* Builds up an array of artifacts associated with a conversation
|
|
1345
1386
|
* Artifacts are content or documents generated during conversations
|
|
1346
|
-
*
|
|
1387
|
+
*
|
|
1347
1388
|
* @param contextUser User context for the query
|
|
1348
1389
|
* @param dataSource Database connection
|
|
1349
1390
|
* @param conversationId ID of the conversation
|
|
1350
1391
|
* @returns Array of artifacts associated with the conversation
|
|
1351
1392
|
*/
|
|
1352
|
-
protected async buildSkipAPIArtifacts(
|
|
1393
|
+
protected async buildSkipAPIArtifacts(
|
|
1394
|
+
contextUser: UserInfo,
|
|
1395
|
+
dataSource: mssql.ConnectionPool,
|
|
1396
|
+
conversationId: string
|
|
1397
|
+
): Promise<SkipAPIArtifact[]> {
|
|
1353
1398
|
const md = new Metadata();
|
|
1354
1399
|
const ei = md.EntityByName('MJ: Conversation Artifacts');
|
|
1355
1400
|
const rv = new RunView();
|
|
1356
|
-
const results = await rv.RunViews(
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1401
|
+
const results = await rv.RunViews(
|
|
1402
|
+
[
|
|
1403
|
+
{
|
|
1404
|
+
EntityName: 'MJ: Conversation Artifacts',
|
|
1405
|
+
ExtraFilter: `ConversationID='${conversationId}'`, // get artifacts linked to this convo
|
|
1406
|
+
OrderBy: '__mj_CreatedAt',
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
EntityName: 'MJ: Artifact Types', // get all artifact types
|
|
1410
|
+
OrderBy: 'Name',
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
EntityName: 'MJ: Conversation Artifact Versions',
|
|
1414
|
+
ExtraFilter: `ConversationArtifactID IN (SELECT ID FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationID='${conversationId}')`,
|
|
1415
|
+
OrderBy: 'ConversationArtifactID, __mj_CreatedAt',
|
|
1416
|
+
},
|
|
1417
|
+
],
|
|
1418
|
+
contextUser
|
|
1419
|
+
);
|
|
1372
1420
|
if (results && results.length > 0 && results.every((r) => r.Success)) {
|
|
1373
1421
|
const types: SkipAPIArtifactType[] = results[1].Results.map((a: ArtifactTypeEntity) => {
|
|
1374
1422
|
const retVal: SkipAPIArtifactType = {
|
|
@@ -1378,13 +1426,13 @@ export class AskSkipResolver {
|
|
|
1378
1426
|
contentType: a.ContentType,
|
|
1379
1427
|
enabled: a.IsEnabled,
|
|
1380
1428
|
createdAt: a.__mj_CreatedAt,
|
|
1381
|
-
updatedAt: a.__mj_UpdatedAt
|
|
1382
|
-
}
|
|
1429
|
+
updatedAt: a.__mj_UpdatedAt,
|
|
1430
|
+
};
|
|
1383
1431
|
return retVal;
|
|
1384
1432
|
});
|
|
1385
1433
|
const allConvoArtifacts = results[0].Results.map((a: ConversationArtifactEntity) => {
|
|
1386
1434
|
const rawVersions: ConversationArtifactVersionEntity[] = results[2].Results as ConversationArtifactVersionEntity[];
|
|
1387
|
-
const thisArtifactsVersions = rawVersions.filter(rv => rv.ConversationArtifactID === a.ID);
|
|
1435
|
+
const thisArtifactsVersions = rawVersions.filter((rv) => rv.ConversationArtifactID === a.ID);
|
|
1388
1436
|
const versionsForThisArtifact: SkipAPIArtifactVersion[] = thisArtifactsVersions.map((v: ConversationArtifactVersionEntity) => {
|
|
1389
1437
|
const versionRetVal: SkipAPIArtifactVersion = {
|
|
1390
1438
|
id: v.ID,
|
|
@@ -1394,7 +1442,7 @@ export class AskSkipResolver {
|
|
|
1394
1442
|
content: v.Content,
|
|
1395
1443
|
comments: v.Comments,
|
|
1396
1444
|
createdAt: v.__mj_CreatedAt,
|
|
1397
|
-
updatedAt: v.__mj_UpdatedAt
|
|
1445
|
+
updatedAt: v.__mj_UpdatedAt,
|
|
1398
1446
|
};
|
|
1399
1447
|
return versionRetVal;
|
|
1400
1448
|
});
|
|
@@ -1403,28 +1451,26 @@ export class AskSkipResolver {
|
|
|
1403
1451
|
name: a.Name,
|
|
1404
1452
|
description: a.Description,
|
|
1405
1453
|
comments: a.Comments,
|
|
1406
|
-
sharingScope: a.SharingScope as 'None' |'SpecificUsers' |'Everyone' |'Public',
|
|
1454
|
+
sharingScope: a.SharingScope as 'None' | 'SpecificUsers' | 'Everyone' | 'Public',
|
|
1407
1455
|
versions: versionsForThisArtifact,
|
|
1408
1456
|
conversationId: a.ConversationID,
|
|
1409
|
-
artifactType: types.find((t => t.id === a.ArtifactTypeID)
|
|
1457
|
+
artifactType: types.find((t) => t.id === a.ArtifactTypeID),
|
|
1410
1458
|
createdAt: a.__mj_CreatedAt,
|
|
1411
|
-
updatedAt: a.__mj_UpdatedAt
|
|
1459
|
+
updatedAt: a.__mj_UpdatedAt,
|
|
1412
1460
|
};
|
|
1413
1461
|
return artifactRetVal;
|
|
1414
1462
|
});
|
|
1415
1463
|
|
|
1416
1464
|
return allConvoArtifacts;
|
|
1417
|
-
}
|
|
1418
|
-
else {
|
|
1465
|
+
} else {
|
|
1419
1466
|
return [];
|
|
1420
1467
|
}
|
|
1421
1468
|
}
|
|
1422
1469
|
|
|
1423
|
-
|
|
1424
1470
|
/**
|
|
1425
1471
|
* Executes a script in the context of a data context
|
|
1426
1472
|
* Allows running code against data context objects
|
|
1427
|
-
*
|
|
1473
|
+
*
|
|
1428
1474
|
* @param dataSource Database connection
|
|
1429
1475
|
* @param userPayload Information about the authenticated user
|
|
1430
1476
|
* @param pubSub Publisher/subscriber for events
|
|
@@ -1443,7 +1489,22 @@ export class AskSkipResolver {
|
|
|
1443
1489
|
if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
1444
1490
|
const dataContext: DataContext = new DataContext();
|
|
1445
1491
|
await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
|
|
1446
|
-
const input = <SkipAPIRunScriptRequest>
|
|
1492
|
+
const input = <SkipAPIRunScriptRequest>(
|
|
1493
|
+
await this.buildSkipChatAPIRequest(
|
|
1494
|
+
[],
|
|
1495
|
+
'',
|
|
1496
|
+
dataContext,
|
|
1497
|
+
'run_existing_script',
|
|
1498
|
+
false,
|
|
1499
|
+
false,
|
|
1500
|
+
false,
|
|
1501
|
+
false,
|
|
1502
|
+
user,
|
|
1503
|
+
dataSource,
|
|
1504
|
+
false,
|
|
1505
|
+
false
|
|
1506
|
+
)
|
|
1507
|
+
);
|
|
1447
1508
|
input.scriptText = ScriptText;
|
|
1448
1509
|
return this.handleSimpleSkipChatPostRequest(input, undefined, undefined, undefined, userPayload.userRecord, userPayload);
|
|
1449
1510
|
}
|
|
@@ -1451,7 +1512,7 @@ export class AskSkipResolver {
|
|
|
1451
1512
|
/**
|
|
1452
1513
|
* Builds the array of API keys for various AI services
|
|
1453
1514
|
* These are used by Skip to call external AI services
|
|
1454
|
-
*
|
|
1515
|
+
*
|
|
1455
1516
|
* @returns Array of API keys for different vendor services
|
|
1456
1517
|
*/
|
|
1457
1518
|
protected buildSkipAPIKeys(): SkipAPIRequestAPIKey[] {
|
|
@@ -1500,34 +1561,34 @@ export class AskSkipResolver {
|
|
|
1500
1561
|
LogError(`User ${userPayload.email} not found in UserCache`);
|
|
1501
1562
|
return null;
|
|
1502
1563
|
}
|
|
1503
|
-
|
|
1564
|
+
|
|
1504
1565
|
// Load the conversation
|
|
1505
1566
|
const convoEntity = await md.GetEntityObject<ConversationEntity>('Conversations', user);
|
|
1506
1567
|
const loadResult = await convoEntity.Load(ConversationId);
|
|
1507
|
-
|
|
1568
|
+
|
|
1508
1569
|
if (!loadResult) {
|
|
1509
1570
|
LogError(`Could not load conversation ${ConversationId} for re-attachment`);
|
|
1510
1571
|
return null;
|
|
1511
1572
|
}
|
|
1512
|
-
|
|
1573
|
+
|
|
1513
1574
|
// Check if the conversation belongs to this user
|
|
1514
1575
|
if (convoEntity.UserID !== user.ID) {
|
|
1515
1576
|
LogError(`Conversation ${ConversationId} does not belong to user ${user.Email}`);
|
|
1516
1577
|
return null;
|
|
1517
1578
|
}
|
|
1518
|
-
|
|
1579
|
+
|
|
1519
1580
|
// If the conversation is processing, reattach the session to receive updates
|
|
1520
1581
|
if (convoEntity.Status === 'Processing') {
|
|
1521
1582
|
// Add this session to the active streams for this conversation
|
|
1522
1583
|
activeStreams.addSession(ConversationId, userPayload.sessionId);
|
|
1523
|
-
|
|
1584
|
+
|
|
1524
1585
|
// Get the last known status message and start time from our cache
|
|
1525
1586
|
const lastStatusMessage = activeStreams.getStatus(ConversationId) || 'Processing...';
|
|
1526
1587
|
const startTime = activeStreams.getStartTime(ConversationId);
|
|
1527
|
-
|
|
1588
|
+
|
|
1528
1589
|
// Check if the stream is still active
|
|
1529
1590
|
const isStreamActive = activeStreams.isActive(ConversationId);
|
|
1530
|
-
|
|
1591
|
+
|
|
1531
1592
|
if (isStreamActive) {
|
|
1532
1593
|
// Send the last known status to the frontend
|
|
1533
1594
|
const statusMessage = {
|
|
@@ -1537,20 +1598,22 @@ export class AskSkipResolver {
|
|
|
1537
1598
|
conversationID: convoEntity.ID,
|
|
1538
1599
|
message: lastStatusMessage,
|
|
1539
1600
|
};
|
|
1540
|
-
|
|
1601
|
+
|
|
1541
1602
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
1542
1603
|
pushStatusUpdates: {
|
|
1543
1604
|
message: JSON.stringify(statusMessage),
|
|
1544
|
-
sessionId: userPayload.sessionId
|
|
1545
|
-
}
|
|
1605
|
+
sessionId: userPayload.sessionId,
|
|
1606
|
+
},
|
|
1546
1607
|
});
|
|
1547
|
-
|
|
1548
|
-
LogStatus(
|
|
1549
|
-
|
|
1608
|
+
|
|
1609
|
+
LogStatus(
|
|
1610
|
+
`Re-attached session ${userPayload.sessionId} to active stream for conversation ${ConversationId}, last status: ${lastStatusMessage}`
|
|
1611
|
+
);
|
|
1612
|
+
|
|
1550
1613
|
// Return the status and start time
|
|
1551
1614
|
return {
|
|
1552
1615
|
lastStatusMessage,
|
|
1553
|
-
startTime: startTime || convoEntity.__mj_UpdatedAt
|
|
1616
|
+
startTime: startTime || convoEntity.__mj_UpdatedAt,
|
|
1554
1617
|
};
|
|
1555
1618
|
} else {
|
|
1556
1619
|
// Stream is inactive or doesn't exist, just send default status
|
|
@@ -1561,20 +1624,20 @@ export class AskSkipResolver {
|
|
|
1561
1624
|
conversationID: convoEntity.ID,
|
|
1562
1625
|
message: 'Processing...',
|
|
1563
1626
|
};
|
|
1564
|
-
|
|
1627
|
+
|
|
1565
1628
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
1566
1629
|
pushStatusUpdates: {
|
|
1567
1630
|
message: JSON.stringify(statusMessage),
|
|
1568
|
-
sessionId: userPayload.sessionId
|
|
1569
|
-
}
|
|
1631
|
+
sessionId: userPayload.sessionId,
|
|
1632
|
+
},
|
|
1570
1633
|
});
|
|
1571
|
-
|
|
1634
|
+
|
|
1572
1635
|
LogStatus(`Re-attached session ${userPayload.sessionId} to conversation ${ConversationId}, but stream is inactive`);
|
|
1573
|
-
|
|
1636
|
+
|
|
1574
1637
|
// Return default start time since stream is inactive
|
|
1575
1638
|
return {
|
|
1576
1639
|
lastStatusMessage: 'Processing...',
|
|
1577
|
-
startTime: convoEntity.__mj_UpdatedAt
|
|
1640
|
+
startTime: convoEntity.__mj_UpdatedAt,
|
|
1578
1641
|
};
|
|
1579
1642
|
}
|
|
1580
1643
|
} else {
|
|
@@ -1590,7 +1653,7 @@ export class AskSkipResolver {
|
|
|
1590
1653
|
/**
|
|
1591
1654
|
* Executes an analysis query with Skip
|
|
1592
1655
|
* This is the primary entry point for general Skip conversations
|
|
1593
|
-
*
|
|
1656
|
+
*
|
|
1594
1657
|
* @param UserQuestion The question or message from the user
|
|
1595
1658
|
* @param ConversationId ID of an existing conversation, or empty for a new conversation
|
|
1596
1659
|
* @param dataSource Database connection
|
|
@@ -1637,8 +1700,21 @@ export class AskSkipResolver {
|
|
|
1637
1700
|
AskSkipResolver._maxHistoricalMessages
|
|
1638
1701
|
);
|
|
1639
1702
|
|
|
1640
|
-
const conversationDetailCount = 1
|
|
1641
|
-
const input = await this.buildSkipChatAPIRequest(
|
|
1703
|
+
const conversationDetailCount = 1;
|
|
1704
|
+
const input = await this.buildSkipChatAPIRequest(
|
|
1705
|
+
messages,
|
|
1706
|
+
ConversationId,
|
|
1707
|
+
dataContext,
|
|
1708
|
+
'initial_request',
|
|
1709
|
+
true,
|
|
1710
|
+
true,
|
|
1711
|
+
true,
|
|
1712
|
+
false,
|
|
1713
|
+
user,
|
|
1714
|
+
dataSource,
|
|
1715
|
+
ForceEntityRefresh === undefined ? false : ForceEntityRefresh,
|
|
1716
|
+
true
|
|
1717
|
+
);
|
|
1642
1718
|
|
|
1643
1719
|
return this.HandleSkipChatRequest(
|
|
1644
1720
|
input,
|
|
@@ -1658,28 +1734,27 @@ export class AskSkipResolver {
|
|
|
1658
1734
|
);
|
|
1659
1735
|
}
|
|
1660
1736
|
|
|
1661
|
-
|
|
1662
1737
|
/**
|
|
1663
1738
|
* Recursively builds the category path for a query
|
|
1664
|
-
* @param md
|
|
1665
|
-
* @param categoryID
|
|
1739
|
+
* @param md
|
|
1740
|
+
* @param categoryID
|
|
1666
1741
|
*/
|
|
1667
1742
|
protected buildQueryCategoryPath(md: Metadata, categoryID: string): string {
|
|
1668
1743
|
const cat = md.QueryCategories.find((c) => c.ID === categoryID);
|
|
1669
1744
|
if (!cat) return '';
|
|
1670
1745
|
if (!cat.ParentID) return cat.Name; // base case, no parent, just return the name
|
|
1671
1746
|
const parentPath = this.buildQueryCategoryPath(md, cat.ParentID); // build the path recursively
|
|
1672
|
-
return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
|
|
1747
|
+
return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
|
|
1673
1748
|
}
|
|
1674
1749
|
|
|
1675
1750
|
/**
|
|
1676
1751
|
* Packages up queries from the metadata based on their status
|
|
1677
1752
|
* Used to provide Skip with information about available queries
|
|
1678
|
-
*
|
|
1753
|
+
*
|
|
1679
1754
|
* @param status The status of queries to include
|
|
1680
1755
|
* @returns Array of query information objects
|
|
1681
1756
|
*/
|
|
1682
|
-
protected BuildSkipQueries(status:
|
|
1757
|
+
protected BuildSkipQueries(status: 'Pending' | 'In-Review' | 'Approved' | 'Rejected' | 'Obsolete' = 'Approved'): SkipQueryInfo[] {
|
|
1683
1758
|
const md = new Metadata();
|
|
1684
1759
|
const approvedQueries = md.Queries.filter((q) => q.Status === status);
|
|
1685
1760
|
return approvedQueries.map((q) => {
|
|
@@ -1743,33 +1818,33 @@ export class AskSkipResolver {
|
|
|
1743
1818
|
createdAt: e.__mj_CreatedAt,
|
|
1744
1819
|
updatedAt: e.__mj_UpdatedAt,
|
|
1745
1820
|
};
|
|
1746
|
-
})
|
|
1747
|
-
}
|
|
1821
|
+
}),
|
|
1822
|
+
};
|
|
1748
1823
|
});
|
|
1749
1824
|
}
|
|
1750
1825
|
|
|
1751
1826
|
// /**
|
|
1752
1827
|
// * Builds up the array of notes and note types for Skip
|
|
1753
1828
|
// * These notes are used to provide Skip with domain knowledge and context
|
|
1754
|
-
// *
|
|
1829
|
+
// *
|
|
1755
1830
|
// * @param contextUser User context for the request
|
|
1756
1831
|
// * @returns Object containing arrays of notes and note types
|
|
1757
1832
|
// */
|
|
1758
1833
|
// protected async BuildSkipAgentNotes(contextUser: UserInfo, filterUserNotesToContextUser: boolean): Promise<{notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[]}> {
|
|
1759
1834
|
// try {
|
|
1760
1835
|
// // if already configured this does nothing, just makes sure we're configured
|
|
1761
|
-
// await AIEngine.Instance.Config(false, contextUser);
|
|
1836
|
+
// await AIEngine.Instance.Config(false, contextUser);
|
|
1762
1837
|
|
|
1763
1838
|
// const agent: AIAgentEntityExtended = AIEngine.Instance.GetAgentByName('Skip');
|
|
1764
1839
|
// if (agent) {
|
|
1765
1840
|
// let notes: SkipAPIAgentNote[] = [];
|
|
1766
1841
|
// let noteTypes: SkipAPIAgentNoteType[] = [];
|
|
1767
|
-
|
|
1842
|
+
|
|
1768
1843
|
// notes = agent.Notes.map((r) => {
|
|
1769
1844
|
// return {
|
|
1770
1845
|
// id: r.ID,
|
|
1771
1846
|
// agentNoteTypeId: r.AgentNoteTypeID,
|
|
1772
|
-
// agentNoteType: r.AgentNoteType,
|
|
1847
|
+
// agentNoteType: r.AgentNoteType,
|
|
1773
1848
|
// note: r.Note,
|
|
1774
1849
|
// type: r.Type,
|
|
1775
1850
|
// userId: r.UserID,
|
|
@@ -1781,7 +1856,7 @@ export class AskSkipResolver {
|
|
|
1781
1856
|
|
|
1782
1857
|
// if (filterUserNotesToContextUser){
|
|
1783
1858
|
// // filter out any notes that are not for this user
|
|
1784
|
-
// notes = notes.filter((n) => n.type === 'Global' ||
|
|
1859
|
+
// notes = notes.filter((n) => n.type === 'Global' ||
|
|
1785
1860
|
// (n.type === 'User' && n.userId === contextUser.ID));
|
|
1786
1861
|
// }
|
|
1787
1862
|
|
|
@@ -1810,21 +1885,22 @@ export class AskSkipResolver {
|
|
|
1810
1885
|
/**
|
|
1811
1886
|
* Packs entity rows for inclusion in Skip requests
|
|
1812
1887
|
* Provides sample data based on entity configuration
|
|
1813
|
-
*
|
|
1888
|
+
*
|
|
1814
1889
|
* @param e Entity information
|
|
1815
1890
|
* @param dataSource Database connection
|
|
1816
1891
|
* @returns Array of entity rows based on packing configuration
|
|
1817
1892
|
*/
|
|
1818
1893
|
protected async PackEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
|
|
1819
1894
|
try {
|
|
1820
|
-
if (e.RowsToPackWithSchema === 'None')
|
|
1821
|
-
return [];
|
|
1895
|
+
if (e.RowsToPackWithSchema === 'None') return [];
|
|
1822
1896
|
|
|
1823
1897
|
// only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
|
|
1824
1898
|
const fields = e.Fields.filter((f) => {
|
|
1825
1899
|
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
1826
1900
|
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
1827
|
-
})
|
|
1901
|
+
})
|
|
1902
|
+
.map((f) => `[${f.Name}]`)
|
|
1903
|
+
.join(',');
|
|
1828
1904
|
|
|
1829
1905
|
// now run the query based on the row packing method
|
|
1830
1906
|
let sql: string = '';
|
|
@@ -1857,12 +1933,10 @@ export class AskSkipResolver {
|
|
|
1857
1933
|
const result = await request.query(sql);
|
|
1858
1934
|
if (!result || !result.recordset) {
|
|
1859
1935
|
return [];
|
|
1860
|
-
}
|
|
1861
|
-
else {
|
|
1936
|
+
} else {
|
|
1862
1937
|
return result.recordset;
|
|
1863
1938
|
}
|
|
1864
|
-
}
|
|
1865
|
-
catch (e) {
|
|
1939
|
+
} catch (e) {
|
|
1866
1940
|
LogError(`AskSkipResolver::PackEntityRows: ${e}`);
|
|
1867
1941
|
return [];
|
|
1868
1942
|
}
|
|
@@ -1871,7 +1945,7 @@ export class AskSkipResolver {
|
|
|
1871
1945
|
/**
|
|
1872
1946
|
* Packs possible values for an entity field
|
|
1873
1947
|
* These values help Skip understand the domain and valid values for fields
|
|
1874
|
-
*
|
|
1948
|
+
*
|
|
1875
1949
|
* @param f Field information
|
|
1876
1950
|
* @param dataSource Database connection
|
|
1877
1951
|
* @returns Array of possible values for the field
|
|
@@ -1880,39 +1954,39 @@ export class AskSkipResolver {
|
|
|
1880
1954
|
try {
|
|
1881
1955
|
if (f.ValuesToPackWithSchema === 'None') {
|
|
1882
1956
|
return []; // don't pack anything
|
|
1883
|
-
}
|
|
1884
|
-
else if (f.ValuesToPackWithSchema === 'All') {
|
|
1957
|
+
} else if (f.ValuesToPackWithSchema === 'All') {
|
|
1885
1958
|
// wants ALL of the distinct values
|
|
1886
1959
|
return await this.GetFieldDistinctValues(f, dataSource);
|
|
1887
|
-
}
|
|
1888
|
-
else if (f.ValuesToPackWithSchema === 'Auto') {
|
|
1960
|
+
} else if (f.ValuesToPackWithSchema === 'Auto') {
|
|
1889
1961
|
// default setting - pack based on the ValueListType
|
|
1890
1962
|
if (f.ValueListTypeEnum === 'List') {
|
|
1891
1963
|
// simple list of values in the Entity Field Values table
|
|
1892
1964
|
return f.EntityFieldValues.map((v) => {
|
|
1893
|
-
return {value: v.Value, displayValue: v.Value};
|
|
1965
|
+
return { value: v.Value, displayValue: v.Value };
|
|
1894
1966
|
});
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// could be a user provided value, OR the values in the list of possible values.
|
|
1967
|
+
} else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
|
|
1968
|
+
// could be a user provided value, OR the values in the list of possible values.
|
|
1898
1969
|
// get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
|
|
1899
1970
|
const values = await this.GetFieldDistinctValues(f, dataSource);
|
|
1900
1971
|
if (!values || values.length === 0) {
|
|
1901
1972
|
// no result, just return the EntityFieldValues
|
|
1902
1973
|
return f.EntityFieldValues.map((v) => {
|
|
1903
|
-
return {value: v.Value, displayValue: v.Value};
|
|
1974
|
+
return { value: v.Value, displayValue: v.Value };
|
|
1904
1975
|
});
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1976
|
+
} else {
|
|
1977
|
+
return [
|
|
1978
|
+
...new Set([
|
|
1979
|
+
...f.EntityFieldValues.map((v) => {
|
|
1980
|
+
return { value: v.Value, displayValue: v.Value };
|
|
1981
|
+
}),
|
|
1982
|
+
...values,
|
|
1983
|
+
]),
|
|
1984
|
+
];
|
|
1910
1985
|
}
|
|
1911
1986
|
}
|
|
1912
1987
|
}
|
|
1913
1988
|
return []; // if we get here, nothing to pack
|
|
1914
|
-
}
|
|
1915
|
-
catch (e) {
|
|
1989
|
+
} catch (e) {
|
|
1916
1990
|
LogError(`AskSkipResolver::PackFieldPossibleValues: ${e}`);
|
|
1917
1991
|
return [];
|
|
1918
1992
|
}
|
|
@@ -1921,7 +1995,7 @@ export class AskSkipResolver {
|
|
|
1921
1995
|
/**
|
|
1922
1996
|
* Gets distinct values for a field from the database
|
|
1923
1997
|
* Used to provide Skip with information about the possible values
|
|
1924
|
-
*
|
|
1998
|
+
*
|
|
1925
1999
|
* @param f Field information
|
|
1926
2000
|
* @param dataSource Database connection
|
|
1927
2001
|
* @returns Array of distinct values for the field
|
|
@@ -1933,64 +2007,70 @@ export class AskSkipResolver {
|
|
|
1933
2007
|
const result = await request.query(sql);
|
|
1934
2008
|
if (!result || !result.recordset) {
|
|
1935
2009
|
return [];
|
|
1936
|
-
}
|
|
1937
|
-
else {
|
|
2010
|
+
} else {
|
|
1938
2011
|
return result.recordset.map((r) => {
|
|
1939
2012
|
return {
|
|
1940
|
-
value: r[f.Name],
|
|
1941
|
-
displayValue: r[f.Name]
|
|
1942
|
-
};
|
|
2013
|
+
value: r[f.Name],
|
|
2014
|
+
displayValue: r[f.Name],
|
|
2015
|
+
};
|
|
1943
2016
|
});
|
|
1944
2017
|
}
|
|
1945
|
-
}
|
|
1946
|
-
catch (e) {
|
|
2018
|
+
} catch (e) {
|
|
1947
2019
|
LogError(`AskSkipResolver::GetFieldDistinctValues: ${e}`);
|
|
1948
|
-
return [];
|
|
2020
|
+
return [];
|
|
1949
2021
|
}
|
|
1950
2022
|
}
|
|
1951
2023
|
|
|
1952
|
-
|
|
1953
2024
|
// SKIP ENTITIES CACHING
|
|
1954
2025
|
// Static variables shared across all instances
|
|
1955
|
-
private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<
|
|
2026
|
+
private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<
|
|
2027
|
+
SkipEntityInfo[]
|
|
2028
|
+
> | null>(null);
|
|
1956
2029
|
private static __lastRefreshTime: number = 0;
|
|
1957
2030
|
|
|
1958
2031
|
/**
|
|
1959
2032
|
* Refreshes the Skip entities cache
|
|
1960
2033
|
* Rebuilds the entity information that is provided to Skip
|
|
1961
|
-
*
|
|
2034
|
+
*
|
|
1962
2035
|
* @param dataSource Database connection
|
|
1963
2036
|
* @returns Updated array of entity information
|
|
1964
2037
|
*/
|
|
1965
2038
|
private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
|
|
1966
2039
|
try {
|
|
1967
2040
|
const md = new Metadata();
|
|
1968
|
-
const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
|
|
1969
|
-
|
|
1970
|
-
|
|
2041
|
+
const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? []).map((e) =>
|
|
2042
|
+
e.trim().toLowerCase()
|
|
2043
|
+
);
|
|
2044
|
+
|
|
1971
2045
|
// get the list of entities
|
|
1972
2046
|
const entities = md.Entities.filter((e) => {
|
|
1973
|
-
if (
|
|
1974
|
-
|
|
2047
|
+
if (
|
|
2048
|
+
!configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
|
|
2049
|
+
skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
|
|
2050
|
+
) {
|
|
1975
2051
|
const sd = e.ScopeDefault?.trim();
|
|
1976
2052
|
if (sd && sd.length > 0) {
|
|
1977
2053
|
const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
|
|
1978
|
-
return
|
|
1979
|
-
|
|
1980
|
-
|
|
2054
|
+
return (
|
|
2055
|
+
!scopes ||
|
|
2056
|
+
scopes.length === 0 ||
|
|
2057
|
+
scopes.includes('all') ||
|
|
2058
|
+
scopes.includes('ai') ||
|
|
2059
|
+
skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
|
|
2060
|
+
);
|
|
2061
|
+
} else {
|
|
1981
2062
|
return true; // no scope, so include it
|
|
1982
2063
|
}
|
|
1983
2064
|
}
|
|
1984
2065
|
return false;
|
|
1985
2066
|
});
|
|
1986
|
-
|
|
2067
|
+
|
|
1987
2068
|
// now we have our list of entities, pack em up
|
|
1988
2069
|
const result = await Promise.all(entities.map((e) => this.PackSingleSkipEntityInfo(e, dataSource)));
|
|
1989
|
-
|
|
2070
|
+
|
|
1990
2071
|
AskSkipResolver.__lastRefreshTime = Date.now(); // Update last refresh time
|
|
1991
|
-
return result;
|
|
1992
|
-
}
|
|
1993
|
-
catch (e) {
|
|
2072
|
+
return result;
|
|
2073
|
+
} catch (e) {
|
|
1994
2074
|
LogError(`AskSkipResolver::refreshSkipEntities: ${e}`);
|
|
1995
2075
|
return [];
|
|
1996
2076
|
}
|
|
@@ -1999,27 +2079,30 @@ export class AskSkipResolver {
|
|
|
1999
2079
|
/**
|
|
2000
2080
|
* Builds or retrieves Skip entities from cache
|
|
2001
2081
|
* Uses caching to avoid expensive rebuilding of entity information
|
|
2002
|
-
*
|
|
2082
|
+
*
|
|
2003
2083
|
* @param dataSource Database connection
|
|
2004
2084
|
* @param forceRefresh Whether to force a refresh regardless of cache state
|
|
2005
2085
|
* @param refreshIntervalMinutes Minutes before cache expires
|
|
2006
2086
|
* @returns Array of entity information
|
|
2007
2087
|
*/
|
|
2008
|
-
public async BuildSkipEntities(
|
|
2088
|
+
public async BuildSkipEntities(
|
|
2089
|
+
dataSource: mssql.ConnectionPool,
|
|
2090
|
+
forceRefresh: boolean = false,
|
|
2091
|
+
refreshIntervalMinutes: number = 15
|
|
2092
|
+
): Promise<SkipEntityInfo[]> {
|
|
2009
2093
|
try {
|
|
2010
2094
|
const now = Date.now();
|
|
2011
|
-
const cacheExpired =
|
|
2012
|
-
|
|
2095
|
+
const cacheExpired = now - AskSkipResolver.__lastRefreshTime > refreshIntervalMinutes * 60 * 1000;
|
|
2096
|
+
|
|
2013
2097
|
// If force refresh is requested OR cache expired OR cache is empty, refresh
|
|
2014
2098
|
if (forceRefresh || cacheExpired || AskSkipResolver.__skipEntitiesCache$.value === null) {
|
|
2015
2099
|
console.log(`Forcing Skip Entities refresh: ${forceRefresh}, Cache Expired: ${cacheExpired}`);
|
|
2016
2100
|
const newData = this.refreshSkipEntities(dataSource);
|
|
2017
2101
|
AskSkipResolver.__skipEntitiesCache$.next(newData);
|
|
2018
2102
|
}
|
|
2019
|
-
|
|
2020
|
-
return AskSkipResolver.__skipEntitiesCache$.pipe(take(1)).toPromise();
|
|
2021
|
-
}
|
|
2022
|
-
catch (e) {
|
|
2103
|
+
|
|
2104
|
+
return AskSkipResolver.__skipEntitiesCache$.pipe(take(1)).toPromise();
|
|
2105
|
+
} catch (e) {
|
|
2023
2106
|
LogError(`AskSkipResolver::BuildSkipEntities: ${e}`);
|
|
2024
2107
|
return [];
|
|
2025
2108
|
}
|
|
@@ -2028,7 +2111,7 @@ export class AskSkipResolver {
|
|
|
2028
2111
|
/**
|
|
2029
2112
|
* Packs information about a single entity for Skip
|
|
2030
2113
|
* Includes fields, relationships, and sample data
|
|
2031
|
-
*
|
|
2114
|
+
*
|
|
2032
2115
|
* @param e Entity information
|
|
2033
2116
|
* @param dataSource Database connection
|
|
2034
2117
|
* @returns Packaged entity information
|
|
@@ -2041,26 +2124,27 @@ export class AskSkipResolver {
|
|
|
2041
2124
|
schemaName: e.SchemaName,
|
|
2042
2125
|
baseView: e.BaseView,
|
|
2043
2126
|
description: e.Description,
|
|
2044
|
-
|
|
2045
|
-
fields: await Promise.all(
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2127
|
+
|
|
2128
|
+
fields: await Promise.all(
|
|
2129
|
+
e.Fields.filter((f) => {
|
|
2130
|
+
// we want to check the scopes for the field level and make sure it is either All or AI or has both
|
|
2131
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
2132
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
2133
|
+
}).map((f) => {
|
|
2134
|
+
return this.PackSingleSkipEntityField(f, dataSource);
|
|
2135
|
+
})
|
|
2136
|
+
),
|
|
2137
|
+
|
|
2053
2138
|
relatedEntities: e.RelatedEntities.map((r) => {
|
|
2054
2139
|
return this.PackSingleSkipEntityRelationship(r);
|
|
2055
2140
|
}),
|
|
2056
|
-
|
|
2141
|
+
|
|
2057
2142
|
rowsPacked: e.RowsToPackWithSchema,
|
|
2058
2143
|
rowsSampleMethod: e.RowsToPackSampleMethod,
|
|
2059
|
-
rows: await this.PackEntityRows(e, dataSource)
|
|
2144
|
+
rows: await this.PackEntityRows(e, dataSource),
|
|
2060
2145
|
};
|
|
2061
2146
|
return ret;
|
|
2062
|
-
}
|
|
2063
|
-
catch (e) {
|
|
2147
|
+
} catch (e) {
|
|
2064
2148
|
LogError(`AskSkipResolver::PackSingleSkipEntityInfo: ${e}`);
|
|
2065
2149
|
return null;
|
|
2066
2150
|
}
|
|
@@ -2069,7 +2153,7 @@ export class AskSkipResolver {
|
|
|
2069
2153
|
/**
|
|
2070
2154
|
* Packs information about a single entity relationship
|
|
2071
2155
|
* These relationships help Skip understand the data model
|
|
2072
|
-
*
|
|
2156
|
+
*
|
|
2073
2157
|
* @param r Relationship information
|
|
2074
2158
|
* @returns Packaged relationship information
|
|
2075
2159
|
*/
|
|
@@ -2089,8 +2173,7 @@ export class AskSkipResolver {
|
|
|
2089
2173
|
relatedEntity: r.RelatedEntity,
|
|
2090
2174
|
relatedEntityBaseView: r.RelatedEntityBaseView,
|
|
2091
2175
|
};
|
|
2092
|
-
}
|
|
2093
|
-
catch (e) {
|
|
2176
|
+
} catch (e) {
|
|
2094
2177
|
LogError(`AskSkipResolver::PackSingleSkipEntityRelationship: ${e}`);
|
|
2095
2178
|
return null;
|
|
2096
2179
|
}
|
|
@@ -2099,7 +2182,7 @@ export class AskSkipResolver {
|
|
|
2099
2182
|
/**
|
|
2100
2183
|
* Packs information about a single entity field
|
|
2101
2184
|
* Includes metadata and possible values
|
|
2102
|
-
*
|
|
2185
|
+
*
|
|
2103
2186
|
* @param f Field information
|
|
2104
2187
|
* @param dataSource Database connection
|
|
2105
2188
|
* @returns Packaged field information
|
|
@@ -2137,8 +2220,7 @@ export class AskSkipResolver {
|
|
|
2137
2220
|
relatedEntityBaseView: f.RelatedEntityBaseView,
|
|
2138
2221
|
possibleValues: await this.PackFieldPossibleValues(f, dataSource),
|
|
2139
2222
|
};
|
|
2140
|
-
}
|
|
2141
|
-
catch (e) {
|
|
2223
|
+
} catch (e) {
|
|
2142
2224
|
LogError(`AskSkipResolver::PackSingleSkipEntityField: ${e}`);
|
|
2143
2225
|
return null;
|
|
2144
2226
|
}
|
|
@@ -2147,7 +2229,7 @@ export class AskSkipResolver {
|
|
|
2147
2229
|
/**
|
|
2148
2230
|
* Handles initial object loading for Skip chat interactions
|
|
2149
2231
|
* Creates or loads conversation objects, data contexts, and other required entities
|
|
2150
|
-
*
|
|
2232
|
+
*
|
|
2151
2233
|
* @param dataSource Database connection
|
|
2152
2234
|
* @param ConversationId ID of an existing conversation, or empty for a new one
|
|
2153
2235
|
* @param UserQuestion The user's question or message
|
|
@@ -2192,8 +2274,7 @@ export class AskSkipResolver {
|
|
|
2192
2274
|
LogError(`Creating a new data context failed`, undefined, dataContextEntity.LatestResult);
|
|
2193
2275
|
throw new Error(`Creating a new data context failed`);
|
|
2194
2276
|
}
|
|
2195
|
-
}
|
|
2196
|
-
else {
|
|
2277
|
+
} else {
|
|
2197
2278
|
const dcLoadResult = await dataContextEntity.Load(DataContextId);
|
|
2198
2279
|
if (!dcLoadResult) {
|
|
2199
2280
|
throw new Error(`Loading DataContextEntity for DataContextId ${DataContextId} failed`);
|
|
@@ -2210,17 +2291,14 @@ export class AskSkipResolver {
|
|
|
2210
2291
|
LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
|
|
2211
2292
|
}
|
|
2212
2293
|
}
|
|
2213
|
-
}
|
|
2214
|
-
else {
|
|
2294
|
+
} else {
|
|
2215
2295
|
LogError(`Creating a new conversation failed`, undefined, convoEntity.LatestResult);
|
|
2216
2296
|
throw new Error(`Creating a new conversation failed`);
|
|
2217
2297
|
}
|
|
2218
|
-
}
|
|
2219
|
-
else {
|
|
2298
|
+
} else {
|
|
2220
2299
|
throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
2221
2300
|
}
|
|
2222
|
-
}
|
|
2223
|
-
else {
|
|
2301
|
+
} else {
|
|
2224
2302
|
await convoEntity.Load(ConversationId); // load the existing conversation, will need it later
|
|
2225
2303
|
dataContextEntity = await md.GetEntityObject<DataContextEntity>('Data Contexts', user);
|
|
2226
2304
|
|
|
@@ -2233,19 +2311,16 @@ export class AskSkipResolver {
|
|
|
2233
2311
|
if (!convoEntitySaveResult) {
|
|
2234
2312
|
LogError(`Error saving conversation entity for conversation: ${ConversationId}`, undefined, convoEntity.LatestResult);
|
|
2235
2313
|
}
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
// note - we ignore the parameter DataContextId if it is passed in, we will use the data context from the conversation that is saved.
|
|
2314
|
+
} else {
|
|
2315
|
+
// note - we ignore the parameter DataContextId if it is passed in, we will use the data context from the conversation that is saved.
|
|
2239
2316
|
// If a user wants to change the data context for a convo, they can do that elsewhere
|
|
2240
2317
|
console.warn(
|
|
2241
2318
|
`AskSkipResolver: DataContextId ${DataContextId} was passed in but it was ignored because it was different than the DataContextID in the conversation ${convoEntity.DataContextID}`
|
|
2242
2319
|
);
|
|
2243
2320
|
}
|
|
2244
2321
|
// only load if we have a data context here, otherwise we have a new record in the dataContext entity
|
|
2245
|
-
if (convoEntity.DataContextID)
|
|
2246
|
-
|
|
2247
|
-
}
|
|
2248
|
-
else if ((!DataContextId || DataContextId.length === 0) && (!convoEntity.DataContextID || convoEntity.DataContextID.length === 0)) {
|
|
2322
|
+
if (convoEntity.DataContextID) await dataContextEntity.Load(convoEntity.DataContextID);
|
|
2323
|
+
} else if ((!DataContextId || DataContextId.length === 0) && (!convoEntity.DataContextID || convoEntity.DataContextID.length === 0)) {
|
|
2249
2324
|
// in this branch of the logic we don't have a passed in DataContextId and we don't have a DataContextID in the conversation, so we need to save the data context, get the ID,
|
|
2250
2325
|
// update the conversation and save it as well
|
|
2251
2326
|
dataContextEntity.NewRecord();
|
|
@@ -2254,14 +2329,11 @@ export class AskSkipResolver {
|
|
|
2254
2329
|
if (await dataContextEntity.Save()) {
|
|
2255
2330
|
DataContextId = convoEntity.DataContextID;
|
|
2256
2331
|
convoEntity.DataContextID = dataContextEntity.ID;
|
|
2257
|
-
if (!await convoEntity.Save()) {
|
|
2332
|
+
if (!(await convoEntity.Save())) {
|
|
2258
2333
|
LogError(`Error saving conversation entity for conversation: ${ConversationId}`, undefined, convoEntity.LatestResult);
|
|
2259
2334
|
}
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
|
|
2263
|
-
}
|
|
2264
|
-
else {
|
|
2335
|
+
} else LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
|
|
2336
|
+
} else {
|
|
2265
2337
|
// finally in this branch we get here if we have a DataContextId passed in and it is the same as the DataContextID in the conversation, in this case simply load the data context
|
|
2266
2338
|
await dataContextEntity.Load(convoEntity.DataContextID);
|
|
2267
2339
|
}
|
|
@@ -2281,7 +2353,7 @@ export class AskSkipResolver {
|
|
|
2281
2353
|
LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
|
|
2282
2354
|
}
|
|
2283
2355
|
|
|
2284
|
-
const dataContext = MJGlobal.Instance.ClassFactory.CreateInstance<DataContext>(DataContext);
|
|
2356
|
+
const dataContext = MJGlobal.Instance.ClassFactory.CreateInstance<DataContext>(DataContext);
|
|
2285
2357
|
await dataContext.Load(dataContextEntity.ID, dataSource, false, false, 0, user);
|
|
2286
2358
|
return { dataContext, convoEntity, dataContextEntity, convoDetailEntity };
|
|
2287
2359
|
}
|
|
@@ -2289,7 +2361,7 @@ export class AskSkipResolver {
|
|
|
2289
2361
|
/**
|
|
2290
2362
|
* Loads conversation details from the database and transforms them into Skip message format
|
|
2291
2363
|
* Used to provide Skip with conversation history for context
|
|
2292
|
-
*
|
|
2364
|
+
*
|
|
2293
2365
|
* @param dataSource Database connection
|
|
2294
2366
|
* @param ConversationId ID of the conversation to load details for
|
|
2295
2367
|
* @param maxHistoricalMessages Maximum number of historical messages to include
|
|
@@ -2309,10 +2381,10 @@ export class AskSkipResolver {
|
|
|
2309
2381
|
// load up all the conversation details from the database server
|
|
2310
2382
|
const md = new Metadata();
|
|
2311
2383
|
const e = md.Entities.find((e) => e.Name === 'Conversation Details');
|
|
2312
|
-
|
|
2384
|
+
|
|
2313
2385
|
// Add role filter if specified
|
|
2314
2386
|
const roleFilterClause = roleFilter ? ` AND Role = '${roleFilter}'` : '';
|
|
2315
|
-
|
|
2387
|
+
|
|
2316
2388
|
const sql = `SELECT
|
|
2317
2389
|
${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} *
|
|
2318
2390
|
FROM
|
|
@@ -2323,8 +2395,7 @@ export class AskSkipResolver {
|
|
|
2323
2395
|
BY __mj_CreatedAt DESC`;
|
|
2324
2396
|
const request = new mssql.Request(dataSource);
|
|
2325
2397
|
const result = await request.query(sql);
|
|
2326
|
-
if (!result || !result.recordset)
|
|
2327
|
-
throw new Error(`Error running SQL: ${sql}`);
|
|
2398
|
+
if (!result || !result.recordset) throw new Error(`Error running SQL: ${sql}`);
|
|
2328
2399
|
else {
|
|
2329
2400
|
// first, let's sort the result array into a local variable called returnData and in that we will sort by __mj_CreatedAt in ASCENDING order so we have the right chronological order
|
|
2330
2401
|
// the reason we're doing a LOCAL sort here is because in the SQL query above, we're sorting in DESCENDING order so we can use the TOP clause to limit the number of records and get the
|
|
@@ -2359,7 +2430,7 @@ export class AskSkipResolver {
|
|
|
2359
2430
|
executionResults: analysisDetail.executionResults,
|
|
2360
2431
|
tableDataColumns: analysisDetail.tableDataColumns,
|
|
2361
2432
|
componentOptions: analysisDetail.componentOptions,
|
|
2362
|
-
artifactRequest: analysisDetail.artifactRequest
|
|
2433
|
+
artifactRequest: analysisDetail.artifactRequest,
|
|
2363
2434
|
});
|
|
2364
2435
|
} else if (detail?.responsePhase === SkipResponsePhase.ClarifyingQuestion) {
|
|
2365
2436
|
const clarifyingQuestionDetail = <SkipAPIClarifyingQuestionResponse>detail;
|
|
@@ -2397,7 +2468,7 @@ export class AskSkipResolver {
|
|
|
2397
2468
|
/**
|
|
2398
2469
|
* Maps database role values to Skip API role format
|
|
2399
2470
|
* Converts role names from database format to the format expected by Skip API
|
|
2400
|
-
*
|
|
2471
|
+
*
|
|
2401
2472
|
* @param role Database role value
|
|
2402
2473
|
* @returns Skip API role value ('user' or 'system')
|
|
2403
2474
|
*/
|
|
@@ -2415,7 +2486,7 @@ export class AskSkipResolver {
|
|
|
2415
2486
|
/**
|
|
2416
2487
|
* Handles the main Skip chat request processing flow
|
|
2417
2488
|
* Routes the request through the different phases based on the Skip API response
|
|
2418
|
-
*
|
|
2489
|
+
*
|
|
2419
2490
|
* @param input Skip API request to send
|
|
2420
2491
|
* @param UserQuestion The question or message from the user
|
|
2421
2492
|
* @param user User information
|
|
@@ -2443,7 +2514,7 @@ export class AskSkipResolver {
|
|
|
2443
2514
|
convoEntity: ConversationEntity,
|
|
2444
2515
|
convoDetailEntity: ConversationDetailEntity,
|
|
2445
2516
|
dataContext: DataContext,
|
|
2446
|
-
dataContextEntity: DataContextEntity,
|
|
2517
|
+
dataContextEntity: DataContextEntity,
|
|
2447
2518
|
conversationDetailCount: number,
|
|
2448
2519
|
startTime: Date
|
|
2449
2520
|
): Promise<AskSkipResultType> {
|
|
@@ -2499,10 +2570,10 @@ export class AskSkipResolver {
|
|
|
2499
2570
|
LogStatus(JSON.stringify(message, null, 4));
|
|
2500
2571
|
if (message.type === 'status_update') {
|
|
2501
2572
|
const statusContent = message.value.messages[0].content;
|
|
2502
|
-
|
|
2573
|
+
|
|
2503
2574
|
// Store the status in our active streams cache
|
|
2504
2575
|
activeStreams.updateStatus(ConversationId, statusContent, userPayload.sessionId);
|
|
2505
|
-
|
|
2576
|
+
|
|
2506
2577
|
// Publish to all sessions listening to this conversation
|
|
2507
2578
|
const sessionIds = activeStreams.getSessionIds(ConversationId);
|
|
2508
2579
|
for (const sessionId of sessionIds) {
|
|
@@ -2523,10 +2594,10 @@ export class AskSkipResolver {
|
|
|
2523
2594
|
} catch (error) {
|
|
2524
2595
|
// Set conversation status to Available on error so user can try again
|
|
2525
2596
|
await this.setConversationStatus(convoEntity, 'Available', userPayload, pubSub);
|
|
2526
|
-
|
|
2597
|
+
|
|
2527
2598
|
// Log the error for debugging
|
|
2528
2599
|
LogError(`Error in HandleSkipChatRequest sendPostRequest: ${error}`);
|
|
2529
|
-
|
|
2600
|
+
|
|
2530
2601
|
// Publish error status update to user
|
|
2531
2602
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
2532
2603
|
message: JSON.stringify({
|
|
@@ -2537,7 +2608,7 @@ export class AskSkipResolver {
|
|
|
2537
2608
|
}),
|
|
2538
2609
|
sessionId: userPayload.sessionId,
|
|
2539
2610
|
});
|
|
2540
|
-
|
|
2611
|
+
|
|
2541
2612
|
// Re-throw the error to propagate it up the stack
|
|
2542
2613
|
throw error;
|
|
2543
2614
|
}
|
|
@@ -2564,7 +2635,7 @@ export class AskSkipResolver {
|
|
|
2564
2635
|
convoEntity,
|
|
2565
2636
|
convoDetailEntity,
|
|
2566
2637
|
dataContext,
|
|
2567
|
-
dataContextEntity,
|
|
2638
|
+
dataContextEntity,
|
|
2568
2639
|
conversationDetailCount,
|
|
2569
2640
|
startTime
|
|
2570
2641
|
);
|
|
@@ -2581,7 +2652,7 @@ export class AskSkipResolver {
|
|
|
2581
2652
|
pubSub,
|
|
2582
2653
|
convoEntity,
|
|
2583
2654
|
convoDetailEntity,
|
|
2584
|
-
startTime
|
|
2655
|
+
startTime
|
|
2585
2656
|
);
|
|
2586
2657
|
} else if (apiResponse.responsePhase === 'analysis_complete') {
|
|
2587
2658
|
return await this.HandleAnalysisComplete(
|
|
@@ -2638,7 +2709,7 @@ export class AskSkipResolver {
|
|
|
2638
2709
|
/**
|
|
2639
2710
|
* Publishes a status update message to the user based on the Skip API response
|
|
2640
2711
|
* Provides feedback about what phase of processing is happening
|
|
2641
|
-
*
|
|
2712
|
+
*
|
|
2642
2713
|
* @param apiResponse The response from the Skip API
|
|
2643
2714
|
* @param userPayload User payload from context
|
|
2644
2715
|
* @param conversationID ID of the conversation
|
|
@@ -2679,7 +2750,7 @@ export class AskSkipResolver {
|
|
|
2679
2750
|
/**
|
|
2680
2751
|
* Handles the analysis complete phase of the Skip chat process
|
|
2681
2752
|
* Finalizes the conversation and creates necessary artifacts
|
|
2682
|
-
*
|
|
2753
|
+
*
|
|
2683
2754
|
* @param apiRequest The original request sent to Skip
|
|
2684
2755
|
* @param apiResponse The analysis complete response from Skip
|
|
2685
2756
|
* @param UserQuestion The original user question
|
|
@@ -2745,7 +2816,7 @@ export class AskSkipResolver {
|
|
|
2745
2816
|
/**
|
|
2746
2817
|
* Handles the clarifying question phase of the Skip chat process
|
|
2747
2818
|
* Creates a conversation detail for the clarifying question from Skip
|
|
2748
|
-
*
|
|
2819
|
+
*
|
|
2749
2820
|
* @param apiRequest The original request sent to Skip
|
|
2750
2821
|
* @param apiResponse The clarifying question response from Skip
|
|
2751
2822
|
* @param UserQuestion The original user question
|
|
@@ -2781,10 +2852,10 @@ export class AskSkipResolver {
|
|
|
2781
2852
|
convoDetailEntityAI.Role = 'AI';
|
|
2782
2853
|
convoDetailEntityAI.HiddenToUser = false;
|
|
2783
2854
|
convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
|
|
2784
|
-
|
|
2855
|
+
|
|
2785
2856
|
// Set conversation status back to Available since we need user input for the clarifying question
|
|
2786
2857
|
await this.setConversationStatus(convoEntity, 'Available', userPayload, pubSub);
|
|
2787
|
-
|
|
2858
|
+
|
|
2788
2859
|
if (await convoDetailEntityAI.Save()) {
|
|
2789
2860
|
return {
|
|
2790
2861
|
Success: true,
|
|
@@ -2816,7 +2887,7 @@ export class AskSkipResolver {
|
|
|
2816
2887
|
/**
|
|
2817
2888
|
* Handles the data request phase of the Skip chat process
|
|
2818
2889
|
* Processes data requests from Skip and loads requested data
|
|
2819
|
-
*
|
|
2890
|
+
*
|
|
2820
2891
|
* @param apiRequest The original request sent to Skip
|
|
2821
2892
|
* @param apiResponse The data request response from Skip
|
|
2822
2893
|
* @param UserQuestion The original user question
|
|
@@ -2844,7 +2915,7 @@ export class AskSkipResolver {
|
|
|
2844
2915
|
convoEntity: ConversationEntity,
|
|
2845
2916
|
convoDetailEntity: ConversationDetailEntity,
|
|
2846
2917
|
dataContext: DataContext,
|
|
2847
|
-
dataContextEntity: DataContextEntity,
|
|
2918
|
+
dataContextEntity: DataContextEntity,
|
|
2848
2919
|
conversationDetailCount: number,
|
|
2849
2920
|
startTime: Date
|
|
2850
2921
|
): Promise<AskSkipResultType> {
|
|
@@ -2975,7 +3046,7 @@ export class AskSkipResolver {
|
|
|
2975
3046
|
convoEntity,
|
|
2976
3047
|
convoDetailEntity,
|
|
2977
3048
|
dataContext,
|
|
2978
|
-
dataContextEntity,
|
|
3049
|
+
dataContextEntity,
|
|
2979
3050
|
conversationDetailCount,
|
|
2980
3051
|
startTime
|
|
2981
3052
|
);
|
|
@@ -2988,7 +3059,7 @@ export class AskSkipResolver {
|
|
|
2988
3059
|
/**
|
|
2989
3060
|
* Finishes a successful conversation and notifies the user
|
|
2990
3061
|
* Creates necessary records, artifacts, and notifications
|
|
2991
|
-
*
|
|
3062
|
+
*
|
|
2992
3063
|
* @param apiResponse The analysis complete response from Skip
|
|
2993
3064
|
* @param dataContext Data context associated with the conversation
|
|
2994
3065
|
* @param dataContextEntity Data context entity
|
|
@@ -3032,40 +3103,39 @@ export class AskSkipResolver {
|
|
|
3032
3103
|
artifactEntity.Name = apiResponse.artifactRequest.name;
|
|
3033
3104
|
artifactEntity.Description = apiResponse.artifactRequest.description;
|
|
3034
3105
|
// make sure AI Engine is configured.
|
|
3035
|
-
await AIEngine.Instance.Config(false, user)
|
|
3106
|
+
await AIEngine.Instance.Config(false, user);
|
|
3036
3107
|
artifactEntity.ArtifactTypeID = AIEngine.Instance.ArtifactTypes.find((t) => t.Name === 'Report')?.ID;
|
|
3037
3108
|
artifactEntity.SharingScope = 'None';
|
|
3038
3109
|
|
|
3039
3110
|
if (await artifactEntity.Save()) {
|
|
3040
3111
|
// saved, grab the new ID
|
|
3041
3112
|
artifactId = artifactEntity.ID;
|
|
3042
|
-
}
|
|
3043
|
-
else {
|
|
3113
|
+
} else {
|
|
3044
3114
|
LogError(`Error saving artifact entity for conversation: ${convoEntity.ID}`, undefined, artifactEntity.LatestResult);
|
|
3045
3115
|
}
|
|
3046
3116
|
newVersion = 1;
|
|
3047
|
-
}
|
|
3048
|
-
else {
|
|
3117
|
+
} else {
|
|
3049
3118
|
// we are updating an existing artifact with a new vesrion so we need to get the old max version and increment it
|
|
3050
|
-
const ei = md.EntityByName(
|
|
3119
|
+
const ei = md.EntityByName('MJ: Conversation Artifact Versions');
|
|
3051
3120
|
const sSQL = `SELECT ISNULL(MAX(Version),0) AS MaxVersion FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationArtifactID = '${artifactId}'`;
|
|
3052
3121
|
try {
|
|
3053
3122
|
const request = new mssql.Request(dataSource);
|
|
3054
3123
|
const result = await request.query(sSQL);
|
|
3055
3124
|
if (result && result.recordset && result.recordset.length > 0) {
|
|
3056
3125
|
newVersion = result.recordset[0].MaxVersion + 1;
|
|
3057
|
-
}
|
|
3058
|
-
else {
|
|
3126
|
+
} else {
|
|
3059
3127
|
LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, result);
|
|
3060
3128
|
}
|
|
3061
|
-
}
|
|
3062
|
-
catch (e) {
|
|
3129
|
+
} catch (e) {
|
|
3063
3130
|
LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, e);
|
|
3064
3131
|
}
|
|
3065
3132
|
}
|
|
3066
3133
|
if (artifactId && newVersion > 0) {
|
|
3067
3134
|
// only do this if we were provided an artifact ID or we saved a new one above successfully
|
|
3068
|
-
const artifactVersionEntity = await md.GetEntityObject<ConversationArtifactVersionEntity>(
|
|
3135
|
+
const artifactVersionEntity = await md.GetEntityObject<ConversationArtifactVersionEntity>(
|
|
3136
|
+
'MJ: Conversation Artifact Versions',
|
|
3137
|
+
user
|
|
3138
|
+
);
|
|
3069
3139
|
// create the new artifact version here
|
|
3070
3140
|
artifactVersionEntity.NewRecord();
|
|
3071
3141
|
artifactVersionEntity.ConversationArtifactID = artifactId;
|
|
@@ -3075,9 +3145,8 @@ export class AskSkipResolver {
|
|
|
3075
3145
|
if (await artifactVersionEntity.Save()) {
|
|
3076
3146
|
// success saving the new version, set the artifactVersionId
|
|
3077
3147
|
artifactVersionId = artifactVersionEntity.ID;
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
LogError(`Error saving Artifact Version record`)
|
|
3148
|
+
} else {
|
|
3149
|
+
LogError(`Error saving Artifact Version record`);
|
|
3081
3150
|
}
|
|
3082
3151
|
}
|
|
3083
3152
|
}
|
|
@@ -3091,14 +3160,14 @@ export class AskSkipResolver {
|
|
|
3091
3160
|
convoDetailEntityAI.Role = 'AI';
|
|
3092
3161
|
convoDetailEntityAI.HiddenToUser = false;
|
|
3093
3162
|
convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
|
|
3094
|
-
|
|
3163
|
+
|
|
3095
3164
|
if (artifactId && artifactId.length > 0) {
|
|
3096
3165
|
// bind the new convo detail record to the artifact + version for this response
|
|
3097
3166
|
convoDetailEntityAI.ArtifactID = artifactId;
|
|
3098
3167
|
if (artifactVersionId && artifactVersionId.length > 0) {
|
|
3099
3168
|
convoDetailEntityAI.ArtifactVersionID = artifactVersionId;
|
|
3100
3169
|
}
|
|
3101
|
-
}
|
|
3170
|
+
}
|
|
3102
3171
|
|
|
3103
3172
|
const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
|
|
3104
3173
|
if (!convoDetailSaveResult) {
|
|
@@ -3107,19 +3176,19 @@ export class AskSkipResolver {
|
|
|
3107
3176
|
|
|
3108
3177
|
// Update the conversation properties: name if it's the default, and set status back to 'Available'
|
|
3109
3178
|
let needToSaveConvo = false;
|
|
3110
|
-
|
|
3179
|
+
|
|
3111
3180
|
// Update name if still default
|
|
3112
3181
|
if (convoEntity.Name === AskSkipResolver._defaultNewChatName && sTitle && sTitle !== AskSkipResolver._defaultNewChatName) {
|
|
3113
3182
|
convoEntity.Name = sTitle; // use the title from the response
|
|
3114
3183
|
needToSaveConvo = true;
|
|
3115
3184
|
}
|
|
3116
|
-
|
|
3185
|
+
|
|
3117
3186
|
// Set status back to 'Available' since processing is complete
|
|
3118
3187
|
if (convoEntity.Status === 'Processing') {
|
|
3119
3188
|
convoEntity.Status = 'Available';
|
|
3120
3189
|
needToSaveConvo = true;
|
|
3121
3190
|
}
|
|
3122
|
-
|
|
3191
|
+
|
|
3123
3192
|
// Save if any changes were made
|
|
3124
3193
|
if (needToSaveConvo) {
|
|
3125
3194
|
const convoEntitySaveResult: boolean = await convoEntity.Save();
|
|
@@ -3180,44 +3249,49 @@ export class AskSkipResolver {
|
|
|
3180
3249
|
};
|
|
3181
3250
|
}
|
|
3182
3251
|
|
|
3183
|
-
private async setConversationStatus(
|
|
3252
|
+
private async setConversationStatus(
|
|
3253
|
+
convoEntity: ConversationEntity,
|
|
3254
|
+
status: 'Processing' | 'Available',
|
|
3255
|
+
userPayload: UserPayload,
|
|
3256
|
+
pubSub?: PubSubEngine
|
|
3257
|
+
): Promise<boolean> {
|
|
3184
3258
|
if (convoEntity.Status !== status) {
|
|
3185
|
-
|
|
3259
|
+
convoEntity.Status = status;
|
|
3186
3260
|
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3261
|
+
const convoSaveResult = await convoEntity.Save();
|
|
3262
|
+
if (!convoSaveResult) {
|
|
3263
|
+
LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
|
|
3264
|
+
} else {
|
|
3265
|
+
// If conversation is now Available (completed), remove it from active streams
|
|
3266
|
+
if (status === 'Available') {
|
|
3267
|
+
activeStreams.removeConversation(convoEntity.ID);
|
|
3268
|
+
LogStatus(`Removed conversation ${convoEntity.ID} from active streams (status changed to Available)`);
|
|
3269
|
+
} else if (status === 'Processing') {
|
|
3270
|
+
// If conversation is starting to process, add the session to active streams
|
|
3271
|
+
activeStreams.addSession(convoEntity.ID, userPayload.sessionId);
|
|
3272
|
+
LogStatus(`Added session ${userPayload.sessionId} to active streams for conversation ${convoEntity.ID}`);
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
if (pubSub) {
|
|
3276
|
+
// Publish status update to notify frontend of conversation status change
|
|
3277
|
+
const statusMessage = {
|
|
3278
|
+
type: 'ConversationStatusUpdate',
|
|
3279
|
+
conversationID: convoEntity.ID,
|
|
3280
|
+
status: status,
|
|
3281
|
+
timestamp: new Date().toISOString(),
|
|
3282
|
+
};
|
|
3283
|
+
|
|
3284
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
3285
|
+
pushStatusUpdates: {
|
|
3286
|
+
message: JSON.stringify(statusMessage),
|
|
3287
|
+
sessionId: userPayload.sessionId,
|
|
3288
|
+
},
|
|
3289
|
+
});
|
|
3290
|
+
|
|
3291
|
+
LogStatus(`Published conversation status update for ${convoEntity.ID}: ${status}`);
|
|
3214
3292
|
}
|
|
3215
|
-
});
|
|
3216
|
-
|
|
3217
|
-
LogStatus(`Published conversation status update for ${convoEntity.ID}: ${status}`);
|
|
3218
3293
|
}
|
|
3219
|
-
|
|
3220
|
-
return convoSaveResult;
|
|
3294
|
+
return convoSaveResult;
|
|
3221
3295
|
}
|
|
3222
3296
|
return true;
|
|
3223
3297
|
}
|
|
@@ -3225,19 +3299,20 @@ export class AskSkipResolver {
|
|
|
3225
3299
|
/**
|
|
3226
3300
|
* Gets the ID of an agent note type by its name
|
|
3227
3301
|
* Falls back to a default note type if the specified one is not found
|
|
3228
|
-
*
|
|
3302
|
+
*
|
|
3229
3303
|
* @param name Name of the agent note type
|
|
3230
3304
|
* @param defaultNoteType Default note type to use if the specified one is not found
|
|
3231
3305
|
* @returns ID of the agent note type
|
|
3232
3306
|
*/
|
|
3233
3307
|
protected getAgentNoteTypeIDByName(name: string, defaultNoteType: string = 'AI'): string {
|
|
3234
|
-
const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
|
|
3235
|
-
if (noteTypeID) {
|
|
3308
|
+
const noteTypeID = AIEngine.Instance.AgentNoteTypes.find((nt) => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
|
|
3309
|
+
if (noteTypeID) {
|
|
3236
3310
|
return noteTypeID;
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3311
|
+
} else {
|
|
3312
|
+
// default
|
|
3313
|
+
const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(
|
|
3314
|
+
(nt) => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase()
|
|
3315
|
+
)?.ID;
|
|
3241
3316
|
return defaultNoteTypeID;
|
|
3242
3317
|
}
|
|
3243
3318
|
}
|
|
@@ -3245,7 +3320,7 @@ export class AskSkipResolver {
|
|
|
3245
3320
|
/**
|
|
3246
3321
|
* Gets data from a view
|
|
3247
3322
|
* Helper method to run a view and retrieve its data
|
|
3248
|
-
*
|
|
3323
|
+
*
|
|
3249
3324
|
* @param ViewId ID of the view to run
|
|
3250
3325
|
* @param user User context for the query
|
|
3251
3326
|
* @returns Results of the view query
|
|
@@ -3260,7 +3335,7 @@ export class AskSkipResolver {
|
|
|
3260
3335
|
// /**
|
|
3261
3336
|
// * Manually executes the Skip AI learning cycle
|
|
3262
3337
|
// * Allows triggering a learning cycle on demand rather than waiting for scheduled execution
|
|
3263
|
-
// *
|
|
3338
|
+
// *
|
|
3264
3339
|
// * @param OrganizationId Optional organization ID to register for this run
|
|
3265
3340
|
// * @returns Result of the manual learning cycle execution
|
|
3266
3341
|
// */
|
|
@@ -3278,7 +3353,7 @@ export class AskSkipResolver {
|
|
|
3278
3353
|
// Message: 'Learning cycles are not enabled in configuration'
|
|
3279
3354
|
// };
|
|
3280
3355
|
// }
|
|
3281
|
-
|
|
3356
|
+
|
|
3282
3357
|
// // Check if we have a valid endpoint when cycles are enabled
|
|
3283
3358
|
// const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
|
|
3284
3359
|
// (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
|
|
@@ -3288,17 +3363,17 @@ export class AskSkipResolver {
|
|
|
3288
3363
|
// Message: 'Learning cycle API endpoint is not configured'
|
|
3289
3364
|
// };
|
|
3290
3365
|
// }
|
|
3291
|
-
|
|
3366
|
+
|
|
3292
3367
|
// // Use the organization ID from config if not provided
|
|
3293
3368
|
// const orgId = OrganizationId || skipConfigInfo.orgID;
|
|
3294
|
-
|
|
3369
|
+
|
|
3295
3370
|
// // Call the scheduler's manual execution method with org ID
|
|
3296
3371
|
// const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
|
|
3297
|
-
|
|
3372
|
+
|
|
3298
3373
|
// return {
|
|
3299
3374
|
// Success: result,
|
|
3300
|
-
// Message: result
|
|
3301
|
-
// ? `Learning cycle was successfully executed manually for organization ${orgId}`
|
|
3375
|
+
// Message: result
|
|
3376
|
+
// ? `Learning cycle was successfully executed manually for organization ${orgId}`
|
|
3302
3377
|
// : `Learning cycle execution failed for organization ${orgId}. Check server logs for details.`
|
|
3303
3378
|
// };
|
|
3304
3379
|
// }
|
|
@@ -3310,18 +3385,18 @@ export class AskSkipResolver {
|
|
|
3310
3385
|
// };
|
|
3311
3386
|
// }
|
|
3312
3387
|
// }
|
|
3313
|
-
|
|
3388
|
+
|
|
3314
3389
|
// /**
|
|
3315
3390
|
// * Gets the current status of the learning cycle scheduler
|
|
3316
3391
|
// * Provides information about the scheduler state and any running cycles
|
|
3317
|
-
// *
|
|
3392
|
+
// *
|
|
3318
3393
|
// * @returns Status information about the learning cycle scheduler
|
|
3319
3394
|
// */
|
|
3320
3395
|
// @Query(() => LearningCycleStatusType)
|
|
3321
3396
|
// async GetLearningCycleStatus(): Promise<LearningCycleStatusType> {
|
|
3322
3397
|
// try {
|
|
3323
3398
|
// const status = LearningCycleScheduler.Instance.getStatus();
|
|
3324
|
-
|
|
3399
|
+
|
|
3325
3400
|
// return {
|
|
3326
3401
|
// IsSchedulerRunning: status.isSchedulerRunning,
|
|
3327
3402
|
// LastRunTime: status.lastRunTime ? status.lastRunTime.toISOString() : null,
|
|
@@ -3342,11 +3417,11 @@ export class AskSkipResolver {
|
|
|
3342
3417
|
// };
|
|
3343
3418
|
// }
|
|
3344
3419
|
// }
|
|
3345
|
-
|
|
3420
|
+
|
|
3346
3421
|
// /**
|
|
3347
3422
|
// * Checks if a specific organization is running a learning cycle
|
|
3348
3423
|
// * Used to determine if a new learning cycle can be started for an organization
|
|
3349
|
-
// *
|
|
3424
|
+
// *
|
|
3350
3425
|
// * @param OrganizationId The organization ID to check
|
|
3351
3426
|
// * @returns Information about the running cycle, or null if no cycle is running
|
|
3352
3427
|
// */
|
|
@@ -3358,13 +3433,13 @@ export class AskSkipResolver {
|
|
|
3358
3433
|
// const skipConfigInfo = configInfo.askSkip;
|
|
3359
3434
|
// // Use the organization ID from config if not provided
|
|
3360
3435
|
// const orgId = OrganizationId || skipConfigInfo.orgID;
|
|
3361
|
-
|
|
3436
|
+
|
|
3362
3437
|
// const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
|
|
3363
|
-
|
|
3438
|
+
|
|
3364
3439
|
// if (!status.isRunning) {
|
|
3365
3440
|
// return null;
|
|
3366
3441
|
// }
|
|
3367
|
-
|
|
3442
|
+
|
|
3368
3443
|
// return {
|
|
3369
3444
|
// OrganizationId: orgId,
|
|
3370
3445
|
// LearningCycleId: status.learningCycleId,
|
|
@@ -3377,11 +3452,11 @@ export class AskSkipResolver {
|
|
|
3377
3452
|
// return null;
|
|
3378
3453
|
// }
|
|
3379
3454
|
// }
|
|
3380
|
-
|
|
3455
|
+
|
|
3381
3456
|
// /**
|
|
3382
3457
|
// * Stops a running learning cycle for a specific organization
|
|
3383
3458
|
// * Allows manual intervention to stop a learning cycle that is taking too long or causing issues
|
|
3384
|
-
// *
|
|
3459
|
+
// *
|
|
3385
3460
|
// * @param OrganizationId The organization ID to stop the cycle for
|
|
3386
3461
|
// * @returns Result of the stop operation, including details about the stopped cycle
|
|
3387
3462
|
// */
|
|
@@ -3392,9 +3467,9 @@ export class AskSkipResolver {
|
|
|
3392
3467
|
// try {
|
|
3393
3468
|
// // Use the organization ID from config if not provided
|
|
3394
3469
|
// const orgId = OrganizationId || configInfo.askSkip.orgID;
|
|
3395
|
-
|
|
3470
|
+
|
|
3396
3471
|
// const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
|
|
3397
|
-
|
|
3472
|
+
|
|
3398
3473
|
// // Transform the result to match our GraphQL type
|
|
3399
3474
|
// return {
|
|
3400
3475
|
// Success: result.success,
|
|
@@ -3417,7 +3492,6 @@ export class AskSkipResolver {
|
|
|
3417
3492
|
// };
|
|
3418
3493
|
// }
|
|
3419
3494
|
// }
|
|
3420
|
-
|
|
3421
3495
|
}
|
|
3422
3496
|
|
|
3423
3497
|
export default AskSkipResolver;
|