@memberjunction/codegen-lib 2.114.0 → 2.116.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 +448 -93
- 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.map +1 -1
- package/dist/Angular/user-view-grid-related-entity-component.js +12 -4
- 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 +358 -142
- 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 +8 -2
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +77 -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
|
}
|
|
@@ -1197,6 +1181,28 @@ NumberedRows AS (
|
|
|
1197
1181
|
return false;
|
|
1198
1182
|
}
|
|
1199
1183
|
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Syncs SchemaInfo records from database schemas, capturing extended properties as descriptions.
|
|
1186
|
+
* Creates new SchemaInfo records for schemas that don't exist yet and updates descriptions
|
|
1187
|
+
* from schema extended properties for existing records.
|
|
1188
|
+
* @param pool - SQL connection pool
|
|
1189
|
+
* @param excludeSchemas - Array of schema names to exclude from processing
|
|
1190
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
1191
|
+
*/
|
|
1192
|
+
async updateSchemaInfoFromDatabase(pool, excludeSchemas) {
|
|
1193
|
+
try {
|
|
1194
|
+
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spUpdateSchemaInfoFromDatabase @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1195
|
+
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
|
|
1196
|
+
if (result && result.length > 0) {
|
|
1197
|
+
(0, status_logging_1.logStatus)(` > Updated/created ${result.length} SchemaInfo records`);
|
|
1198
|
+
}
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
catch (e) {
|
|
1202
|
+
(0, status_logging_1.logError)(e);
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1200
1206
|
async deleteUnneededEntityFields(pool, excludeSchemas) {
|
|
1201
1207
|
try {
|
|
1202
1208
|
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
@@ -1349,65 +1355,21 @@ NumberedRows AS (
|
|
|
1349
1355
|
}
|
|
1350
1356
|
try {
|
|
1351
1357
|
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
|
|
1358
|
+
// feature is enabled, so let's call the AI to generate a function for us
|
|
1353
1359
|
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
1360
|
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
|
-
}
|
|
1361
|
+
// Use new API to parse check constraint
|
|
1362
|
+
const result = await ag.parseCheckConstraint(constraintDefinition, entityFieldListInfo, generatedValidationFunctionName, currentUser);
|
|
1363
|
+
if (result?.Description && result?.Code && result?.MethodName) {
|
|
1364
|
+
returnResult.functionText = result.Code;
|
|
1365
|
+
returnResult.functionName = result.MethodName;
|
|
1366
|
+
returnResult.functionDescription = result.Description;
|
|
1367
|
+
returnResult.aiModelID = result.ModelID;
|
|
1368
|
+
returnResult.wasGenerated = true; // we just generated this code
|
|
1369
|
+
returnResult.success = true;
|
|
1408
1370
|
}
|
|
1409
1371
|
else {
|
|
1410
|
-
(0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM
|
|
1372
|
+
(0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
|
|
1411
1373
|
}
|
|
1412
1374
|
}
|
|
1413
1375
|
}
|
|
@@ -1552,7 +1514,7 @@ NumberedRows AS (
|
|
|
1552
1514
|
(sExcludeSchemas.length > 0 ? (sExcludeTables.length > 0 ? ` AND ` : ``) + '(' + sExcludeSchemas + ')' : '');
|
|
1553
1515
|
return sWhere;
|
|
1554
1516
|
}
|
|
1555
|
-
async createNewEntities(pool) {
|
|
1517
|
+
async createNewEntities(pool, currentUser) {
|
|
1556
1518
|
try {
|
|
1557
1519
|
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + this.createExcludeTablesAndSchemasFilter('');
|
|
1558
1520
|
const newEntitiesResult = await pool.request().query(sSQL);
|
|
@@ -1565,7 +1527,7 @@ NumberedRows AS (
|
|
|
1565
1527
|
// wrap in a transaction so we get all of it or none of it
|
|
1566
1528
|
for (let i = 0; i < newEntities.length; ++i) {
|
|
1567
1529
|
// process each of the new entities
|
|
1568
|
-
await this.createNewEntity(pool, newEntities[i], md);
|
|
1530
|
+
await this.createNewEntity(pool, newEntities[i], md, currentUser);
|
|
1569
1531
|
}
|
|
1570
1532
|
await transaction.commit();
|
|
1571
1533
|
}
|
|
@@ -1606,10 +1568,10 @@ NumberedRows AS (
|
|
|
1606
1568
|
return { shouldCreate: false, validationMessage: errorMsg };
|
|
1607
1569
|
}
|
|
1608
1570
|
}
|
|
1609
|
-
async createNewEntityName(newEntity) {
|
|
1571
|
+
async createNewEntityName(newEntity, currentUser) {
|
|
1610
1572
|
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
1611
1573
|
if (ag.featureEnabled('EntityNames')) {
|
|
1612
|
-
return this.newEntityNameWithAdvancedGeneration(ag, newEntity);
|
|
1574
|
+
return this.newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser);
|
|
1613
1575
|
}
|
|
1614
1576
|
else {
|
|
1615
1577
|
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
@@ -1636,44 +1598,13 @@ NumberedRows AS (
|
|
|
1636
1598
|
}
|
|
1637
1599
|
return null; // nothing to do here, the DisplayName can be null as it will just end up being the entity name
|
|
1638
1600
|
}
|
|
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
|
-
}
|
|
1601
|
+
async newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser) {
|
|
1602
|
+
const result = await ag.generateEntityName(newEntity.TableName, currentUser);
|
|
1603
|
+
if (result?.entityName) {
|
|
1604
|
+
return this.markupEntityName(newEntity.SchemaName, result.entityName);
|
|
1674
1605
|
}
|
|
1675
1606
|
else {
|
|
1676
|
-
console.warn(' >>> Advanced Generation Error: LLM
|
|
1607
|
+
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name');
|
|
1677
1608
|
return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
|
|
1678
1609
|
}
|
|
1679
1610
|
}
|
|
@@ -1712,12 +1643,12 @@ NumberedRows AS (
|
|
|
1712
1643
|
createNewUUID() {
|
|
1713
1644
|
return (0, uuid_1.v4)();
|
|
1714
1645
|
}
|
|
1715
|
-
async createNewEntity(pool, newEntity, md) {
|
|
1646
|
+
async createNewEntity(pool, newEntity, md, currentUser) {
|
|
1716
1647
|
try {
|
|
1717
1648
|
const { shouldCreate, validationMessage } = await this.shouldCreateNewEntity(pool, newEntity);
|
|
1718
1649
|
if (shouldCreate) {
|
|
1719
1650
|
// process a single new entity
|
|
1720
|
-
let newEntityName = await this.createNewEntityName(newEntity);
|
|
1651
|
+
let newEntityName = await this.createNewEntityName(newEntity, currentUser);
|
|
1721
1652
|
const newEntityDisplayName = this.createNewEntityDisplayName(newEntity, newEntityName);
|
|
1722
1653
|
let suffix = '';
|
|
1723
1654
|
const existingEntity = md.Entities.find(e => e.Name.toLowerCase() === newEntityName.toLowerCase());
|
|
@@ -1897,6 +1828,291 @@ NumberedRows AS (
|
|
|
1897
1828
|
`;
|
|
1898
1829
|
return sSQLInsert;
|
|
1899
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Apply Advanced Generation features - Smart Field Identification and Form Layout Generation
|
|
1833
|
+
*/
|
|
1834
|
+
async applyAdvancedGeneration(pool, excludeSchemas, currentUser) {
|
|
1835
|
+
try {
|
|
1836
|
+
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
1837
|
+
if (!ag.enabled) {
|
|
1838
|
+
return true;
|
|
1839
|
+
}
|
|
1840
|
+
// Get list of entities to process
|
|
1841
|
+
// If forceRegeneration.enabled is true, process ALL entities
|
|
1842
|
+
// Otherwise, only process new or modified entities
|
|
1843
|
+
let entitiesToProcess = [];
|
|
1844
|
+
let whereClause = '';
|
|
1845
|
+
if (config_1.configInfo.forceRegeneration?.enabled) {
|
|
1846
|
+
// Force regeneration mode - process all entities (or filtered by entityWhereClause)
|
|
1847
|
+
(0, status_logging_1.logStatus)(` Force regeneration enabled - processing all entities...`);
|
|
1848
|
+
whereClause = 'e.VirtualEntity = 0';
|
|
1849
|
+
if (config_1.configInfo.forceRegeneration.entityWhereClause && config_1.configInfo.forceRegeneration.entityWhereClause.trim().length > 0) {
|
|
1850
|
+
whereClause += ` AND (${config_1.configInfo.forceRegeneration.entityWhereClause})`;
|
|
1851
|
+
(0, status_logging_1.logStatus)(` Filtered by: ${config_1.configInfo.forceRegeneration.entityWhereClause}`);
|
|
1852
|
+
}
|
|
1853
|
+
whereClause += ` AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
// Normal mode - only process new or modified entities
|
|
1857
|
+
// Deduplicate in case an entity appears in both lists
|
|
1858
|
+
entitiesToProcess = [
|
|
1859
|
+
...new Set([
|
|
1860
|
+
...ManageMetadataBase.newEntityList,
|
|
1861
|
+
...ManageMetadataBase.modifiedEntityList
|
|
1862
|
+
])
|
|
1863
|
+
];
|
|
1864
|
+
if (entitiesToProcess.length === 0) {
|
|
1865
|
+
return true;
|
|
1866
|
+
}
|
|
1867
|
+
(0, status_logging_1.logStatus)(` Advanced Generation enabled, processing ${entitiesToProcess.length} entities...`);
|
|
1868
|
+
whereClause = `e.VirtualEntity = 0 AND e.Name IN (${entitiesToProcess.map(name => `'${name}'`).join(',')}) AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1869
|
+
}
|
|
1870
|
+
// Get entity details for entities that need processing
|
|
1871
|
+
const entitiesSQL = `
|
|
1872
|
+
SELECT
|
|
1873
|
+
e.ID,
|
|
1874
|
+
e.Name,
|
|
1875
|
+
e.Description,
|
|
1876
|
+
e.SchemaName,
|
|
1877
|
+
e.BaseTable
|
|
1878
|
+
FROM
|
|
1879
|
+
[${(0, config_1.mj_core_schema)()}].vwEntities e
|
|
1880
|
+
WHERE
|
|
1881
|
+
${whereClause}
|
|
1882
|
+
ORDER BY
|
|
1883
|
+
e.Name
|
|
1884
|
+
`;
|
|
1885
|
+
const entitiesResult = await pool.request().query(entitiesSQL);
|
|
1886
|
+
const entities = entitiesResult.recordset;
|
|
1887
|
+
if (entities.length === 0) {
|
|
1888
|
+
return true;
|
|
1889
|
+
}
|
|
1890
|
+
// Get ALL fields for ALL entities in a single query
|
|
1891
|
+
const entityIds = entities.map((e) => `'${e.ID}'`).join(',');
|
|
1892
|
+
const fieldsSQL = `
|
|
1893
|
+
SELECT
|
|
1894
|
+
ef.EntityID,
|
|
1895
|
+
ef.ID,
|
|
1896
|
+
ef.Name,
|
|
1897
|
+
ef.Type,
|
|
1898
|
+
ef.AllowsNull,
|
|
1899
|
+
ef.IsPrimaryKey,
|
|
1900
|
+
ef.IsUnique,
|
|
1901
|
+
ef.Description,
|
|
1902
|
+
ef.AutoUpdateIsNameField,
|
|
1903
|
+
ef.AutoUpdateDefaultInView,
|
|
1904
|
+
ef.AutoUpdateCategory
|
|
1905
|
+
FROM
|
|
1906
|
+
[${(0, config_1.mj_core_schema)()}].vwEntityFields ef
|
|
1907
|
+
WHERE
|
|
1908
|
+
ef.EntityID IN (${entityIds})
|
|
1909
|
+
ORDER BY
|
|
1910
|
+
ef.EntityID,
|
|
1911
|
+
ef.Sequence
|
|
1912
|
+
`;
|
|
1913
|
+
const fieldsResult = await pool.request().query(fieldsSQL);
|
|
1914
|
+
const allFields = fieldsResult.recordset;
|
|
1915
|
+
// Process entities in batches with parallelization
|
|
1916
|
+
return await this.processEntitiesBatched(pool, entities, allFields, ag, currentUser);
|
|
1917
|
+
}
|
|
1918
|
+
catch (error) {
|
|
1919
|
+
(0, status_logging_1.logError)(`Advanced Generation failed: ${error}`);
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Process entities in batches with parallel execution
|
|
1925
|
+
* @param pool Database connection pool
|
|
1926
|
+
* @param entities Entities to process
|
|
1927
|
+
* @param allFields All fields for all entities (will be filtered per entity)
|
|
1928
|
+
* @param ag AdvancedGeneration instance
|
|
1929
|
+
* @param currentUser User context
|
|
1930
|
+
* @param batchSize Number of entities to process in parallel (default 5)
|
|
1931
|
+
*/
|
|
1932
|
+
async processEntitiesBatched(pool, entities, allFields, ag, currentUser, batchSize = 5) {
|
|
1933
|
+
let processedCount = 0;
|
|
1934
|
+
let errorCount = 0;
|
|
1935
|
+
// Process in batches
|
|
1936
|
+
for (let i = 0; i < entities.length; i += batchSize) {
|
|
1937
|
+
const batch = entities.slice(i, i + batchSize);
|
|
1938
|
+
// Process batch in parallel
|
|
1939
|
+
const batchResults = await Promise.allSettled(batch.map(entity => this.processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser)));
|
|
1940
|
+
// Tally results
|
|
1941
|
+
for (const result of batchResults) {
|
|
1942
|
+
if (result.status === 'fulfilled') {
|
|
1943
|
+
processedCount++;
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
errorCount++;
|
|
1947
|
+
(0, status_logging_1.logError)(` Error processing entity: ${result.reason}`);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
(0, status_logging_1.logStatus)(` Progress: ${processedCount}/${entities.length} entities processed`);
|
|
1951
|
+
}
|
|
1952
|
+
(0, status_logging_1.logStatus)(` Advanced Generation complete: ${processedCount} entities processed, ${errorCount} errors`);
|
|
1953
|
+
return errorCount === 0;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Process advanced generation for a single entity
|
|
1957
|
+
* @param pool Database connection pool
|
|
1958
|
+
* @param entity Entity to process
|
|
1959
|
+
* @param allFields All fields for all entities (will be filtered for this entity)
|
|
1960
|
+
* @param ag AdvancedGeneration instance
|
|
1961
|
+
* @param currentUser User context
|
|
1962
|
+
*/
|
|
1963
|
+
async processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser) {
|
|
1964
|
+
// Filter fields for this entity (client-side filtering)
|
|
1965
|
+
const fields = allFields.filter((f) => f.EntityID === entity.ID);
|
|
1966
|
+
// Smart Field Identification
|
|
1967
|
+
// Only run if at least one field allows auto-update
|
|
1968
|
+
if (fields.some((f) => f.AutoUpdateIsNameField || f.AutoUpdateDefaultInView)) {
|
|
1969
|
+
const fieldAnalysis = await ag.identifyFields({
|
|
1970
|
+
Name: entity.Name,
|
|
1971
|
+
Description: entity.Description,
|
|
1972
|
+
Fields: fields
|
|
1973
|
+
}, currentUser);
|
|
1974
|
+
if (fieldAnalysis) {
|
|
1975
|
+
await this.applySmartFieldIdentification(pool, entity.ID, fields, fieldAnalysis);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
// Form Layout Generation
|
|
1979
|
+
// Only run if at least one field allows auto-update
|
|
1980
|
+
if (fields.some((f) => f.AutoUpdateCategory)) {
|
|
1981
|
+
const layoutAnalysis = await ag.generateFormLayout({
|
|
1982
|
+
Name: entity.Name,
|
|
1983
|
+
Description: entity.Description,
|
|
1984
|
+
Fields: fields
|
|
1985
|
+
}, currentUser);
|
|
1986
|
+
if (layoutAnalysis) {
|
|
1987
|
+
await this.applyFormLayout(pool, entity.ID, fields, layoutAnalysis);
|
|
1988
|
+
(0, status_logging_1.logStatus)(` Applied form layout for ${entity.Name}`);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Apply smart field identification results to entity fields
|
|
1994
|
+
*/
|
|
1995
|
+
async applySmartFieldIdentification(pool, entityId, fields, result) {
|
|
1996
|
+
const sqlStatements = [];
|
|
1997
|
+
// Find the name field (exactly one)
|
|
1998
|
+
const nameField = fields.find(f => f.Name === result.nameField);
|
|
1999
|
+
if (nameField && nameField.AutoUpdateIsNameField && nameField.ID) {
|
|
2000
|
+
sqlStatements.push(`
|
|
2001
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2002
|
+
SET IsNameField = 1
|
|
2003
|
+
WHERE ID = '${nameField.ID}'
|
|
2004
|
+
AND AutoUpdateIsNameField = 1
|
|
2005
|
+
`);
|
|
2006
|
+
}
|
|
2007
|
+
else if (!nameField) {
|
|
2008
|
+
(0, status_logging_1.logError)(`Smart field identification returned invalid nameField: '${result.nameField}' not found in entity fields`);
|
|
2009
|
+
}
|
|
2010
|
+
// Find all default in view fields (one or more)
|
|
2011
|
+
const defaultInViewFields = fields.filter(f => result.defaultInView.includes(f.Name) && f.AutoUpdateDefaultInView && f.ID);
|
|
2012
|
+
// Warn about any fields that weren't found
|
|
2013
|
+
const missingFields = result.defaultInView.filter(name => !fields.some(f => f.Name === name));
|
|
2014
|
+
if (missingFields.length > 0) {
|
|
2015
|
+
(0, status_logging_1.logError)(`Smart field identification returned invalid defaultInView fields: ${missingFields.join(', ')} not found in entity`);
|
|
2016
|
+
}
|
|
2017
|
+
// Build update statements for all default in view fields
|
|
2018
|
+
for (const field of defaultInViewFields) {
|
|
2019
|
+
sqlStatements.push(`
|
|
2020
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2021
|
+
SET DefaultInView = 1
|
|
2022
|
+
WHERE ID = '${field.ID}'
|
|
2023
|
+
AND AutoUpdateDefaultInView = 1
|
|
2024
|
+
`);
|
|
2025
|
+
}
|
|
2026
|
+
// Execute all updates in one batch
|
|
2027
|
+
if (sqlStatements.length > 0) {
|
|
2028
|
+
const combinedSQL = sqlStatements.join('\n');
|
|
2029
|
+
await this.LogSQLAndExecute(pool, combinedSQL, `Set field properties for entity`, false);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Apply form layout generation results to set category on entity fields
|
|
2034
|
+
*/
|
|
2035
|
+
async applyFormLayout(pool, entityId, fields, result) {
|
|
2036
|
+
// Collect all SQL statements for batch execution
|
|
2037
|
+
const sqlStatements = [];
|
|
2038
|
+
// Assign category to each field
|
|
2039
|
+
for (const fieldCategory of result.fieldCategories) {
|
|
2040
|
+
const field = fields.find(f => f.Name === fieldCategory.fieldName);
|
|
2041
|
+
if (field && field.AutoUpdateCategory && field.ID) {
|
|
2042
|
+
// Override category to "System Metadata" for __mj_ fields (system audit fields)
|
|
2043
|
+
let category = fieldCategory.category;
|
|
2044
|
+
if (field.Name.startsWith('__mj_')) {
|
|
2045
|
+
category = 'System Metadata';
|
|
2046
|
+
}
|
|
2047
|
+
const updateSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
|
|
2048
|
+
SET Category = '${category.replace(/'/g, "''")}',
|
|
2049
|
+
GeneratedFormSection = 'Category'
|
|
2050
|
+
WHERE ID = '${field.ID}'
|
|
2051
|
+
AND AutoUpdateCategory = 1`;
|
|
2052
|
+
sqlStatements.push(updateSQL);
|
|
2053
|
+
}
|
|
2054
|
+
else if (!field) {
|
|
2055
|
+
(0, status_logging_1.logError)(`Form layout generation returned invalid fieldName: '${fieldCategory.fieldName}' not found in entity`);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
// Execute all field updates in one batch
|
|
2059
|
+
if (sqlStatements.length > 0) {
|
|
2060
|
+
const combinedSQL = sqlStatements.join('\n');
|
|
2061
|
+
await this.LogSQLAndExecute(pool, combinedSQL, `Set categories for ${sqlStatements.length} fields`, false);
|
|
2062
|
+
}
|
|
2063
|
+
// Store entity icon if provided and entity doesn't already have one
|
|
2064
|
+
if (result.entityIcon && result.entityIcon.trim().length > 0) {
|
|
2065
|
+
// Check if entity already has an icon
|
|
2066
|
+
const checkEntitySQL = `
|
|
2067
|
+
SELECT Icon FROM [${(0, config_1.mj_core_schema)()}].Entity
|
|
2068
|
+
WHERE ID = '${entityId}'
|
|
2069
|
+
`;
|
|
2070
|
+
const entityCheck = await pool.request().query(checkEntitySQL);
|
|
2071
|
+
if (entityCheck.recordset.length > 0) {
|
|
2072
|
+
const currentIcon = entityCheck.recordset[0].Icon;
|
|
2073
|
+
// Only update if entity doesn't have an icon set
|
|
2074
|
+
if (!currentIcon || currentIcon.trim().length === 0) {
|
|
2075
|
+
const escapedIcon = result.entityIcon.replace(/'/g, "''");
|
|
2076
|
+
const updateEntitySQL = `
|
|
2077
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].Entity
|
|
2078
|
+
SET Icon = '${escapedIcon}',
|
|
2079
|
+
__mj_UpdatedAt = GETUTCDATE()
|
|
2080
|
+
WHERE ID = '${entityId}'
|
|
2081
|
+
`;
|
|
2082
|
+
await this.LogSQLAndExecute(pool, updateEntitySQL, `Set entity icon to ${result.entityIcon}`, false);
|
|
2083
|
+
(0, status_logging_1.logStatus)(` ✓ Set entity icon: ${result.entityIcon}`);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
// Store category icons in EntitySetting if provided
|
|
2088
|
+
if (result.categoryIcons && Object.keys(result.categoryIcons).length > 0) {
|
|
2089
|
+
const iconsJSON = JSON.stringify(result.categoryIcons).replace(/'/g, "''");
|
|
2090
|
+
// First check if setting already exists
|
|
2091
|
+
const checkSQL = `
|
|
2092
|
+
SELECT ID FROM [${(0, config_1.mj_core_schema)()}].EntitySetting
|
|
2093
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
2094
|
+
`;
|
|
2095
|
+
const existing = await pool.request().query(checkSQL);
|
|
2096
|
+
if (existing.recordset.length > 0) {
|
|
2097
|
+
// Update existing setting
|
|
2098
|
+
const updateSQL = `
|
|
2099
|
+
UPDATE [${(0, config_1.mj_core_schema)()}].EntitySetting
|
|
2100
|
+
SET Value = '${iconsJSON}',
|
|
2101
|
+
__mj_UpdatedAt = GETUTCDATE()
|
|
2102
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
2103
|
+
`;
|
|
2104
|
+
await this.LogSQLAndExecute(pool, updateSQL, `Update FieldCategoryIcons setting for entity`, false);
|
|
2105
|
+
}
|
|
2106
|
+
else {
|
|
2107
|
+
// Insert new setting
|
|
2108
|
+
const insertSQL = `
|
|
2109
|
+
INSERT INTO [${(0, config_1.mj_core_schema)()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
2110
|
+
VALUES (NEWID(), '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', GETUTCDATE(), GETUTCDATE())
|
|
2111
|
+
`;
|
|
2112
|
+
await this.LogSQLAndExecute(pool, insertSQL, `Insert FieldCategoryIcons setting for entity`, false);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
1900
2116
|
/**
|
|
1901
2117
|
* Executes the given SQL query using the given ConnectionPool object.
|
|
1902
2118
|
* If the appendToLogFile parameter is true, the query will also be appended to the log file.
|