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