@memberjunction/server 2.43.0 → 2.45.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/README.md +228 -2
- package/dist/generated/generated.d.ts +227 -5
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +1409 -29
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +79 -48
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ReportResolver.d.ts.map +1 -1
- package/dist/resolvers/ReportResolver.js +2 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +19 -0
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -0
- package/dist/resolvers/RunAIPromptResolver.js +188 -0
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -0
- package/dist/resolvers/RunTemplateResolver.d.ts +14 -0
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -0
- package/dist/resolvers/RunTemplateResolver.js +138 -0
- package/dist/resolvers/RunTemplateResolver.js.map +1 -0
- package/package.json +23 -22
- package/src/generated/generated.ts +880 -21
- package/src/generic/ResolverBase.ts +2 -1
- package/src/index.ts +2 -0
- package/src/resolvers/AskSkipResolver.ts +120 -76
- package/src/resolvers/ReportResolver.ts +4 -1
- package/src/resolvers/RunAIPromptResolver.ts +169 -0
- package/src/resolvers/RunTemplateResolver.ts +130 -0
|
@@ -742,6 +742,7 @@ export class ResolverBase {
|
|
|
742
742
|
if (await entityObject.Save()) {
|
|
743
743
|
// save worked, fire afterevent and return all the data
|
|
744
744
|
await this.AfterUpdate(dataSource, input); // fire event
|
|
745
|
+
|
|
745
746
|
return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
|
|
746
747
|
} else {
|
|
747
748
|
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
@@ -753,7 +754,7 @@ export class ResolverBase {
|
|
|
753
754
|
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
754
755
|
});
|
|
755
756
|
}
|
|
756
|
-
|
|
757
|
+
|
|
757
758
|
/**
|
|
758
759
|
* This routine compares the OldValues property in the input object to the values in the DB that we just loaded. If there are differences, we need to check to see if the client
|
|
759
760
|
* is trying to update any of those fields (e.g. overlap). If there is overlap, we throw an error. If there is no overlap, we can proceed with the update even if the DB Values
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,8 @@ export { TokenExpiredError, getSystemUser } from './auth/index.js';
|
|
|
52
52
|
export * from './generic/PushStatusResolver.js';
|
|
53
53
|
export * from './generic/ResolverBase.js';
|
|
54
54
|
export * from './generic/RunViewResolver.js';
|
|
55
|
+
export * from './resolvers/RunTemplateResolver.js';
|
|
56
|
+
export * from './resolvers/RunAIPromptResolver.js';
|
|
55
57
|
export * from './generic/KeyValuePairInput.js';
|
|
56
58
|
export * from './generic/KeyInputOutputTypes.js';
|
|
57
59
|
export * from './generic/DeleteOptionsInput.js';
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
DataContextEntity,
|
|
52
52
|
DataContextItemEntity,
|
|
53
53
|
UserNotificationEntity,
|
|
54
|
+
AIAgentEntityExtended
|
|
54
55
|
} from '@memberjunction/core-entities';
|
|
55
56
|
import { DataSource } from 'typeorm';
|
|
56
57
|
import { apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
|
|
@@ -60,7 +61,7 @@ import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
|
|
|
60
61
|
import { sendPostRequest } from '../util.js';
|
|
61
62
|
import { GetAIAPIKey } from '@memberjunction/ai';
|
|
62
63
|
import { CompositeKeyInputType } from '../generic/KeyInputOutputTypes.js';
|
|
63
|
-
import {
|
|
64
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
64
65
|
import { deleteAccessToken, GetDataAccessToken, registerAccessToken, tokenExists } from './GetDataResolver.js';
|
|
65
66
|
import e from 'express';
|
|
66
67
|
|
|
@@ -414,7 +415,7 @@ export class AskSkipResolver {
|
|
|
414
415
|
conversationDetailID: convoDetailEntity.ID,
|
|
415
416
|
});
|
|
416
417
|
|
|
417
|
-
return this.handleSimpleSkipChatPostRequest(input, convoEntity
|
|
418
|
+
return this.handleSimpleSkipChatPostRequest(input, convoEntity, convoDetailEntity, true, user);
|
|
418
419
|
}
|
|
419
420
|
|
|
420
421
|
/**
|
|
@@ -641,51 +642,69 @@ export class AskSkipResolver {
|
|
|
641
642
|
* Sends the chat request and processes the response
|
|
642
643
|
*
|
|
643
644
|
* @param input The chat request payload
|
|
644
|
-
* @param
|
|
645
|
-
* @param
|
|
645
|
+
* @param convoEntity The conversation entity object
|
|
646
|
+
* @param convoDetailEntity The conversation detail entity object
|
|
646
647
|
* @param createAIMessageConversationDetail Whether to create a conversation detail for the AI response
|
|
647
648
|
* @param user User context for the request
|
|
648
649
|
* @returns Result of the Skip interaction
|
|
649
650
|
*/
|
|
650
651
|
protected async handleSimpleSkipChatPostRequest(
|
|
651
652
|
input: SkipAPIRequest,
|
|
652
|
-
|
|
653
|
-
|
|
653
|
+
convoEntity: ConversationEntity = null,
|
|
654
|
+
convoDetailEntity: ConversationDetailEntity = null,
|
|
654
655
|
createAIMessageConversationDetail: boolean = false,
|
|
655
656
|
user: UserInfo = null
|
|
656
657
|
): Promise<AskSkipResultType> {
|
|
657
658
|
const skipConfigInfo = configInfo.askSkip;
|
|
658
659
|
LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
|
|
659
660
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
661
|
+
try {
|
|
662
|
+
const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, null);
|
|
663
|
+
|
|
664
|
+
if (response && response.length > 0) {
|
|
665
|
+
// the last object in the response array is the final response from the Skip API
|
|
666
|
+
const apiResponse = <SkipAPIResponse>response[response.length - 1].value;
|
|
667
|
+
const AIMessageConversationDetailID = createAIMessageConversationDetail && convoEntity
|
|
668
|
+
? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user)
|
|
669
|
+
: '';
|
|
670
|
+
// const apiResponse = <SkipAPIResponse>response.data;
|
|
671
|
+
LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
|
|
672
|
+
return {
|
|
673
|
+
Success: true,
|
|
674
|
+
Status: 'OK',
|
|
675
|
+
ResponsePhase: SkipResponsePhase.AnalysisComplete,
|
|
676
|
+
ConversationId: convoEntity ? convoEntity.ID : '',
|
|
677
|
+
UserMessageConversationDetailId: convoDetailEntity ? convoDetailEntity.ID : '',
|
|
678
|
+
AIMessageConversationDetailId: AIMessageConversationDetailID,
|
|
679
|
+
Result: JSON.stringify(apiResponse),
|
|
680
|
+
};
|
|
681
|
+
} else {
|
|
682
|
+
// Set conversation status to Available on failure so user can try again (if conversation exists)
|
|
683
|
+
if (convoEntity) {
|
|
684
|
+
await this.setConversationStatus(convoEntity, 'Available');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
Success: false,
|
|
689
|
+
Status: 'Error',
|
|
690
|
+
Result: `Request failed`,
|
|
691
|
+
ResponsePhase: SkipResponsePhase.AnalysisComplete,
|
|
692
|
+
ConversationId: convoEntity ? convoEntity.ID : '',
|
|
693
|
+
UserMessageConversationDetailId: convoDetailEntity ? convoDetailEntity.ID : '',
|
|
694
|
+
AIMessageConversationDetailId: '',
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
// Set conversation status to Available on error so user can try again (if conversation exists)
|
|
699
|
+
if (convoEntity) {
|
|
700
|
+
await this.setConversationStatus(convoEntity, 'Available');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Log the error for debugging
|
|
704
|
+
LogError(`Error in handleSimpleSkipChatPostRequest: ${error}`);
|
|
705
|
+
|
|
706
|
+
// Re-throw the error to propagate it up the stack
|
|
707
|
+
throw error;
|
|
689
708
|
}
|
|
690
709
|
}
|
|
691
710
|
|
|
@@ -1343,7 +1362,7 @@ cycle.`);
|
|
|
1343
1362
|
);
|
|
1344
1363
|
|
|
1345
1364
|
// Set the conversation status to 'Processing' when a request is initiated
|
|
1346
|
-
this.setConversationStatus(convoEntity, 'Processing');
|
|
1365
|
+
await this.setConversationStatus(convoEntity, 'Processing');
|
|
1347
1366
|
|
|
1348
1367
|
// now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
|
|
1349
1368
|
const messages: SkipMessage[] = await this.LoadConversationDetailsIntoSkipMessages(
|
|
@@ -1641,7 +1660,8 @@ cycle.`);
|
|
|
1641
1660
|
|
|
1642
1661
|
// get the list of entities
|
|
1643
1662
|
const entities = md.Entities.filter((e) => {
|
|
1644
|
-
if (
|
|
1663
|
+
if (!configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
|
|
1664
|
+
skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
|
|
1645
1665
|
const sd = e.ScopeDefault?.trim();
|
|
1646
1666
|
if (sd && sd.length > 0) {
|
|
1647
1667
|
const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
|
|
@@ -1941,6 +1961,7 @@ cycle.`);
|
|
|
1941
1961
|
const convoDetailEntity = await md.GetEntityObject<ConversationDetailEntity>('Conversation Details', user);
|
|
1942
1962
|
convoDetailEntity.NewRecord();
|
|
1943
1963
|
convoDetailEntity.ConversationID = ConversationId;
|
|
1964
|
+
convoDetailEntity.UserID = user.ID;
|
|
1944
1965
|
convoDetailEntity.Message = UserQuestion;
|
|
1945
1966
|
convoDetailEntity.Role = 'User';
|
|
1946
1967
|
convoDetailEntity.HiddenToUser = false;
|
|
@@ -2141,38 +2162,61 @@ cycle.`);
|
|
|
2141
2162
|
};
|
|
2142
2163
|
}
|
|
2143
2164
|
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2165
|
+
let response;
|
|
2166
|
+
try {
|
|
2167
|
+
response = await sendPostRequest(
|
|
2168
|
+
skipConfigInfo.chatURL,
|
|
2169
|
+
input,
|
|
2170
|
+
true,
|
|
2171
|
+
null,
|
|
2172
|
+
(message: {
|
|
2173
|
+
type: string;
|
|
2174
|
+
value: {
|
|
2175
|
+
success: boolean;
|
|
2176
|
+
error: string;
|
|
2177
|
+
responsePhase: string;
|
|
2178
|
+
messages: {
|
|
2179
|
+
role: string;
|
|
2180
|
+
content: string;
|
|
2181
|
+
}[];
|
|
2182
|
+
};
|
|
2183
|
+
}) => {
|
|
2184
|
+
LogStatus(JSON.stringify(message, null, 4));
|
|
2185
|
+
if (message.type === 'status_update') {
|
|
2186
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
2187
|
+
message: JSON.stringify({
|
|
2188
|
+
type: 'AskSkip',
|
|
2189
|
+
status: 'OK',
|
|
2190
|
+
conversationID: ConversationId,
|
|
2191
|
+
ResponsePhase: message.value.responsePhase,
|
|
2192
|
+
message: message.value.messages[0].content,
|
|
2193
|
+
}),
|
|
2194
|
+
sessionId: userPayload.sessionId,
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2173
2197
|
}
|
|
2174
|
-
|
|
2175
|
-
)
|
|
2198
|
+
);
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
// Set conversation status to Available on error so user can try again
|
|
2201
|
+
await this.setConversationStatus(convoEntity, 'Available');
|
|
2202
|
+
|
|
2203
|
+
// Log the error for debugging
|
|
2204
|
+
LogError(`Error in HandleSkipChatRequest sendPostRequest: ${error}`);
|
|
2205
|
+
|
|
2206
|
+
// Publish error status update to user
|
|
2207
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
2208
|
+
message: JSON.stringify({
|
|
2209
|
+
type: 'AskSkip',
|
|
2210
|
+
status: 'Error',
|
|
2211
|
+
conversationID: ConversationId,
|
|
2212
|
+
message: 'Request failed. Please try again later and if this continues, contact your support desk.',
|
|
2213
|
+
}),
|
|
2214
|
+
sessionId: userPayload.sessionId,
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
// Re-throw the error to propagate it up the stack
|
|
2218
|
+
throw error;
|
|
2219
|
+
}
|
|
2176
2220
|
|
|
2177
2221
|
if (response && response.length > 0) {
|
|
2178
2222
|
// response.status === 200) {
|
|
@@ -2409,7 +2453,7 @@ cycle.`);
|
|
|
2409
2453
|
convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
|
|
2410
2454
|
|
|
2411
2455
|
// Set conversation status back to Available since we need user input for the clarifying question
|
|
2412
|
-
this.setConversationStatus(convoEntity, 'Available');
|
|
2456
|
+
await this.setConversationStatus(convoEntity, 'Available');
|
|
2413
2457
|
|
|
2414
2458
|
if (await convoDetailEntityAI.Save()) {
|
|
2415
2459
|
return {
|
|
@@ -2637,7 +2681,7 @@ cycle.`);
|
|
|
2637
2681
|
dataSource: DataSource,
|
|
2638
2682
|
startTime: Date
|
|
2639
2683
|
): Promise<{ AIMessageConversationDetailID: string }> {
|
|
2640
|
-
const sTitle = apiResponse.
|
|
2684
|
+
const sTitle = apiResponse.title;
|
|
2641
2685
|
const sResult = JSON.stringify(apiResponse);
|
|
2642
2686
|
|
|
2643
2687
|
// first up, let's see if Skip asked us to create an artifact or add a new version to an existing artifact, or NOT
|
|
@@ -2804,12 +2848,12 @@ cycle.`);
|
|
|
2804
2848
|
|
|
2805
2849
|
private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available'): Promise<boolean> {
|
|
2806
2850
|
if (convoEntity.Status !== status) {
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2851
|
+
convoEntity.Status = status;
|
|
2852
|
+
const convoSaveResult = await convoEntity.Save();
|
|
2853
|
+
if (!convoSaveResult) {
|
|
2854
|
+
LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
|
|
2855
|
+
}
|
|
2856
|
+
return convoSaveResult;
|
|
2813
2857
|
}
|
|
2814
2858
|
return true;
|
|
2815
2859
|
}
|
|
@@ -96,7 +96,10 @@ export class ReportResolverExtended {
|
|
|
96
96
|
|
|
97
97
|
const report = await md.GetEntityObject<ReportEntity>('Reports', u);
|
|
98
98
|
report.NewRecord();
|
|
99
|
-
report
|
|
99
|
+
// support the legacy report title as old conversation details had a reportTitle property
|
|
100
|
+
// but the new SkipData object has a title property, so favor the title property
|
|
101
|
+
const title = skipData.title ? skipData.title : skipData.reportTitle ? skipData.reportTitle : 'Untitled Report';
|
|
102
|
+
report.Name = title;
|
|
100
103
|
report.Description = skipData.userExplanation ? skipData.userExplanation : '';
|
|
101
104
|
report.ConversationID = result[0].ConversationID;
|
|
102
105
|
report.ConversationDetailID = ConversationDetailID;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field } from 'type-graphql';
|
|
2
|
+
import { UserPayload } from '../types.js';
|
|
3
|
+
import { LogError, LogStatus, Metadata } from '@memberjunction/core';
|
|
4
|
+
import { AIPromptEntity } from '@memberjunction/core-entities';
|
|
5
|
+
import { AIPromptRunner, AIPromptParams } from '@memberjunction/ai-prompts';
|
|
6
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
|
+
|
|
8
|
+
@ObjectType()
|
|
9
|
+
export class AIPromptRunResult {
|
|
10
|
+
@Field()
|
|
11
|
+
success: boolean;
|
|
12
|
+
|
|
13
|
+
@Field({ nullable: true })
|
|
14
|
+
output?: string;
|
|
15
|
+
|
|
16
|
+
@Field({ nullable: true })
|
|
17
|
+
parsedResult?: string;
|
|
18
|
+
|
|
19
|
+
@Field({ nullable: true })
|
|
20
|
+
error?: string;
|
|
21
|
+
|
|
22
|
+
@Field({ nullable: true })
|
|
23
|
+
executionTimeMs?: number;
|
|
24
|
+
|
|
25
|
+
@Field({ nullable: true })
|
|
26
|
+
tokensUsed?: number;
|
|
27
|
+
|
|
28
|
+
@Field({ nullable: true })
|
|
29
|
+
promptRunId?: string;
|
|
30
|
+
|
|
31
|
+
@Field({ nullable: true })
|
|
32
|
+
rawResult?: string;
|
|
33
|
+
|
|
34
|
+
@Field({ nullable: true })
|
|
35
|
+
validationResult?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Resolver()
|
|
39
|
+
export class RunAIPromptResolver extends ResolverBase {
|
|
40
|
+
@Mutation(() => AIPromptRunResult)
|
|
41
|
+
async RunAIPrompt(
|
|
42
|
+
@Arg('promptId') promptId: string,
|
|
43
|
+
@Ctx() { userPayload }: { userPayload: UserPayload },
|
|
44
|
+
@Arg('data', { nullable: true }) data?: string,
|
|
45
|
+
@Arg('modelId', { nullable: true }) modelId?: string,
|
|
46
|
+
@Arg('vendorId', { nullable: true }) vendorId?: string,
|
|
47
|
+
@Arg('configurationId', { nullable: true }) configurationId?: string,
|
|
48
|
+
@Arg('skipValidation', { nullable: true }) skipValidation?: boolean,
|
|
49
|
+
@Arg('templateData', { nullable: true }) templateData?: string
|
|
50
|
+
): Promise<AIPromptRunResult> {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
LogStatus(`=== RUNNING AI PROMPT FOR ID: ${promptId} ===`);
|
|
55
|
+
|
|
56
|
+
// Parse data contexts (JSON strings)
|
|
57
|
+
let parsedData = {};
|
|
58
|
+
let parsedTemplateData = {};
|
|
59
|
+
|
|
60
|
+
if (data) {
|
|
61
|
+
try {
|
|
62
|
+
parsedData = JSON.parse(data);
|
|
63
|
+
} catch (parseError) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `Invalid JSON in data: ${(parseError as Error).message}`,
|
|
67
|
+
executionTimeMs: Date.now() - startTime
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (templateData) {
|
|
73
|
+
try {
|
|
74
|
+
parsedTemplateData = JSON.parse(templateData);
|
|
75
|
+
} catch (parseError) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: `Invalid JSON in template data: ${(parseError as Error).message}`,
|
|
79
|
+
executionTimeMs: Date.now() - startTime
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get current user from payload
|
|
85
|
+
const currentUser = this.GetUserFromPayload(userPayload);
|
|
86
|
+
if (!currentUser) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: 'Unable to determine current user',
|
|
90
|
+
executionTimeMs: Date.now() - startTime
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const md = new Metadata();
|
|
95
|
+
|
|
96
|
+
// Load the AI prompt entity
|
|
97
|
+
const promptEntity = await md.GetEntityObject<AIPromptEntity>('AI Prompts', currentUser);
|
|
98
|
+
await promptEntity.Load(promptId);
|
|
99
|
+
|
|
100
|
+
if (!promptEntity.IsSaved) {
|
|
101
|
+
return {
|
|
102
|
+
success: false,
|
|
103
|
+
error: `AI Prompt with ID ${promptId} not found`,
|
|
104
|
+
executionTimeMs: Date.now() - startTime
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if prompt is active
|
|
109
|
+
if (promptEntity.Status !== 'Active') {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: `AI Prompt "${promptEntity.Name}" is not active (Status: ${promptEntity.Status})`,
|
|
113
|
+
executionTimeMs: Date.now() - startTime
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Create AI prompt runner and execute
|
|
118
|
+
const promptRunner = new AIPromptRunner();
|
|
119
|
+
|
|
120
|
+
// Build execution parameters
|
|
121
|
+
const promptParams = new AIPromptParams();
|
|
122
|
+
promptParams.prompt = promptEntity;
|
|
123
|
+
promptParams.data = parsedData;
|
|
124
|
+
promptParams.templateData = parsedTemplateData;
|
|
125
|
+
promptParams.modelId = modelId;
|
|
126
|
+
promptParams.vendorId = vendorId;
|
|
127
|
+
promptParams.configurationId = configurationId;
|
|
128
|
+
promptParams.contextUser = currentUser;
|
|
129
|
+
promptParams.skipValidation = skipValidation || false;
|
|
130
|
+
|
|
131
|
+
// Execute the prompt
|
|
132
|
+
const result = await promptRunner.ExecutePrompt(promptParams);
|
|
133
|
+
|
|
134
|
+
const executionTime = Date.now() - startTime;
|
|
135
|
+
|
|
136
|
+
if (result.success) {
|
|
137
|
+
LogStatus(`=== AI PROMPT RUN COMPLETED FOR: ${promptEntity.Name} (${executionTime}ms) ===`);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
output: result.rawResult,
|
|
142
|
+
parsedResult: typeof result.result === 'string' ? result.result : JSON.stringify(result.result),
|
|
143
|
+
rawResult: result.rawResult,
|
|
144
|
+
executionTimeMs: executionTime,
|
|
145
|
+
tokensUsed: result.tokensUsed,
|
|
146
|
+
promptRunId: result.promptRun?.ID,
|
|
147
|
+
validationResult: result.validationResult ? JSON.stringify(result.validationResult) : undefined
|
|
148
|
+
};
|
|
149
|
+
} else {
|
|
150
|
+
LogError(`AI Prompt run failed for ${promptEntity.Name}: ${result.errorMessage}`);
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: result.errorMessage,
|
|
154
|
+
executionTimeMs: executionTime,
|
|
155
|
+
promptRunId: result.promptRun?.ID
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const executionTime = Date.now() - startTime;
|
|
161
|
+
LogError(`AI Prompt run failed:`, undefined, error);
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
error: (error as Error).message || 'Unknown error occurred',
|
|
165
|
+
executionTimeMs: executionTime
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field } from 'type-graphql';
|
|
2
|
+
import { UserPayload } from '../types.js';
|
|
3
|
+
import { LogError, LogStatus, Metadata, RunView } from '@memberjunction/core';
|
|
4
|
+
import { TemplateContentEntity } from '@memberjunction/core-entities';
|
|
5
|
+
import { TemplateEngineServer } from '@memberjunction/templates';
|
|
6
|
+
import { TemplateEntityExtended } from '@memberjunction/templates-base-types';
|
|
7
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
8
|
+
|
|
9
|
+
@ObjectType()
|
|
10
|
+
export class TemplateRunResult {
|
|
11
|
+
@Field()
|
|
12
|
+
success: boolean;
|
|
13
|
+
|
|
14
|
+
@Field({ nullable: true })
|
|
15
|
+
output?: string;
|
|
16
|
+
|
|
17
|
+
@Field({ nullable: true })
|
|
18
|
+
error?: string;
|
|
19
|
+
|
|
20
|
+
@Field({ nullable: true })
|
|
21
|
+
executionTimeMs?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Resolver()
|
|
25
|
+
export class RunTemplateResolver extends ResolverBase {
|
|
26
|
+
@Mutation(() => TemplateRunResult)
|
|
27
|
+
async RunTemplate(
|
|
28
|
+
@Arg('templateId') templateId: string,
|
|
29
|
+
@Ctx() { userPayload }: { userPayload: UserPayload },
|
|
30
|
+
@Arg('contextData', { nullable: true }) contextData?: string
|
|
31
|
+
): Promise<TemplateRunResult> {
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
LogStatus(`=== RUNNING TEMPLATE FOR ID: ${templateId} ===`);
|
|
36
|
+
|
|
37
|
+
// Parse context data (JSON string)
|
|
38
|
+
let data = {};
|
|
39
|
+
if (contextData) {
|
|
40
|
+
try {
|
|
41
|
+
data = JSON.parse(contextData);
|
|
42
|
+
} catch (parseError) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: `Invalid JSON in context data: ${(parseError as Error).message}`,
|
|
46
|
+
executionTimeMs: Date.now() - startTime
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get current user from payload
|
|
52
|
+
const currentUser = this.GetUserFromPayload(userPayload);
|
|
53
|
+
if (!currentUser) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: 'Unable to determine current user',
|
|
57
|
+
executionTimeMs: Date.now() - startTime
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const md = new Metadata();
|
|
62
|
+
|
|
63
|
+
// Load the template entity
|
|
64
|
+
const templateEntity = await md.GetEntityObject<TemplateEntityExtended>('Templates', currentUser);
|
|
65
|
+
await templateEntity.Load(templateId);
|
|
66
|
+
|
|
67
|
+
if (!templateEntity.IsSaved) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Template with ID ${templateId} not found`,
|
|
71
|
+
executionTimeMs: Date.now() - startTime
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Load template content (get the first/highest priority content)
|
|
76
|
+
const rv = new RunView();
|
|
77
|
+
const templateContentResult = await rv.RunView<TemplateContentEntity>({
|
|
78
|
+
EntityName: 'Template Contents',
|
|
79
|
+
ExtraFilter: `TemplateID = '${templateId}'`,
|
|
80
|
+
OrderBy: 'Priority ASC',
|
|
81
|
+
MaxRows: 1,
|
|
82
|
+
ResultType: 'entity_object'
|
|
83
|
+
}, currentUser);
|
|
84
|
+
|
|
85
|
+
if (!templateContentResult.Results || templateContentResult.Results.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: `No template content found for template ${templateEntity.Name}`,
|
|
89
|
+
executionTimeMs: Date.now() - startTime
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Configure and render the template
|
|
94
|
+
await TemplateEngineServer.Instance.Config(true /*always refresh to get latest templates*/, currentUser);
|
|
95
|
+
const result = await TemplateEngineServer.Instance.RenderTemplate(
|
|
96
|
+
templateEntity,
|
|
97
|
+
templateContentResult.Results[0],
|
|
98
|
+
data,
|
|
99
|
+
true // skip validation for execution
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const executionTime = Date.now() - startTime;
|
|
103
|
+
|
|
104
|
+
if (result.Success) {
|
|
105
|
+
LogStatus(`=== TEMPLATE RUN COMPLETED FOR: ${templateEntity.Name} (${executionTime}ms) ===`);
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
output: result.Output,
|
|
109
|
+
executionTimeMs: executionTime
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
LogError(`Template run failed for ${templateEntity.Name}: ${result.Message}`);
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: result.Message,
|
|
116
|
+
executionTimeMs: executionTime
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const executionTime = Date.now() - startTime;
|
|
122
|
+
LogError(`Template run failed:`, undefined, error);
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: (error as Error).message || 'Unknown error occurred',
|
|
126
|
+
executionTimeMs: executionTime
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|