@memberjunction/codegen-lib 2.115.0 → 2.117.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/Angular/angular-codegen.d.ts +50 -3
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +364 -118
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Angular/related-entity-components.d.ts +6 -0
- package/dist/Angular/related-entity-components.d.ts.map +1 -1
- package/dist/Angular/related-entity-components.js +7 -0
- package/dist/Angular/related-entity-components.js.map +1 -1
- package/dist/Angular/user-view-grid-related-entity-component.d.ts +1 -1
- package/dist/Angular/user-view-grid-related-entity-component.d.ts.map +1 -1
- package/dist/Angular/user-view-grid-related-entity-component.js +14 -12
- package/dist/Angular/user-view-grid-related-entity-component.js.map +1 -1
- package/dist/Config/config.d.ts +0 -18
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +2 -2
- package/dist/Config/config.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +48 -7
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +383 -146
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/sql.d.ts.map +1 -1
- package/dist/Database/sql.js +134 -28
- package/dist/Database/sql.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +3 -2
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +5 -2
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +80 -22
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +248 -142
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +6 -1
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +7 -0
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +10 -10
|
@@ -38,7 +38,6 @@ const uuid_1 = require("uuid");
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path_1 = __importDefault(require("path"));
|
|
40
40
|
const sql_logging_1 = require("../Misc/sql_logging");
|
|
41
|
-
const aiengine_1 = require("@memberjunction/aiengine");
|
|
42
41
|
class ValidatorResult {
|
|
43
42
|
entityName = "";
|
|
44
43
|
fieldName;
|
|
@@ -99,7 +98,7 @@ class ManageMetadataBase {
|
|
|
99
98
|
let bSuccess = true;
|
|
100
99
|
let start = new Date();
|
|
101
100
|
(0, status_logging_1.logStatus)(' Creating new entities...');
|
|
102
|
-
if (!await this.createNewEntities(pool)) {
|
|
101
|
+
if (!await this.createNewEntities(pool, currentUser)) {
|
|
103
102
|
(0, status_logging_1.logError)(' Error creating new entities');
|
|
104
103
|
bSuccess = false;
|
|
105
104
|
}
|
|
@@ -133,7 +132,8 @@ class ManageMetadataBase {
|
|
|
133
132
|
(0, status_logging_1.logStatus)(` > Recompiled base views in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
134
133
|
start = new Date();
|
|
135
134
|
(0, status_logging_1.logStatus)(' Managing entity fields...');
|
|
136
|
-
|
|
135
|
+
// note that we skip Advanced Generation here because we do it again later when the manageSQLScriptsAndExecution occurs in SQLCodeGen class
|
|
136
|
+
if (!await this.manageEntityFields(pool, excludeSchemas, false, false, currentUser, true)) {
|
|
137
137
|
(0, status_logging_1.logError)(' Error managing entity fields');
|
|
138
138
|
bSuccess = false;
|
|
139
139
|
}
|
|
@@ -146,13 +146,20 @@ class ManageMetadataBase {
|
|
|
146
146
|
}
|
|
147
147
|
(0, status_logging_1.logStatus)(` > Managed entity relationships in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
148
148
|
if (ManageMetadataBase.newEntityList.length > 0) {
|
|
149
|
-
await this.generateNewEntityDescriptions(pool, md); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
|
|
149
|
+
await this.generateNewEntityDescriptions(pool, md, currentUser); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
|
|
150
150
|
}
|
|
151
151
|
const veResult = await this.manageVirtualEntities(pool);
|
|
152
152
|
if (!veResult.success) {
|
|
153
153
|
(0, status_logging_1.logError)(' Error managing virtual entities');
|
|
154
154
|
bSuccess = false;
|
|
155
155
|
}
|
|
156
|
+
start = new Date();
|
|
157
|
+
(0, status_logging_1.logStatus)(' Syncing schema info from database...');
|
|
158
|
+
if (!await this.updateSchemaInfoFromDatabase(pool, excludeSchemas)) {
|
|
159
|
+
(0, status_logging_1.logError)(' Error syncing schema info');
|
|
160
|
+
bSuccess = false;
|
|
161
|
+
}
|
|
162
|
+
(0, status_logging_1.logStatus)(` > Synced schema info in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
156
163
|
return bSuccess;
|
|
157
164
|
}
|
|
158
165
|
async manageVirtualEntities(pool) {
|
|
@@ -484,7 +491,7 @@ class ManageMetadataBase {
|
|
|
484
491
|
* @param excludeSchemas
|
|
485
492
|
* @returns
|
|
486
493
|
*/
|
|
487
|
-
async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser) {
|
|
494
|
+
async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration) {
|
|
488
495
|
let bSuccess = true;
|
|
489
496
|
const startTime = new Date();
|
|
490
497
|
if (!skipCreatedAtUpdatedAtDeletedAtFieldValidation) {
|
|
@@ -540,6 +547,15 @@ class ManageMetadataBase {
|
|
|
540
547
|
}
|
|
541
548
|
(0, status_logging_1.logStatus)(` Managed entity field values in ${(new Date().getTime() - step6StartTime.getTime()) / 1000} seconds`);
|
|
542
549
|
}
|
|
550
|
+
// Advanced Generation - Smart field identification and form layout
|
|
551
|
+
if (!skipAdvancedGeneration) {
|
|
552
|
+
const step7StartTime = new Date();
|
|
553
|
+
if (!await this.applyAdvancedGeneration(pool, excludeSchemas, currentUser)) {
|
|
554
|
+
(0, status_logging_1.logError)('Error applying advanced generation features');
|
|
555
|
+
// Don't fail the entire process - advanced generation is optional
|
|
556
|
+
}
|
|
557
|
+
(0, status_logging_1.logStatus)(` Applied advanced generation features in ${(new Date().getTime() - step7StartTime.getTime()) / 1000} seconds`);
|
|
558
|
+
}
|
|
543
559
|
(0, status_logging_1.logStatus)(` Total time to manage entity fields: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
544
560
|
return bSuccess;
|
|
545
561
|
}
|
|
@@ -740,58 +756,26 @@ class ManageMetadataBase {
|
|
|
740
756
|
* to be invoked, the EntityDescriptions feature must be enabled in the Advanced Generation configuration.
|
|
741
757
|
* @param pool
|
|
742
758
|
* @param md
|
|
759
|
+
* @param currentUser
|
|
743
760
|
*/
|
|
744
|
-
async generateNewEntityDescriptions(pool, md) {
|
|
761
|
+
async generateNewEntityDescriptions(pool, md, currentUser) {
|
|
745
762
|
// for the list of new entities, go through and attempt to generate new entity descriptions
|
|
746
763
|
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
747
764
|
if (ag.featureEnabled('EntityDescriptions')) {
|
|
748
765
|
// we have the feature enabled, so let's loop through the new entities and generate descriptions for them
|
|
749
|
-
const llm = ag.LLM;
|
|
750
|
-
const prompt = ag.getPrompt('EntityDescriptions');
|
|
751
|
-
const systemPrompt = prompt.systemPrompt;
|
|
752
|
-
const userMessage = prompt.userMessage + '\n\n';
|
|
753
|
-
// now loop through the new entities and generate descriptions for them
|
|
754
766
|
for (let e of ManageMetadataBase.newEntityList) {
|
|
755
767
|
const dataResult = await pool.request().query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntities WHERE Name = '${e}'`);
|
|
756
768
|
const data = dataResult.recordset;
|
|
757
769
|
const fieldsResult = await pool.request().query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntityFields WHERE EntityID='${data[0].ID}'`);
|
|
758
770
|
const fields = fieldsResult.recordset;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const result = await llm.ChatCompletion({
|
|
765
|
-
model: ag.AIModel,
|
|
766
|
-
messages: [
|
|
767
|
-
{
|
|
768
|
-
role: 'system',
|
|
769
|
-
content: systemPrompt
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
role: 'user',
|
|
773
|
-
content: entityUserMessage
|
|
774
|
-
}
|
|
775
|
-
]
|
|
776
|
-
});
|
|
777
|
-
if (result?.success) {
|
|
778
|
-
const resultText = result?.data.choices[0].message.content;
|
|
779
|
-
try {
|
|
780
|
-
const structuredResult = JSON.parse(resultText);
|
|
781
|
-
if (structuredResult?.entityDescription && structuredResult.entityDescription.length > 0) {
|
|
782
|
-
const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].Entity SET Description = '${structuredResult.entityDescription}' WHERE Name = '${e}'`;
|
|
783
|
-
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
786
|
-
console.warn(' >>> Advanced Generation Error: LLM returned a blank entity description, skipping entity description for entity ' + e);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
catch (e) {
|
|
790
|
-
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e + '. Result from LLM: ' + resultText, e);
|
|
791
|
-
}
|
|
771
|
+
// Use new API to generate entity description
|
|
772
|
+
const result = await ag.generateEntityDescription(e, data[0].BaseTable, fields.map((f) => ({ Name: f.Name, Type: f.Type, IsNullable: f.AllowsNull, Description: f.Description })), currentUser);
|
|
773
|
+
if (result?.entityDescription && result.entityDescription.length > 0) {
|
|
774
|
+
const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].Entity SET Description = '${result.entityDescription}' WHERE Name = '${e}'`;
|
|
775
|
+
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
|
|
792
776
|
}
|
|
793
777
|
else {
|
|
794
|
-
console.warn(' >>> Advanced Generation Error: LLM
|
|
778
|
+
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e);
|
|
795
779
|
}
|
|
796
780
|
}
|
|
797
781
|
}
|
|
@@ -980,10 +964,13 @@ NumberedRows AS (
|
|
|
980
964
|
* @returns
|
|
981
965
|
*/
|
|
982
966
|
getPendingEntityFieldINSERTSQL(newEntityFieldUUID, n) {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
967
|
+
// DefaultInView logic: Include name fields and early sequence fields, but EXCLUDE primary keys and foreign keys
|
|
968
|
+
// Primary keys (ID) and foreign keys are UUIDs that aren't useful for end users
|
|
969
|
+
const isPrimaryKey = n.FieldName?.trim().toLowerCase() === 'id';
|
|
970
|
+
const isForeignKey = n.RelatedEntityID && n.RelatedEntityID.length > 0; // Foreign keys have RelatedEntityID set
|
|
971
|
+
const isNameField = n.FieldName?.trim().toLowerCase() === 'name' || n.IsNameField;
|
|
972
|
+
const isEarlySequence = n.Sequence <= config_1.configInfo.newEntityDefaults?.IncludeFirstNFieldsAsDefaultInView;
|
|
973
|
+
const bDefaultInView = (isNameField || isEarlySequence) && !isPrimaryKey && !isForeignKey;
|
|
987
974
|
const escapedDescription = n.Description ? `'${n.Description.replace(/'/g, "''")}'` : 'NULL';
|
|
988
975
|
let fieldDisplayName = '';
|
|
989
976
|
switch (n.FieldName.trim().toLowerCase()) {
|
|
@@ -1197,6 +1184,28 @@ NumberedRows AS (
|
|
|
1197
1184
|
return false;
|
|
1198
1185
|
}
|
|
1199
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Syncs SchemaInfo records from database schemas, capturing extended properties as descriptions.
|
|
1189
|
+
* Creates new SchemaInfo records for schemas that don't exist yet and updates descriptions
|
|
1190
|
+
* from schema extended properties for existing records.
|
|
1191
|
+
* @param pool - SQL connection pool
|
|
1192
|
+
* @param excludeSchemas - Array of schema names to exclude from processing
|
|
1193
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
1194
|
+
*/
|
|
1195
|
+
async updateSchemaInfoFromDatabase(pool, excludeSchemas) {
|
|
1196
|
+
try {
|
|
1197
|
+
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spUpdateSchemaInfoFromDatabase @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1198
|
+
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
|
|
1199
|
+
if (result && result.length > 0) {
|
|
1200
|
+
(0, status_logging_1.logStatus)(` > Updated/created ${result.length} SchemaInfo records`);
|
|
1201
|
+
}
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
catch (e) {
|
|
1205
|
+
(0, status_logging_1.logError)(e);
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1200
1209
|
async deleteUnneededEntityFields(pool, excludeSchemas) {
|
|
1201
1210
|
try {
|
|
1202
1211
|
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
@@ -1349,65 +1358,21 @@ NumberedRows AS (
|
|
|
1349
1358
|
}
|
|
1350
1359
|
try {
|
|
1351
1360
|
if (generateNewCode && config_1.configInfo.advancedGeneration?.enableAdvancedGeneration && config_1.configInfo.advancedGeneration?.features.find(f => f.name === 'ParseCheckConstraints' && f.enabled)) {
|
|
1352
|
-
// feature is enabled, so let's call the
|
|
1361
|
+
// feature is enabled, so let's call the AI to generate a function for us
|
|
1353
1362
|
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
1354
|
-
const llm = ag.LLM;
|
|
1355
|
-
if (!llm) {
|
|
1356
|
-
// we weren't able to get an LLM instance, so log an error and return
|
|
1357
|
-
(0, status_logging_1.logError)(' >>> Error generating validator function from check constraint. Unable to get an LLM instance.');
|
|
1358
|
-
return returnResult;
|
|
1359
|
-
}
|
|
1360
|
-
await aiengine_1.AIEngine.Instance.Config(false, currentUser); // make sure metadata loaded
|
|
1361
|
-
const model = aiengine_1.AIEngine.Instance.Models.find(m => {
|
|
1362
|
-
const modelMatch = m.APINameOrName.trim().toLowerCase() === ag.AIModel.trim().toLowerCase();
|
|
1363
|
-
if (!modelMatch)
|
|
1364
|
-
return false;
|
|
1365
|
-
// Check if model has a vendor matching the specified vendor
|
|
1366
|
-
const hasVendor = aiengine_1.AIEngine.Instance.ModelVendors.some(mv => mv.ModelID === m.ID &&
|
|
1367
|
-
mv.Vendor.trim().toLowerCase() === ag.AIVendor.trim().toLowerCase() &&
|
|
1368
|
-
mv.Status === 'Active');
|
|
1369
|
-
return hasVendor;
|
|
1370
|
-
});
|
|
1371
|
-
if (!model)
|
|
1372
|
-
throw new Error(` >>> Error generating validator function from check constraint. Unable to find AI Model with name ${ag.AIModel} and vendor ${ag.AIVendor}.`);
|
|
1373
|
-
const prompt = ag.getPrompt('CheckConstraintParser');
|
|
1374
1363
|
const entityFieldListInfo = allEntityFields.filter(item => item.Entity.trim().toLowerCase() === data.EntityName.trim().toLowerCase()).map(item => ` * ${item.Name} - ${item.Type}${item.AllowsNull ? ' (nullable)' : ' (not null)'}`).join('\n');
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
content: markedUpSysPrompt
|
|
1385
|
-
},
|
|
1386
|
-
{
|
|
1387
|
-
role: 'user',
|
|
1388
|
-
content: `${constraintDefinition}`
|
|
1389
|
-
}
|
|
1390
|
-
],
|
|
1391
|
-
model: ag.AIModel,
|
|
1392
|
-
responseFormat: 'JSON'
|
|
1393
|
-
});
|
|
1394
|
-
if (result && result.success) {
|
|
1395
|
-
const jsonResult = result.data.choices[0].message.content;
|
|
1396
|
-
const structuredResult = (0, global_1.SafeJSONParse)(jsonResult);
|
|
1397
|
-
if (structuredResult?.Description && structuredResult?.Code && structuredResult?.MethodName) {
|
|
1398
|
-
returnResult.functionText = structuredResult.Code;
|
|
1399
|
-
returnResult.functionName = structuredResult.MethodName;
|
|
1400
|
-
returnResult.functionDescription = structuredResult.Description;
|
|
1401
|
-
returnResult.aiModelID = model.ID;
|
|
1402
|
-
returnResult.wasGenerated = true; // we just generated this code
|
|
1403
|
-
returnResult.success = true;
|
|
1404
|
-
}
|
|
1405
|
-
else {
|
|
1406
|
-
(0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
|
|
1407
|
-
}
|
|
1364
|
+
// Use new API to parse check constraint
|
|
1365
|
+
const result = await ag.parseCheckConstraint(constraintDefinition, entityFieldListInfo, generatedValidationFunctionName, currentUser);
|
|
1366
|
+
if (result?.Description && result?.Code && result?.MethodName) {
|
|
1367
|
+
returnResult.functionText = result.Code;
|
|
1368
|
+
returnResult.functionName = result.MethodName;
|
|
1369
|
+
returnResult.functionDescription = result.Description;
|
|
1370
|
+
returnResult.aiModelID = result.ModelID;
|
|
1371
|
+
returnResult.wasGenerated = true; // we just generated this code
|
|
1372
|
+
returnResult.success = true;
|
|
1408
1373
|
}
|
|
1409
1374
|
else {
|
|
1410
|
-
(0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM
|
|
1375
|
+
(0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
|
|
1411
1376
|
}
|
|
1412
1377
|
}
|
|
1413
1378
|
}
|
|
@@ -1552,7 +1517,7 @@ NumberedRows AS (
|
|
|
1552
1517
|
(sExcludeSchemas.length > 0 ? (sExcludeTables.length > 0 ? ` AND ` : ``) + '(' + sExcludeSchemas + ')' : '');
|
|
1553
1518
|
return sWhere;
|
|
1554
1519
|
}
|
|
1555
|
-
async createNewEntities(pool) {
|
|
1520
|
+
async createNewEntities(pool, currentUser) {
|
|
1556
1521
|
try {
|
|
1557
1522
|
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + this.createExcludeTablesAndSchemasFilter('');
|
|
1558
1523
|
const newEntitiesResult = await pool.request().query(sSQL);
|
|
@@ -1565,7 +1530,7 @@ NumberedRows AS (
|
|
|
1565
1530
|
// wrap in a transaction so we get all of it or none of it
|
|
1566
1531
|
for (let i = 0; i < newEntities.length; ++i) {
|
|
1567
1532
|
// process each of the new entities
|
|
1568
|
-
await this.createNewEntity(pool, newEntities[i], md);
|
|
1533
|
+
await this.createNewEntity(pool, newEntities[i], md, currentUser);
|
|
1569
1534
|
}
|
|
1570
1535
|
await transaction.commit();
|
|
1571
1536
|
}
|
|
@@ -1606,10 +1571,10 @@ NumberedRows AS (
|
|
|
1606
1571
|
return { shouldCreate: false, validationMessage: errorMsg };
|
|
1607
1572
|
}
|
|
1608
1573
|
}
|
|
1609
|
-
async createNewEntityName(newEntity) {
|
|
1574
|
+
async createNewEntityName(newEntity, currentUser) {
|
|
1610
1575
|
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
1611
1576
|
if (ag.featureEnabled('EntityNames')) {
|
|
1612
|
-
return this.newEntityNameWithAdvancedGeneration(ag, newEntity);
|
|
1577
|
+
return this.newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser);
|
|
1613
1578
|
}
|
|
1614
1579
|
else {
|
|
1615
1580
|
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
@@ -1636,44 +1601,13 @@ NumberedRows AS (
|
|
|
1636
1601
|
}
|
|
1637
1602
|
return null; // nothing to do here, the DisplayName can be null as it will just end up being the entity name
|
|
1638
1603
|
}
|
|
1639
|
-
async newEntityNameWithAdvancedGeneration(ag, newEntity) {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const systemPrompt = ag.fillTemplate(prompt.systemPrompt, newEntity);
|
|
1644
|
-
const userMessage = ag.fillTemplate(prompt.userMessage, newEntity);
|
|
1645
|
-
const result = await chat.ChatCompletion({
|
|
1646
|
-
model: ag.AIModel,
|
|
1647
|
-
messages: [
|
|
1648
|
-
{
|
|
1649
|
-
role: 'system',
|
|
1650
|
-
content: systemPrompt
|
|
1651
|
-
},
|
|
1652
|
-
{
|
|
1653
|
-
role: 'user',
|
|
1654
|
-
content: userMessage
|
|
1655
|
-
}
|
|
1656
|
-
]
|
|
1657
|
-
});
|
|
1658
|
-
if (result?.success) {
|
|
1659
|
-
const resultText = result?.data.choices[0].message.content;
|
|
1660
|
-
try {
|
|
1661
|
-
const structuredResult = JSON.parse(resultText);
|
|
1662
|
-
if (structuredResult?.entityName) {
|
|
1663
|
-
return this.markupEntityName(newEntity.SchemaName, structuredResult.entityName);
|
|
1664
|
-
}
|
|
1665
|
-
else {
|
|
1666
|
-
console.warn(' >>> Advanced Generation Error: LLM returned a blank entity name, falling back to simple generated entity name');
|
|
1667
|
-
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
catch (e) {
|
|
1671
|
-
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name. Result from LLM: ' + resultText, e);
|
|
1672
|
-
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
1673
|
-
}
|
|
1604
|
+
async newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser) {
|
|
1605
|
+
const result = await ag.generateEntityName(newEntity.TableName, currentUser);
|
|
1606
|
+
if (result?.entityName) {
|
|
1607
|
+
return this.markupEntityName(newEntity.SchemaName, result.entityName);
|
|
1674
1608
|
}
|
|
1675
1609
|
else {
|
|
1676
|
-
console.warn(' >>> Advanced Generation Error: LLM
|
|
1610
|
+
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name');
|
|
1677
1611
|
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
1678
1612
|
}
|
|
1679
1613
|
}
|
|
@@ -1712,12 +1646,12 @@ NumberedRows AS (
|
|
|
1712
1646
|
createNewUUID() {
|
|
1713
1647
|
return (0, uuid_1.v4)();
|
|
1714
1648
|
}
|
|
1715
|
-
async createNewEntity(pool, newEntity, md) {
|
|
1649
|
+
async createNewEntity(pool, newEntity, md, currentUser) {
|
|
1716
1650
|
try {
|
|
1717
1651
|
const { shouldCreate, validationMessage } = await this.shouldCreateNewEntity(pool, newEntity);
|
|
1718
1652
|
if (shouldCreate) {
|
|
1719
1653
|
// process a single new entity
|
|
1720
|
-
let newEntityName = await this.createNewEntityName(newEntity);
|
|
1654
|
+
let newEntityName = await this.createNewEntityName(newEntity, currentUser);
|
|
1721
1655
|
const newEntityDisplayName = this.createNewEntityDisplayName(newEntity, newEntityName);
|
|
1722
1656
|
let suffix = '';
|
|
1723
1657
|
const existingEntity = md.Entities.find(e => e.Name.toLowerCase() === newEntityName.toLowerCase());
|
|
@@ -1897,6 +1831,309 @@ NumberedRows AS (
|
|
|
1897
1831
|
`;
|
|
1898
1832
|
return sSQLInsert;
|
|
1899
1833
|
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Apply Advanced Generation features - Smart Field Identification and Form Layout Generation
|
|
1836
|
+
*/
|
|
1837
|
+
async applyAdvancedGeneration(pool, excludeSchemas, currentUser) {
|
|
1838
|
+
try {
|
|
1839
|
+
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
1840
|
+
if (!ag.enabled) {
|
|
1841
|
+
return true;
|
|
1842
|
+
}
|
|
1843
|
+
// Get list of entities to process
|
|
1844
|
+
// If forceRegeneration.enabled is true, process ALL entities
|
|
1845
|
+
// Otherwise, only process new or modified entities
|
|
1846
|
+
let entitiesToProcess = [];
|
|
1847
|
+
let whereClause = '';
|
|
1848
|
+
if (config_1.configInfo.forceRegeneration?.enabled) {
|
|
1849
|
+
// Force regeneration mode - process all entities (or filtered by entityWhereClause)
|
|
1850
|
+
(0, status_logging_1.logStatus)(` Force regeneration enabled - processing all entities...`);
|
|
1851
|
+
whereClause = 'e.VirtualEntity = 0';
|
|
1852
|
+
if (config_1.configInfo.forceRegeneration.entityWhereClause && config_1.configInfo.forceRegeneration.entityWhereClause.trim().length > 0) {
|
|
1853
|
+
whereClause += ` AND (${config_1.configInfo.forceRegeneration.entityWhereClause})`;
|
|
1854
|
+
(0, status_logging_1.logStatus)(` Filtered by: ${config_1.configInfo.forceRegeneration.entityWhereClause}`);
|
|
1855
|
+
}
|
|
1856
|
+
whereClause += ` AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
// Normal mode - only process new or modified entities
|
|
1860
|
+
// Deduplicate in case an entity appears in both lists
|
|
1861
|
+
entitiesToProcess = [
|
|
1862
|
+
...new Set([
|
|
1863
|
+
...ManageMetadataBase.newEntityList,
|
|
1864
|
+
...ManageMetadataBase.modifiedEntityList
|
|
1865
|
+
])
|
|
1866
|
+
];
|
|
1867
|
+
if (entitiesToProcess.length === 0) {
|
|
1868
|
+
return true;
|
|
1869
|
+
}
|
|
1870
|
+
(0, status_logging_1.logStatus)(` Advanced Generation enabled, processing ${entitiesToProcess.length} entities...`);
|
|
1871
|
+
whereClause = `e.VirtualEntity = 0 AND e.Name IN (${entitiesToProcess.map(name => `'${name}'`).join(',')}) AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1872
|
+
}
|
|
1873
|
+
// Get entity details for entities that need processing
|
|
1874
|
+
const entitiesSQL = `
|
|
1875
|
+
SELECT
|
|
1876
|
+
e.ID,
|
|
1877
|
+
e.Name,
|
|
1878
|
+
e.Description,
|
|
1879
|
+
e.SchemaName,
|
|
1880
|
+
e.BaseTable
|
|
1881
|
+
FROM
|
|
1882
|
+
[${(0, config_1.mj_core_schema)()}].vwEntities e
|
|
1883
|
+
WHERE
|
|
1884
|
+
${whereClause}
|
|
1885
|
+
ORDER BY
|
|
1886
|
+
e.Name
|
|
1887
|
+
`;
|
|
1888
|
+
const entitiesResult = await pool.request().query(entitiesSQL);
|
|
1889
|
+
const entities = entitiesResult.recordset;
|
|
1890
|
+
if (entities.length === 0) {
|
|
1891
|
+
return true;
|
|
1892
|
+
}
|
|
1893
|
+
// Get ALL fields for ALL entities in a single query
|
|
1894
|
+
const entityIds = entities.map((e) => `'${e.ID}'`).join(',');
|
|
1895
|
+
const fieldsSQL = `
|
|
1896
|
+
SELECT
|
|
1897
|
+
ef.EntityID,
|
|
1898
|
+
ef.ID,
|
|
1899
|
+
ef.Name,
|
|
1900
|
+
ef.Type,
|
|
1901
|
+
ef.AllowsNull,
|
|
1902
|
+
ef.IsPrimaryKey,
|
|
1903
|
+
ef.IsUnique,
|
|
1904
|
+
ef.Description,
|
|
1905
|
+
ef.AutoUpdateIsNameField,
|
|
1906
|
+
ef.AutoUpdateDefaultInView,
|
|
1907
|
+
ef.AutoUpdateCategory
|
|
1908
|
+
FROM
|
|
1909
|
+
[${(0, config_1.mj_core_schema)()}].vwEntityFields ef
|
|
1910
|
+
WHERE
|
|
1911
|
+
ef.EntityID IN (${entityIds})
|
|
1912
|
+
ORDER BY
|
|
1913
|
+
ef.EntityID,
|
|
1914
|
+
ef.Sequence
|
|
1915
|
+
`;
|
|
1916
|
+
const fieldsResult = await pool.request().query(fieldsSQL);
|
|
1917
|
+
const allFields = fieldsResult.recordset;
|
|
1918
|
+
// Process entities in batches with parallelization
|
|
1919
|
+
return await this.processEntitiesBatched(pool, entities, allFields, ag, currentUser);
|
|
1920
|
+
}
|
|
1921
|
+
catch (error) {
|
|
1922
|
+
(0, status_logging_1.logError)(`Advanced Generation failed: ${error}`);
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Process entities in batches with parallel execution
|
|
1928
|
+
* @param pool Database connection pool
|
|
1929
|
+
* @param entities Entities to process
|
|
1930
|
+
* @param allFields All fields for all entities (will be filtered per entity)
|
|
1931
|
+
* @param ag AdvancedGeneration instance
|
|
1932
|
+
* @param currentUser User context
|
|
1933
|
+
* @param batchSize Number of entities to process in parallel (default 5)
|
|
1934
|
+
*/
|
|
1935
|
+
async processEntitiesBatched(pool, entities, allFields, ag, currentUser, batchSize = 5) {
|
|
1936
|
+
let processedCount = 0;
|
|
1937
|
+
let errorCount = 0;
|
|
1938
|
+
// Process in batches
|
|
1939
|
+
for (let i = 0; i < entities.length; i += batchSize) {
|
|
1940
|
+
const batch = entities.slice(i, i + batchSize);
|
|
1941
|
+
// Process batch in parallel
|
|
1942
|
+
const batchResults = await Promise.allSettled(batch.map(entity => this.processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser)));
|
|
1943
|
+
// Tally results
|
|
1944
|
+
for (const result of batchResults) {
|
|
1945
|
+
if (result.status === 'fulfilled') {
|
|
1946
|
+
processedCount++;
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
errorCount++;
|
|
1950
|
+
(0, status_logging_1.logError)(` Error processing entity: ${result.reason}`);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
(0, status_logging_1.logStatus)(` Progress: ${processedCount}/${entities.length} entities processed`);
|
|
1954
|
+
}
|
|
1955
|
+
(0, status_logging_1.logStatus)(` Advanced Generation complete: ${processedCount} entities processed, ${errorCount} errors`);
|
|
1956
|
+
return errorCount === 0;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Process advanced generation for a single entity
|
|
1960
|
+
* @param pool Database connection pool
|
|
1961
|
+
* @param entity Entity to process
|
|
1962
|
+
* @param allFields All fields for all entities (will be filtered for this entity)
|
|
1963
|
+
* @param ag AdvancedGeneration instance
|
|
1964
|
+
* @param currentUser User context
|
|
1965
|
+
*/
|
|
1966
|
+
async processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser) {
|
|
1967
|
+
// Filter fields for this entity (client-side filtering)
|
|
1968
|
+
const fields = allFields.filter((f) => f.EntityID === entity.ID);
|
|
1969
|
+
// Smart Field Identification
|
|
1970
|
+
// Only run if at least one field allows auto-update
|
|
1971
|
+
if (fields.some((f) => f.AutoUpdateIsNameField || f.AutoUpdateDefaultInView)) {
|
|
1972
|
+
const fieldAnalysis = await ag.identifyFields({
|
|
1973
|
+
Name: entity.Name,
|
|
1974
|
+
Description: entity.Description,
|
|
1975
|
+
Fields: fields
|
|
1976
|
+
}, currentUser);
|
|
1977
|
+
if (fieldAnalysis) {
|
|
1978
|
+
await this.applySmartFieldIdentification(pool, entity.ID, fields, fieldAnalysis);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
// Form Layout Generation
|
|
1982
|
+
// Only run if at least one field allows auto-update
|
|
1983
|
+
if (fields.some((f) => f.AutoUpdateCategory)) {
|
|
1984
|
+
const layoutAnalysis = await ag.generateFormLayout({
|
|
1985
|
+
Name: entity.Name,
|
|
1986
|
+
Description: entity.Description,
|
|
1987
|
+
Fields: fields
|
|
1988
|
+
}, currentUser);
|
|
1989
|
+
if (layoutAnalysis) {
|
|
1990
|
+
await this.applyFormLayout(pool, entity.ID, fields, layoutAnalysis);
|
|
1991
|
+
(0, status_logging_1.logStatus)(` Applied form layout for ${entity.Name}`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Apply smart field identification results to entity fields
|
|
1997
|
+
*/
|
|
1998
|
+
async applySmartFieldIdentification(pool, entityId, fields, result) {
|
|
1999
|
+
const sqlStatements = [];
|
|
2000
|
+
// Find the name field (exactly one)
|
|
2001
|
+
const nameField = fields.find(f => f.Name === result.nameField);
|
|
2002
|
+
if (nameField && nameField.AutoUpdateIsNameField && nameField.ID) {
|
|
2003
|
+
sqlStatements.push(`
|
|
2004
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2005
|
+
SET IsNameField = 1
|
|
2006
|
+
WHERE ID = '${nameField.ID}'
|
|
2007
|
+
AND AutoUpdateIsNameField = 1
|
|
2008
|
+
`);
|
|
2009
|
+
}
|
|
2010
|
+
else if (!nameField) {
|
|
2011
|
+
(0, status_logging_1.logError)(`Smart field identification returned invalid nameField: '${result.nameField}' not found in entity fields`);
|
|
2012
|
+
}
|
|
2013
|
+
// Find all default in view fields (one or more)
|
|
2014
|
+
const defaultInViewFields = fields.filter(f => result.defaultInView.includes(f.Name) && f.AutoUpdateDefaultInView && f.ID);
|
|
2015
|
+
// Warn about any fields that weren't found
|
|
2016
|
+
const missingFields = result.defaultInView.filter(name => !fields.some(f => f.Name === name));
|
|
2017
|
+
if (missingFields.length > 0) {
|
|
2018
|
+
(0, status_logging_1.logError)(`Smart field identification returned invalid defaultInView fields: ${missingFields.join(', ')} not found in entity`);
|
|
2019
|
+
}
|
|
2020
|
+
// Build update statements for all default in view fields
|
|
2021
|
+
for (const field of defaultInViewFields) {
|
|
2022
|
+
sqlStatements.push(`
|
|
2023
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2024
|
+
SET DefaultInView = 1
|
|
2025
|
+
WHERE ID = '${field.ID}'
|
|
2026
|
+
AND AutoUpdateDefaultInView = 1
|
|
2027
|
+
`);
|
|
2028
|
+
}
|
|
2029
|
+
// Execute all updates in one batch
|
|
2030
|
+
if (sqlStatements.length > 0) {
|
|
2031
|
+
const combinedSQL = sqlStatements.join('\n');
|
|
2032
|
+
await this.LogSQLAndExecute(pool, combinedSQL, `Set field properties for entity`, false);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Apply form layout generation results to set category on entity fields
|
|
2037
|
+
*/
|
|
2038
|
+
async applyFormLayout(pool, entityId, fields, result) {
|
|
2039
|
+
// Collect all SQL statements for batch execution
|
|
2040
|
+
const sqlStatements = [];
|
|
2041
|
+
// Assign category to each field
|
|
2042
|
+
for (const fieldCategory of result.fieldCategories) {
|
|
2043
|
+
const field = fields.find(f => f.Name === fieldCategory.fieldName);
|
|
2044
|
+
if (field && field.AutoUpdateCategory && field.ID) {
|
|
2045
|
+
// Override category to "System Metadata" for __mj_ fields (system audit fields)
|
|
2046
|
+
let category = fieldCategory.category;
|
|
2047
|
+
if (field.Name.startsWith('__mj_')) {
|
|
2048
|
+
category = 'System Metadata';
|
|
2049
|
+
}
|
|
2050
|
+
// Build SET clause with all available metadata
|
|
2051
|
+
const setClauses = [
|
|
2052
|
+
`Category = '${category.replace(/'/g, "''")}'`,
|
|
2053
|
+
`GeneratedFormSection = 'Category'`
|
|
2054
|
+
];
|
|
2055
|
+
// Add DisplayName if provided and field allows auto-update
|
|
2056
|
+
if (fieldCategory.displayName && field.AutoUpdateDisplayName) {
|
|
2057
|
+
setClauses.push(`DisplayName = '${fieldCategory.displayName.replace(/'/g, "''")}'`);
|
|
2058
|
+
}
|
|
2059
|
+
// Add ExtendedType if provided
|
|
2060
|
+
if (fieldCategory.extendedType !== undefined) {
|
|
2061
|
+
const extendedType = fieldCategory.extendedType === null ? 'NULL' : `'${fieldCategory.extendedType.replace(/'/g, "''")}'`;
|
|
2062
|
+
setClauses.push(`ExtendedType = ${extendedType}`);
|
|
2063
|
+
}
|
|
2064
|
+
// Add CodeType if provided
|
|
2065
|
+
if (fieldCategory.codeType !== undefined) {
|
|
2066
|
+
const codeType = fieldCategory.codeType === null ? 'NULL' : `'${fieldCategory.codeType.replace(/'/g, "''")}'`;
|
|
2067
|
+
setClauses.push(`CodeType = ${codeType}`);
|
|
2068
|
+
}
|
|
2069
|
+
const updateSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2070
|
+
SET ${setClauses.join(',\n ')}
|
|
2071
|
+
WHERE ID = '${field.ID}'
|
|
2072
|
+
AND AutoUpdateCategory = 1`;
|
|
2073
|
+
sqlStatements.push(updateSQL);
|
|
2074
|
+
}
|
|
2075
|
+
else if (!field) {
|
|
2076
|
+
(0, status_logging_1.logError)(`Form layout generation returned invalid fieldName: '${fieldCategory.fieldName}' not found in entity`);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
// Execute all field updates in one batch
|
|
2080
|
+
if (sqlStatements.length > 0) {
|
|
2081
|
+
const combinedSQL = sqlStatements.join('\n');
|
|
2082
|
+
await this.LogSQLAndExecute(pool, combinedSQL, `Set categories for ${sqlStatements.length} fields`, false);
|
|
2083
|
+
}
|
|
2084
|
+
// Store entity icon if provided and entity doesn't already have one
|
|
2085
|
+
if (result.entityIcon && result.entityIcon.trim().length > 0) {
|
|
2086
|
+
// Check if entity already has an icon
|
|
2087
|
+
const checkEntitySQL = `
|
|
2088
|
+
SELECT Icon FROM [${(0, config_1.mj_core_schema)()}].Entity
|
|
2089
|
+
WHERE ID = '${entityId}'
|
|
2090
|
+
`;
|
|
2091
|
+
const entityCheck = await pool.request().query(checkEntitySQL);
|
|
2092
|
+
if (entityCheck.recordset.length > 0) {
|
|
2093
|
+
const currentIcon = entityCheck.recordset[0].Icon;
|
|
2094
|
+
// Only update if entity doesn't have an icon set
|
|
2095
|
+
if (!currentIcon || currentIcon.trim().length === 0) {
|
|
2096
|
+
const escapedIcon = result.entityIcon.replace(/'/g, "''");
|
|
2097
|
+
const updateEntitySQL = `
|
|
2098
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].Entity
|
|
2099
|
+
SET Icon = '${escapedIcon}',
|
|
2100
|
+
__mj_UpdatedAt = GETUTCDATE()
|
|
2101
|
+
WHERE ID = '${entityId}'
|
|
2102
|
+
`;
|
|
2103
|
+
await this.LogSQLAndExecute(pool, updateEntitySQL, `Set entity icon to ${result.entityIcon}`, false);
|
|
2104
|
+
(0, status_logging_1.logStatus)(` ✓ Set entity icon: ${result.entityIcon}`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
// Store category icons in EntitySetting if provided
|
|
2109
|
+
if (result.categoryIcons && Object.keys(result.categoryIcons).length > 0) {
|
|
2110
|
+
const iconsJSON = JSON.stringify(result.categoryIcons).replace(/'/g, "''");
|
|
2111
|
+
// First check if setting already exists
|
|
2112
|
+
const checkSQL = `
|
|
2113
|
+
SELECT ID FROM [${(0, config_1.mj_core_schema)()}].EntitySetting
|
|
2114
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
2115
|
+
`;
|
|
2116
|
+
const existing = await pool.request().query(checkSQL);
|
|
2117
|
+
if (existing.recordset.length > 0) {
|
|
2118
|
+
// Update existing setting
|
|
2119
|
+
const updateSQL = `
|
|
2120
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntitySetting
|
|
2121
|
+
SET Value = '${iconsJSON}',
|
|
2122
|
+
__mj_UpdatedAt = GETUTCDATE()
|
|
2123
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
2124
|
+
`;
|
|
2125
|
+
await this.LogSQLAndExecute(pool, updateSQL, `Update FieldCategoryIcons setting for entity`, false);
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
// Insert new setting
|
|
2129
|
+
const insertSQL = `
|
|
2130
|
+
INSERT INTO [${(0, config_1.mj_core_schema)()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
2131
|
+
VALUES (NEWID(), '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', GETUTCDATE(), GETUTCDATE())
|
|
2132
|
+
`;
|
|
2133
|
+
await this.LogSQLAndExecute(pool, insertSQL, `Insert FieldCategoryIcons setting for entity`, false);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
1900
2137
|
/**
|
|
1901
2138
|
* Executes the given SQL query using the given ConnectionPool object.
|
|
1902
2139
|
* If the appendToLogFile parameter is true, the query will also be appended to the log file.
|