@memberjunction/server 2.36.1 → 2.37.1
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/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +23 -15
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +138 -73
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +24 -1
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +18 -16
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +169 -19
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.js +3 -4
- package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
- package/package.json +22 -22
- package/src/config.ts +8 -0
- package/src/generated/generated.ts +92 -53
- package/src/generic/ResolverBase.ts +25 -1
- package/src/resolvers/AskSkipResolver.ts +233 -25
- package/src/scheduler/LearningCycleScheduler.ts +7 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Arg, Ctx, Field,
|
|
2
|
-
import { LogError, LogStatus, Metadata,
|
|
1
|
+
import { Arg, Ctx, Field, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
|
|
2
|
+
import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo } from '@memberjunction/core';
|
|
3
3
|
import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
|
|
4
4
|
import { BehaviorSubject } from 'rxjs';
|
|
5
5
|
import { take } from 'rxjs/operators';
|
|
@@ -32,6 +32,9 @@ import {
|
|
|
32
32
|
SkipConversation,
|
|
33
33
|
SkipAPIArtifact,
|
|
34
34
|
SkipAPIAgentRequest,
|
|
35
|
+
SkipAPIArtifactRequest,
|
|
36
|
+
SkipAPIArtifactType,
|
|
37
|
+
SkipAPIArtifactVersion,
|
|
35
38
|
} from '@memberjunction/skip-types';
|
|
36
39
|
|
|
37
40
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
@@ -40,6 +43,9 @@ import {
|
|
|
40
43
|
AIAgentLearningCycleEntity,
|
|
41
44
|
AIAgentNoteEntity,
|
|
42
45
|
AIAgentRequestEntity,
|
|
46
|
+
ArtifactTypeEntity,
|
|
47
|
+
ConversationArtifactEntity,
|
|
48
|
+
ConversationArtifactVersionEntity,
|
|
43
49
|
ConversationDetailEntity,
|
|
44
50
|
ConversationEntity,
|
|
45
51
|
DataContextEntity,
|
|
@@ -47,7 +53,7 @@ import {
|
|
|
47
53
|
UserNotificationEntity,
|
|
48
54
|
} from '@memberjunction/core-entities';
|
|
49
55
|
import { DataSource } from 'typeorm';
|
|
50
|
-
import { ___skipAPIOrgId, ___skipAPIurl, ___skipLearningAPIurl, ___skipLearningCycleIntervalInMinutes, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
|
|
56
|
+
import { ___skipAPIOrgId, ___skipAPIurl, ___skipLearningAPIurl, ___skipLearningCycleIntervalInMinutes, ___skipRunLearningCycles, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
|
|
51
57
|
|
|
52
58
|
import { registerEnumType } from 'type-graphql';
|
|
53
59
|
import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
|
|
@@ -56,6 +62,8 @@ import { GetAIAPIKey } from '@memberjunction/ai';
|
|
|
56
62
|
import { CompositeKeyInputType } from '../generic/KeyInputOutputTypes.js';
|
|
57
63
|
import { AIAgentEntityExtended, AIEngine } from '@memberjunction/aiengine';
|
|
58
64
|
import { deleteAccessToken, GetDataAccessToken, registerAccessToken, tokenExists } from './GetDataResolver.js';
|
|
65
|
+
import e from 'express';
|
|
66
|
+
import { Skip } from '@graphql-tools/utils';
|
|
59
67
|
|
|
60
68
|
enum SkipResponsePhase {
|
|
61
69
|
ClarifyingQuestion = 'clarifying_question',
|
|
@@ -155,6 +163,23 @@ export class StopLearningCycleResultType {
|
|
|
155
163
|
CycleDetails: CycleDetailsType;
|
|
156
164
|
}
|
|
157
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Internally used type
|
|
168
|
+
*/
|
|
169
|
+
type BaseSkipRequest = {
|
|
170
|
+
entities: SkipEntityInfo[],
|
|
171
|
+
queries: SkipQueryInfo[],
|
|
172
|
+
notes: SkipAPIAgentNote[],
|
|
173
|
+
noteTypes: SkipAPIAgentNoteType[],
|
|
174
|
+
requests: SkipAPIAgentRequest[],
|
|
175
|
+
accessToken: GetDataAccessToken,
|
|
176
|
+
organizationID: string,
|
|
177
|
+
organizationInfo: any,
|
|
178
|
+
apiKeys: SkipAPIRequestAPIKey[],
|
|
179
|
+
callingServerURL: string,
|
|
180
|
+
callingServerAPIKey: string,
|
|
181
|
+
callingServerAccessToken: string
|
|
182
|
+
}
|
|
158
183
|
@Resolver(AskSkipResultType)
|
|
159
184
|
export class AskSkipResolver {
|
|
160
185
|
private static _defaultNewChatName = 'New Chat';
|
|
@@ -162,14 +187,23 @@ export class AskSkipResolver {
|
|
|
162
187
|
// Static initializer that runs when the class is loaded - initializes the learning cycle scheduler
|
|
163
188
|
static {
|
|
164
189
|
try {
|
|
165
|
-
LogStatus('Initializing Skip AI Learning Cycle Scheduler');
|
|
166
|
-
|
|
167
190
|
// Set up event listener for server initialization
|
|
168
191
|
const eventListener = MJGlobal.Instance.GetEventListener(true);
|
|
169
192
|
eventListener.subscribe(event => {
|
|
170
193
|
// Filter for our server's setup complete event
|
|
171
194
|
if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
|
|
172
195
|
try {
|
|
196
|
+
if (___skipRunLearningCycles !== 'Y') {
|
|
197
|
+
LogStatus('Skip AI Learning cycle scheduler not started: Disabled in configuration');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if we have a valid endpoint when cycles are enabled
|
|
202
|
+
if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
|
|
203
|
+
LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no API endpoint is configured');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
173
207
|
const dataSources = event.args.dataSources;
|
|
174
208
|
if (dataSources && dataSources.length > 0) {
|
|
175
209
|
// Initialize the scheduler
|
|
@@ -181,7 +215,6 @@ export class AskSkipResolver {
|
|
|
181
215
|
// Default is 60 minutes, if the interval is not set in the config, use 60 minutes
|
|
182
216
|
const interval = ___skipLearningCycleIntervalInMinutes ?? 60;
|
|
183
217
|
scheduler.start(interval);
|
|
184
|
-
LogStatus(`📅 Skip AI Learning cycle scheduler started with ${interval} minute interval`);
|
|
185
218
|
} else {
|
|
186
219
|
LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
|
|
187
220
|
}
|
|
@@ -190,8 +223,6 @@ export class AskSkipResolver {
|
|
|
190
223
|
}
|
|
191
224
|
}
|
|
192
225
|
});
|
|
193
|
-
|
|
194
|
-
LogStatus('Skip AI Learning Cycle Scheduler initialization listener registered');
|
|
195
226
|
} catch (error) {
|
|
196
227
|
// Handle any errors from the static initializer
|
|
197
228
|
LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
|
|
@@ -283,6 +314,30 @@ export class AskSkipResolver {
|
|
|
283
314
|
@Ctx() { dataSource, userPayload }: AppContext,
|
|
284
315
|
@Arg('ForceEntityRefresh', () => Boolean, { nullable: true }) ForceEntityRefresh?: boolean
|
|
285
316
|
) {
|
|
317
|
+
// First check if learning cycles are enabled in configuration
|
|
318
|
+
if (___skipRunLearningCycles !== 'Y') {
|
|
319
|
+
return {
|
|
320
|
+
success: false,
|
|
321
|
+
error: 'Learning cycles are disabled in configuration',
|
|
322
|
+
elapsedTime: 0,
|
|
323
|
+
noteChanges: [],
|
|
324
|
+
queryChanges: [],
|
|
325
|
+
requestChanges: []
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check if we have a valid endpoint when cycles are enabled
|
|
330
|
+
if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
|
|
331
|
+
return {
|
|
332
|
+
success: false,
|
|
333
|
+
error: 'Learning cycle API endpoint is not configured',
|
|
334
|
+
elapsedTime: 0,
|
|
335
|
+
noteChanges: [],
|
|
336
|
+
queryChanges: [],
|
|
337
|
+
requestChanges: []
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
286
341
|
const startTime = new Date();
|
|
287
342
|
// First, get the user from the cache
|
|
288
343
|
const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
|
|
@@ -308,8 +363,6 @@ export class AskSkipResolver {
|
|
|
308
363
|
};
|
|
309
364
|
}
|
|
310
365
|
|
|
311
|
-
LogStatus(`Starting learning cycle for AI agent Skip`);
|
|
312
|
-
|
|
313
366
|
// Get the Skip agent ID
|
|
314
367
|
const md = new Metadata();
|
|
315
368
|
const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
|
|
@@ -342,7 +395,7 @@ export class AskSkipResolver {
|
|
|
342
395
|
try {
|
|
343
396
|
// Build the request to Skip learning API
|
|
344
397
|
LogStatus(`Building Skip Learning API request`);
|
|
345
|
-
const input = await this.buildSkipLearningAPIRequest(learningCycleId, lastCompleteLearningCycleDate, true, true, true,
|
|
398
|
+
const input = await this.buildSkipLearningAPIRequest(learningCycleId, lastCompleteLearningCycleDate, true, true, true, false, dataSource, user, ForceEntityRefresh || false);
|
|
346
399
|
|
|
347
400
|
// Make the API request
|
|
348
401
|
const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
|
|
@@ -625,7 +678,7 @@ cycle.`);
|
|
|
625
678
|
forceEntitiesRefresh: boolean = false,
|
|
626
679
|
includeCallBackKeyAndAccessToken: boolean = false,
|
|
627
680
|
additionalTokenInfo: any = {}
|
|
628
|
-
) {
|
|
681
|
+
): Promise<BaseSkipRequest> {
|
|
629
682
|
|
|
630
683
|
const entities = includeEntities ? await this.BuildSkipEntities(dataSource, forceEntitiesRefresh) : [];
|
|
631
684
|
const queries = includeQueries ? this.BuildSkipQueries() : [];
|
|
@@ -657,7 +710,7 @@ cycle.`);
|
|
|
657
710
|
noteTypes,
|
|
658
711
|
requests,
|
|
659
712
|
accessToken,
|
|
660
|
-
|
|
713
|
+
organizationID: ___skipAPIOrgId,
|
|
661
714
|
organizationInfo: configInfo?.askSkip?.organizationInfo,
|
|
662
715
|
apiKeys: this.buildSkipAPIKeys(),
|
|
663
716
|
callingServerURL: accessToken ? `${baseUrl}:${graphqlPort}` : undefined,
|
|
@@ -698,7 +751,7 @@ cycle.`);
|
|
|
698
751
|
|
|
699
752
|
// Create the learning-specific request object
|
|
700
753
|
const input: SkipAPILearningCycleRequest = {
|
|
701
|
-
organizationId: baseRequest.
|
|
754
|
+
organizationId: baseRequest.organizationID,
|
|
702
755
|
organizationInfo: baseRequest.organizationInfo,
|
|
703
756
|
learningCycleId,
|
|
704
757
|
lastLearningCycleDate,
|
|
@@ -858,23 +911,101 @@ cycle.`);
|
|
|
858
911
|
additionalTokenInfo
|
|
859
912
|
);
|
|
860
913
|
|
|
914
|
+
const artifacts: SkipAPIArtifact[] = await this.buildSkipAPIArtifacts(contextUser, dataSource, conversationId);
|
|
915
|
+
|
|
861
916
|
// Create the chat-specific request object
|
|
862
917
|
const input: SkipAPIRequest = {
|
|
918
|
+
...baseRequest,
|
|
863
919
|
messages,
|
|
864
920
|
conversationID: conversationId.toString(),
|
|
865
921
|
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
|
|
866
|
-
organizationID: baseRequest.organizationId,
|
|
867
922
|
requestPhase,
|
|
868
|
-
|
|
869
|
-
queries: baseRequest.queries,
|
|
870
|
-
notes: baseRequest.notes,
|
|
871
|
-
noteTypes: baseRequest.noteTypes,
|
|
872
|
-
apiKeys: baseRequest.apiKeys,
|
|
923
|
+
artifacts: artifacts
|
|
873
924
|
};
|
|
874
925
|
|
|
875
926
|
return input;
|
|
876
927
|
}
|
|
877
928
|
|
|
929
|
+
/**
|
|
930
|
+
* Builds up an array of SkipAPIArtifact types to send across information about the artifacts associated with this particular
|
|
931
|
+
* conversation.
|
|
932
|
+
* @param contextUser
|
|
933
|
+
* @param dataSource
|
|
934
|
+
* @param conversationId
|
|
935
|
+
* @returns
|
|
936
|
+
*/
|
|
937
|
+
protected async buildSkipAPIArtifacts(contextUser: UserInfo, dataSource: DataSource, conversationId: string): Promise<SkipAPIArtifact[]> {
|
|
938
|
+
const md = new Metadata();
|
|
939
|
+
const ei = md.EntityByName('MJ: Conversation Artifacts');
|
|
940
|
+
const rv = new RunView();
|
|
941
|
+
const results = await rv.RunViews([
|
|
942
|
+
{
|
|
943
|
+
EntityName: "MJ: Conversation Artifacts",
|
|
944
|
+
ExtraFilter: `ConversationID='${conversationId}'`, // get artifacts linked to this convo
|
|
945
|
+
OrderBy: "__mj_CreatedAt"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
EntityName: "MJ: Artifact Types", // get all artifact types
|
|
949
|
+
OrderBy: "Name"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
EntityName: "MJ: Conversation Artifact Versions",
|
|
953
|
+
ExtraFilter: `ConversationArtifactID IN (SELECT ID FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationID='${conversationId}')`,
|
|
954
|
+
OrderBy: 'ConversationArtifactID, __mj_CreatedAt'
|
|
955
|
+
}
|
|
956
|
+
], contextUser);
|
|
957
|
+
if (results && results.length > 0 && results.every((r) => r.Success)) {
|
|
958
|
+
const types: SkipAPIArtifactType[] = results[1].Results.map((a: ArtifactTypeEntity) => {
|
|
959
|
+
const retVal: SkipAPIArtifactType = {
|
|
960
|
+
id: a.ID,
|
|
961
|
+
name: a.Name,
|
|
962
|
+
description: a.Description,
|
|
963
|
+
contentType: a.ContentType,
|
|
964
|
+
enabled: a.IsEnabled,
|
|
965
|
+
createdAt: a.__mj_CreatedAt,
|
|
966
|
+
updatedAt: a.__mj_UpdatedAt
|
|
967
|
+
}
|
|
968
|
+
return retVal;
|
|
969
|
+
});
|
|
970
|
+
const allConvoArtifacts = results[0].Results.map((a: ConversationArtifactEntity) => {
|
|
971
|
+
const rawVersions: ConversationArtifactVersionEntity[] = results[2].Results as ConversationArtifactVersionEntity[];
|
|
972
|
+
const thisArtifactsVersions = rawVersions.filter(rv => rv.ConversationArtifactID === a.ID);
|
|
973
|
+
const versionsForThisArtifact: SkipAPIArtifactVersion[] = thisArtifactsVersions.map((v: ConversationArtifactVersionEntity) => {
|
|
974
|
+
const versionRetVal: SkipAPIArtifactVersion = {
|
|
975
|
+
id: v.ID,
|
|
976
|
+
artifactId: v.ConversationArtifactID,
|
|
977
|
+
version: v.Version,
|
|
978
|
+
configuration: v.Configuration,
|
|
979
|
+
content: v.Content,
|
|
980
|
+
comments: v.Comments,
|
|
981
|
+
createdAt: v.__mj_CreatedAt,
|
|
982
|
+
updatedAt: v.__mj_UpdatedAt
|
|
983
|
+
};
|
|
984
|
+
return versionRetVal;
|
|
985
|
+
});
|
|
986
|
+
const artifactRetVal: SkipAPIArtifact = {
|
|
987
|
+
id: a.ID,
|
|
988
|
+
name: a.Name,
|
|
989
|
+
description: a.Description,
|
|
990
|
+
comments: a.Comments,
|
|
991
|
+
sharingScope: a.SharingScope as 'None' |'SpecificUsers' |'Everyone' |'Public',
|
|
992
|
+
versions: versionsForThisArtifact,
|
|
993
|
+
conversationId: a.ConversationID,
|
|
994
|
+
artifactType: types.find((t => t.id === a.ArtifactTypeID)),
|
|
995
|
+
createdAt: a.__mj_CreatedAt,
|
|
996
|
+
updatedAt: a.__mj_UpdatedAt
|
|
997
|
+
};
|
|
998
|
+
return artifactRetVal;
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
return allConvoArtifacts;
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
|
|
878
1009
|
/**
|
|
879
1010
|
* Executes a script in the context of a data context and returns the results
|
|
880
1011
|
* @param pubSub
|
|
@@ -1452,7 +1583,6 @@ cycle.`);
|
|
|
1452
1583
|
convoDetailEntity.Message = UserQuestion;
|
|
1453
1584
|
convoDetailEntity.Role = 'User';
|
|
1454
1585
|
convoDetailEntity.HiddenToUser = false;
|
|
1455
|
-
convoDetailEntity.Set('Sequence', 1); // using weakly typed here because we're going to get rid of this field soon
|
|
1456
1586
|
let convoDetailSaveResult: boolean = await convoDetailEntity.Save();
|
|
1457
1587
|
if (!convoDetailSaveResult) {
|
|
1458
1588
|
LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
|
|
@@ -1782,7 +1912,8 @@ cycle.`);
|
|
|
1782
1912
|
user,
|
|
1783
1913
|
convoEntity,
|
|
1784
1914
|
pubSub,
|
|
1785
|
-
userPayload
|
|
1915
|
+
userPayload,
|
|
1916
|
+
dataSource
|
|
1786
1917
|
);
|
|
1787
1918
|
const response: AskSkipResultType = {
|
|
1788
1919
|
Success: true,
|
|
@@ -2012,11 +2143,66 @@ cycle.`);
|
|
|
2012
2143
|
user: UserInfo,
|
|
2013
2144
|
convoEntity: ConversationEntity,
|
|
2014
2145
|
pubSub: PubSubEngine,
|
|
2015
|
-
userPayload: UserPayload
|
|
2146
|
+
userPayload: UserPayload,
|
|
2147
|
+
dataSource: DataSource
|
|
2016
2148
|
): Promise<{ AIMessageConversationDetailID: string }> {
|
|
2017
2149
|
const sTitle = apiResponse.reportTitle;
|
|
2018
2150
|
const sResult = JSON.stringify(apiResponse);
|
|
2019
2151
|
|
|
2152
|
+
// first up, let's see if Skip asked us to create an artifact or add a new version to an existing artifact, or NOT
|
|
2153
|
+
// use artifacts at all...
|
|
2154
|
+
let artifactId: string = null;
|
|
2155
|
+
let artifactVersionId: string = null;
|
|
2156
|
+
|
|
2157
|
+
if (apiResponse.artifactRequest?.action === 'new_artifact' || apiResponse.artifactRequest?.action === 'new_artifact_version') {
|
|
2158
|
+
// Skip has requested that we create a new artifact or add a new version to an existing artifact
|
|
2159
|
+
artifactId = apiResponse.artifactRequest.artifactId; // will only be populated if action == new_artifact_version
|
|
2160
|
+
let newVersion: number = 0;
|
|
2161
|
+
if (apiResponse.artifactRequest?.action === 'new_artifact') {
|
|
2162
|
+
const artifactEntity = await md.GetEntityObject<ConversationArtifactEntity>('MJ: Convesration Artifacts', user);
|
|
2163
|
+
// create the new artifact here
|
|
2164
|
+
artifactEntity.NewRecord();
|
|
2165
|
+
artifactEntity.ConversationID = convoEntity.ID;
|
|
2166
|
+
artifactEntity.Name = apiResponse.artifactRequest.name;
|
|
2167
|
+
artifactEntity.Description = apiResponse.artifactRequest.description;
|
|
2168
|
+
if (await artifactEntity.Save()) {
|
|
2169
|
+
// saved, grab the new ID
|
|
2170
|
+
artifactId = artifactEntity.ID;
|
|
2171
|
+
}
|
|
2172
|
+
else {
|
|
2173
|
+
LogError(`Error saving artifact entity for conversation: ${convoEntity.ID}`, undefined, artifactEntity.LatestResult);
|
|
2174
|
+
}
|
|
2175
|
+
newVersion = 1;
|
|
2176
|
+
}
|
|
2177
|
+
else {
|
|
2178
|
+
// we are updating an existing artifact with a new vesrion so we need to get the old max version and increment it
|
|
2179
|
+
const ei = md.EntityByName("MJ: Convesration Artifacts");
|
|
2180
|
+
const sSQL = `SELECT ISNULL(MAX(Version),0) AS MaxVersion FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ID = '${artifactId}'`;
|
|
2181
|
+
const result = await dataSource.query(sSQL);
|
|
2182
|
+
if (result && result.length > 0) {
|
|
2183
|
+
newVersion = result[0].MaxVersion + 1;
|
|
2184
|
+
} else {
|
|
2185
|
+
LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, result);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
if (artifactId && newVersion > 0) {
|
|
2189
|
+
// only do this if we were provided an artifact ID or we saved a new one above successfully
|
|
2190
|
+
const artifactVersionEntity = await md.GetEntityObject<ConversationArtifactVersionEntity>('MJ: Conversation Artifact Versions', user);
|
|
2191
|
+
// create the new artifact version here
|
|
2192
|
+
artifactVersionEntity.NewRecord();
|
|
2193
|
+
artifactVersionEntity.ConversationArtifactID = artifactId;
|
|
2194
|
+
artifactVersionEntity.Version = newVersion;
|
|
2195
|
+
artifactVersionEntity.Configuration = sResult; // store the full response here
|
|
2196
|
+
if (await artifactVersionEntity.Save()) {
|
|
2197
|
+
// success saving the new version, set the artifactVersionId
|
|
2198
|
+
artifactVersionId = artifactVersionEntity.ID;
|
|
2199
|
+
}
|
|
2200
|
+
else {
|
|
2201
|
+
LogError(`Error saving Artifact Version record`)
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2020
2206
|
// Create a conversation detail record for the Skip response
|
|
2021
2207
|
const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
|
|
2022
2208
|
convoDetailEntityAI.NewRecord();
|
|
@@ -2024,7 +2210,13 @@ cycle.`);
|
|
|
2024
2210
|
convoDetailEntityAI.Message = sResult;
|
|
2025
2211
|
convoDetailEntityAI.Role = 'AI';
|
|
2026
2212
|
convoDetailEntityAI.HiddenToUser = false;
|
|
2027
|
-
|
|
2213
|
+
if (artifactId && artifactId.length > 0) {
|
|
2214
|
+
// bind the new convo detail record to the artifact + version for this response
|
|
2215
|
+
convoDetailEntityAI.ArtifactID = artifactId;
|
|
2216
|
+
if (artifactVersionId && artifactVersionId.length > 0) {
|
|
2217
|
+
convoDetailEntityAI.ArtifactVersionID = artifactVersionId;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2028
2220
|
const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
|
|
2029
2221
|
if (!convoDetailSaveResult) {
|
|
2030
2222
|
LogError(`Error saving conversation detail entity for AI message: ${sResult}`, undefined, convoDetailEntityAI.LatestResult);
|
|
@@ -2070,7 +2262,7 @@ cycle.`);
|
|
|
2070
2262
|
// Save the data context items...
|
|
2071
2263
|
// FOR NOW, we don't want to store the data in the database, we will just load it from the data context when we need it
|
|
2072
2264
|
// we need a better strategy to persist because the cost of storage and retrieval/parsing is higher than just running the query again in many/most cases
|
|
2073
|
-
dataContext.SaveItems(user, false);
|
|
2265
|
+
await dataContext.SaveItems(user, false);
|
|
2074
2266
|
|
|
2075
2267
|
// send a UI update trhough pub-sub
|
|
2076
2268
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
@@ -2121,6 +2313,22 @@ cycle.`);
|
|
|
2121
2313
|
try {
|
|
2122
2314
|
LogStatus('Manual execution of Skip learning cycle requested via API');
|
|
2123
2315
|
|
|
2316
|
+
// First check if learning cycles are enabled in configuration
|
|
2317
|
+
if (___skipRunLearningCycles !== 'Y') {
|
|
2318
|
+
return {
|
|
2319
|
+
Success: false,
|
|
2320
|
+
Message: 'Learning cycles are disabled in configuration'
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// Check if we have a valid endpoint when cycles are enabled
|
|
2325
|
+
if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
|
|
2326
|
+
return {
|
|
2327
|
+
Success: false,
|
|
2328
|
+
Message: 'Learning cycle API endpoint is not configured'
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2124
2332
|
// Use the organization ID from config if not provided
|
|
2125
2333
|
const orgId = OrganizationId || ___skipAPIOrgId;
|
|
2126
2334
|
|
|
@@ -39,22 +39,21 @@ export class LearningCycleScheduler extends BaseSingleton<LearningCycleScheduler
|
|
|
39
39
|
/**
|
|
40
40
|
* Start the scheduler with the specified interval in minutes
|
|
41
41
|
* @param intervalMinutes The interval in minutes between runs
|
|
42
|
+
* @param skipLearningAPIurl The URL for the learning cycle API endpoint
|
|
42
43
|
*/
|
|
43
44
|
public start(intervalMinutes: number = 60): void {
|
|
44
|
-
|
|
45
|
-
// start learning cycle immediately upon the server start
|
|
46
|
-
this.runLearningCycle()
|
|
47
|
-
.catch(error => LogError(`Error in initial learning cycle: ${error}`));
|
|
48
|
-
|
|
49
|
-
const intervalMs = intervalMinutes * 60 * 1000;
|
|
50
|
-
|
|
51
45
|
LogStatus(`Starting learning cycle scheduler with interval of ${intervalMinutes} minutes`);
|
|
52
46
|
|
|
53
|
-
//
|
|
47
|
+
// Set up the interval for recurring calls
|
|
48
|
+
const intervalMs = intervalMinutes * 60 * 1000;
|
|
54
49
|
this.intervalId = setInterval(() => {
|
|
55
50
|
this.runLearningCycle()
|
|
56
51
|
.catch(error => LogError(`Error in scheduled learning cycle: ${error}`));
|
|
57
52
|
}, intervalMs);
|
|
53
|
+
|
|
54
|
+
// Start learning cycle immediately upon the server start
|
|
55
|
+
this.runLearningCycle()
|
|
56
|
+
.catch(error => LogError(`Error in initial learning cycle: ${error}`));
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
/**
|
|
@@ -76,8 +75,6 @@ export class LearningCycleScheduler extends BaseSingleton<LearningCycleScheduler
|
|
|
76
75
|
const startTime = new Date();
|
|
77
76
|
|
|
78
77
|
try {
|
|
79
|
-
LogStatus('Starting scheduled learning cycle execution');
|
|
80
|
-
|
|
81
78
|
// Make sure we have data sources
|
|
82
79
|
if (!this.dataSources || this.dataSources.length === 0) {
|
|
83
80
|
throw new Error('No data sources available for the learning cycle');
|