Use AI assistant to create records in the system, added configurable list views
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = async function(knex) {
|
||||
// Check if layout_type column already exists (in case of partial migration)
|
||||
const hasLayoutType = await knex.schema.hasColumn('page_layouts', 'layout_type');
|
||||
|
||||
// Check if the old index exists
|
||||
const [indexes] = await knex.raw(`SHOW INDEX FROM page_layouts WHERE Key_name = 'page_layouts_object_id_is_default_index'`);
|
||||
const hasOldIndex = indexes.length > 0;
|
||||
|
||||
// Check if foreign key exists
|
||||
const [fks] = await knex.raw(`
|
||||
SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'page_layouts'
|
||||
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||
AND CONSTRAINT_NAME = 'page_layouts_object_id_foreign'
|
||||
`);
|
||||
const hasForeignKey = fks.length > 0;
|
||||
|
||||
if (hasOldIndex) {
|
||||
// First, drop the foreign key constraint that depends on the index (if it exists)
|
||||
if (hasForeignKey) {
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.dropForeign(['object_id']);
|
||||
});
|
||||
}
|
||||
|
||||
// Now we can safely drop the old index
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.dropIndex(['object_id', 'is_default']);
|
||||
});
|
||||
}
|
||||
|
||||
// Add layout_type column if it doesn't exist
|
||||
if (!hasLayoutType) {
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
// Add layout_type column to distinguish between detail/edit layouts and list view layouts
|
||||
// Default to 'detail' for existing layouts
|
||||
table.enum('layout_type', ['detail', 'list']).notNullable().defaultTo('detail').after('name');
|
||||
});
|
||||
}
|
||||
|
||||
// Check if new index exists
|
||||
const [newIndexes] = await knex.raw(`SHOW INDEX FROM page_layouts WHERE Key_name = 'page_layouts_object_id_layout_type_is_default_index'`);
|
||||
const hasNewIndex = newIndexes.length > 0;
|
||||
|
||||
if (!hasNewIndex) {
|
||||
// Create new index including layout_type
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.index(['object_id', 'layout_type', 'is_default']);
|
||||
});
|
||||
}
|
||||
|
||||
// Re-check if foreign key exists (may have been dropped above or in previous attempt)
|
||||
const [fksAfter] = await knex.raw(`
|
||||
SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'page_layouts'
|
||||
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||
AND CONSTRAINT_NAME = 'page_layouts_object_id_foreign'
|
||||
`);
|
||||
|
||||
if (fksAfter.length === 0) {
|
||||
// Re-add the foreign key constraint
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.foreign('object_id').references('id').inTable('object_definitions').onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = async function(knex) {
|
||||
// Drop the foreign key first
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.dropForeign(['object_id']);
|
||||
});
|
||||
|
||||
// Drop the new index and column, restore old index
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.dropIndex(['object_id', 'layout_type', 'is_default']);
|
||||
table.dropColumn('layout_type');
|
||||
table.index(['object_id', 'is_default']);
|
||||
});
|
||||
|
||||
// Re-add the foreign key constraint
|
||||
await knex.schema.alterTable('page_layouts', (table) => {
|
||||
table.foreign('object_id').references('id').inTable('object_definitions').onDelete('CASCADE');
|
||||
});
|
||||
};
|
||||
136
backend/package-lock.json
generated
136
backend/package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@casl/ability": "^6.7.5",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"@langchain/core": "^1.1.12",
|
||||
"@langchain/core": "^1.1.15",
|
||||
"@langchain/langgraph": "^1.0.15",
|
||||
"@langchain/openai": "^1.2.1",
|
||||
"@nestjs/bullmq": "^10.1.0",
|
||||
@@ -29,9 +29,10 @@
|
||||
"bullmq": "^5.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"deepagents": "^1.5.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"knex": "^3.1.0",
|
||||
"langchain": "^1.2.7",
|
||||
"langchain": "^1.2.10",
|
||||
"mysql2": "^3.15.3",
|
||||
"objection": "^3.1.5",
|
||||
"openai": "^6.15.0",
|
||||
@@ -228,6 +229,26 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz",
|
||||
"integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"anthropic-ai-sdk": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
@@ -689,6 +710,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
|
||||
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -1689,10 +1719,26 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/anthropic": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-1.3.10.tgz",
|
||||
"integrity": "sha512-VXq5fsEJ4FB5XGrnoG+bfm0I7OlmYLI4jZ6cX9RasyqhGo9wcDyKw1+uEQ1H7Og7jWrTa1bfXCun76wttewJnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.71.0",
|
||||
"zod": "^3.25.76 || ^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "1.1.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.12.tgz",
|
||||
"integrity": "sha512-sHWLvhyLi3fntlg3MEPB89kCjxEX7/+imlIYJcp6uFGCAZfGxVWklqp22HwjT1szorUBYrkO8u0YA554ReKxGQ==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.15.tgz",
|
||||
"integrity": "sha512-b8RN5DkWAmDAlMu/UpTZEluYwCLpm63PPWniRKlE8ie3KkkE7IuMQ38pf4kV1iaiI+d99BEQa2vafQHfCujsRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cfworker/json-schema": "^4.0.2",
|
||||
@@ -2487,7 +2533,6 @@
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
@@ -2501,7 +2546,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -2511,7 +2555,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
@@ -4029,7 +4072,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
@@ -4754,6 +4796,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepagents": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/deepagents/-/deepagents-1.5.0.tgz",
|
||||
"integrity": "sha512-tjZLOISPMpqfk+k/iE1uIZavXW9j4NrhopUmH5ARqzmk95EEtGDyN++tgnY+tdVOOZTjE2LHjOVV7or58dtx8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^1.3.7",
|
||||
"@langchain/core": "^1.1.12",
|
||||
"@langchain/langgraph": "^1.0.14",
|
||||
"fast-glob": "^3.3.3",
|
||||
"langchain": "^1.2.7",
|
||||
"micromatch": "^4.0.8",
|
||||
"yaml": "^2.8.2",
|
||||
"zod": "^4.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
@@ -5514,7 +5572,6 @@
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -5740,7 +5797,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@@ -6145,7 +6201,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
@@ -6594,7 +6649,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6623,7 +6677,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
@@ -6658,7 +6711,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
@@ -7609,6 +7661,19 @@
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-to-ts": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
|
||||
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"ts-algebra": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -7811,9 +7876,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/langchain": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/langchain/-/langchain-1.2.7.tgz",
|
||||
"integrity": "sha512-G+3Ftz/08CurJaE7LukQGBf3mCSz7XM8LZeAaFPg391Ru4lT8eLYfG6Fv4ZI0u6EBsPVcOQfaS9ig8nCRmJeqA==",
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/langchain/-/langchain-1.2.10.tgz",
|
||||
"integrity": "sha512-9uVxOJE/RTECvNutQfOLwH7f6R9mcq0G/IMHwA2eptDA86R/Yz2zWMz4vARVFPxPrdSJ9nJFDPAqRQlRFwdHBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/langgraph": "^1.0.0",
|
||||
@@ -7826,7 +7891,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "1.1.12"
|
||||
"@langchain/core": "1.1.15"
|
||||
}
|
||||
},
|
||||
"node_modules/langchain/node_modules/uuid": {
|
||||
@@ -8201,7 +8266,6 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -8211,7 +8275,6 @@
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
@@ -8225,7 +8288,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
@@ -9388,7 +9450,6 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -9727,7 +9788,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -10657,7 +10717,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
@@ -10709,6 +10768,12 @@
|
||||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-algebra": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
|
||||
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
||||
@@ -11455,6 +11520,21 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
@@ -11508,9 +11588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@casl/ability": "^6.7.5",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"@langchain/core": "^1.1.12",
|
||||
"@langchain/core": "^1.1.15",
|
||||
"@langchain/langgraph": "^1.0.15",
|
||||
"@langchain/openai": "^1.2.1",
|
||||
"@nestjs/bullmq": "^10.1.0",
|
||||
@@ -46,9 +46,10 @@
|
||||
"bullmq": "^5.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"deepagents": "^1.5.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"knex": "^3.1.0",
|
||||
"langchain": "^1.2.7",
|
||||
"langchain": "^1.2.10",
|
||||
"mysql2": "^3.15.3",
|
||||
"objection": "^3.1.5",
|
||||
"openai": "^6.15.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,15 +12,94 @@ export interface AiChatContext {
|
||||
|
||||
export interface AiAssistantReply {
|
||||
reply: string;
|
||||
action?: 'create_record' | 'collect_fields' | 'clarify';
|
||||
action?: 'create_record' | 'collect_fields' | 'clarify' | 'plan_complete' | 'plan_pending';
|
||||
missingFields?: string[];
|
||||
record?: any;
|
||||
records?: any[]; // Multiple records when plan execution completes
|
||||
plan?: RecordCreationPlan;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Entity Discovery Types
|
||||
// ============================================
|
||||
|
||||
export interface EntityFieldInfo {
|
||||
apiName: string;
|
||||
label: string;
|
||||
type: string;
|
||||
isRequired: boolean;
|
||||
isSystem: boolean;
|
||||
referenceObject?: string; // For LOOKUP fields, the target entity
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface EntityRelationship {
|
||||
fieldApiName: string;
|
||||
fieldLabel: string;
|
||||
targetEntity: string;
|
||||
relationshipType: 'lookup' | 'master-detail' | 'polymorphic';
|
||||
}
|
||||
|
||||
export interface EntityInfo {
|
||||
apiName: string;
|
||||
label: string;
|
||||
pluralLabel?: string;
|
||||
description?: string;
|
||||
fields: EntityFieldInfo[];
|
||||
requiredFields: string[]; // Field apiNames that are required
|
||||
relationships: EntityRelationship[];
|
||||
}
|
||||
|
||||
export interface SystemEntities {
|
||||
entities: EntityInfo[];
|
||||
entityByApiName: Record<string, EntityInfo>; // Changed from Map for state serialization
|
||||
loadedAt: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Planning Types
|
||||
// ============================================
|
||||
|
||||
export interface PlannedRecord {
|
||||
id: string; // Temporary ID for planning (e.g., "temp_account_1")
|
||||
entityApiName: string;
|
||||
entityLabel: string;
|
||||
fields: Record<string, any>;
|
||||
resolvedFields?: Record<string, any>; // Fields after dependency resolution
|
||||
missingRequiredFields: string[];
|
||||
dependsOn: string[]; // IDs of other planned records this depends on
|
||||
status: 'pending' | 'ready' | 'created' | 'failed';
|
||||
createdRecordId?: string; // Actual ID after creation
|
||||
wasExisting?: boolean; // True if record already existed in database
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface RecordCreationPlan {
|
||||
id: string;
|
||||
records: PlannedRecord[];
|
||||
executionOrder: string[]; // Ordered list of planned record IDs
|
||||
status: 'building' | 'incomplete' | 'ready' | 'executing' | 'completed' | 'failed';
|
||||
createdRecords: any[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// State Types
|
||||
// ============================================
|
||||
|
||||
export interface AiAssistantState {
|
||||
message: string;
|
||||
messages?: any[]; // BaseMessage[] from langchain - used when invoked by Deep Agent
|
||||
history?: AiChatMessage[];
|
||||
context: AiChatContext;
|
||||
|
||||
// Entity discovery
|
||||
systemEntities?: SystemEntities;
|
||||
|
||||
// Planning
|
||||
plan?: RecordCreationPlan;
|
||||
|
||||
// Legacy fields (kept for compatibility during transition)
|
||||
objectDefinition?: any;
|
||||
pageLayout?: any;
|
||||
extractedFields?: Record<string, any>;
|
||||
|
||||
@@ -79,6 +79,10 @@ export class FieldMapperService {
|
||||
const frontendType = this.mapFieldType(field.type);
|
||||
const isLookupField = frontendType === 'belongsTo' || field.type.toLowerCase().includes('lookup');
|
||||
|
||||
// Hide 'id' field from list view by default
|
||||
const isIdField = field.apiName === 'id';
|
||||
const defaultShowOnList = isIdField ? false : true;
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
apiName: field.apiName,
|
||||
@@ -95,7 +99,7 @@ export class FieldMapperService {
|
||||
isReadOnly: field.isSystem || uiMetadata.isReadOnly || false,
|
||||
|
||||
// View visibility
|
||||
showOnList: uiMetadata.showOnList !== false,
|
||||
showOnList: uiMetadata.showOnList !== undefined ? uiMetadata.showOnList : defaultShowOnList,
|
||||
showOnDetail: uiMetadata.showOnDetail !== false,
|
||||
showOnEdit: uiMetadata.showOnEdit !== false && !field.isSystem,
|
||||
sortable: uiMetadata.sortable !== false,
|
||||
@@ -141,6 +145,7 @@ export class FieldMapperService {
|
||||
'boolean': 'boolean',
|
||||
'date': 'date',
|
||||
'datetime': 'datetime',
|
||||
'date_time': 'datetime',
|
||||
'time': 'time',
|
||||
'email': 'email',
|
||||
'url': 'url',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { IsString, IsUUID, IsBoolean, IsOptional, IsObject } from 'class-validator';
|
||||
import { IsString, IsUUID, IsBoolean, IsOptional, IsObject, IsIn } from 'class-validator';
|
||||
|
||||
export type PageLayoutType = 'detail' | 'list';
|
||||
|
||||
export class CreatePageLayoutDto {
|
||||
@IsString()
|
||||
@@ -7,18 +9,25 @@ export class CreatePageLayoutDto {
|
||||
@IsUUID()
|
||||
objectId: string;
|
||||
|
||||
@IsIn(['detail', 'list'])
|
||||
@IsOptional()
|
||||
layoutType?: PageLayoutType = 'detail';
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isDefault?: boolean;
|
||||
|
||||
@IsObject()
|
||||
layoutConfig: {
|
||||
// For detail layouts: grid-based field positions
|
||||
fields: Array<{
|
||||
fieldId: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
w?: number;
|
||||
h?: number;
|
||||
// For list layouts: field order (optional, defaults to array index)
|
||||
order?: number;
|
||||
}>;
|
||||
relatedLists?: string[];
|
||||
};
|
||||
@@ -42,10 +51,11 @@ export class UpdatePageLayoutDto {
|
||||
layoutConfig?: {
|
||||
fields: Array<{
|
||||
fieldId: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
w?: number;
|
||||
h?: number;
|
||||
order?: number;
|
||||
}>;
|
||||
relatedLists?: string[];
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { PageLayoutService } from './page-layout.service';
|
||||
import { CreatePageLayoutDto, UpdatePageLayoutDto } from './dto/page-layout.dto';
|
||||
import { CreatePageLayoutDto, UpdatePageLayoutDto, PageLayoutType } from './dto/page-layout.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { TenantId } from '../tenant/tenant.decorator';
|
||||
|
||||
@@ -25,13 +25,21 @@ export class PageLayoutController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll(@TenantId() tenantId: string, @Query('objectId') objectId?: string) {
|
||||
return this.pageLayoutService.findAll(tenantId, objectId);
|
||||
findAll(
|
||||
@TenantId() tenantId: string,
|
||||
@Query('objectId') objectId?: string,
|
||||
@Query('layoutType') layoutType?: PageLayoutType,
|
||||
) {
|
||||
return this.pageLayoutService.findAll(tenantId, objectId, layoutType);
|
||||
}
|
||||
|
||||
@Get('default/:objectId')
|
||||
findDefaultByObject(@TenantId() tenantId: string, @Param('objectId') objectId: string) {
|
||||
return this.pageLayoutService.findDefaultByObject(tenantId, objectId);
|
||||
findDefaultByObject(
|
||||
@TenantId() tenantId: string,
|
||||
@Param('objectId') objectId: string,
|
||||
@Query('layoutType') layoutType?: PageLayoutType,
|
||||
) {
|
||||
return this.pageLayoutService.findDefaultByObject(tenantId, objectId, layoutType || 'detail');
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { TenantDatabaseService } from '../tenant/tenant-database.service';
|
||||
import { CreatePageLayoutDto, UpdatePageLayoutDto } from './dto/page-layout.dto';
|
||||
import { CreatePageLayoutDto, UpdatePageLayoutDto, PageLayoutType } from './dto/page-layout.dto';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutService {
|
||||
@@ -8,17 +8,19 @@ export class PageLayoutService {
|
||||
|
||||
async create(tenantId: string, createDto: CreatePageLayoutDto) {
|
||||
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||
const layoutType = createDto.layoutType || 'detail';
|
||||
|
||||
// If this layout is set as default, unset other defaults for the same object
|
||||
// If this layout is set as default, unset other defaults for the same object and layout type
|
||||
if (createDto.isDefault) {
|
||||
await knex('page_layouts')
|
||||
.where({ object_id: createDto.objectId })
|
||||
.where({ object_id: createDto.objectId, layout_type: layoutType })
|
||||
.update({ is_default: false });
|
||||
}
|
||||
|
||||
const [id] = await knex('page_layouts').insert({
|
||||
name: createDto.name,
|
||||
object_id: createDto.objectId,
|
||||
layout_type: layoutType,
|
||||
is_default: createDto.isDefault || false,
|
||||
layout_config: JSON.stringify(createDto.layoutConfig),
|
||||
description: createDto.description || null,
|
||||
@@ -29,7 +31,7 @@ export class PageLayoutService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async findAll(tenantId: string, objectId?: string) {
|
||||
async findAll(tenantId: string, objectId?: string, layoutType?: PageLayoutType) {
|
||||
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||
|
||||
let query = knex('page_layouts');
|
||||
@@ -38,6 +40,10 @@ export class PageLayoutService {
|
||||
query = query.where({ object_id: objectId });
|
||||
}
|
||||
|
||||
if (layoutType) {
|
||||
query = query.where({ layout_type: layoutType });
|
||||
}
|
||||
|
||||
const layouts = await query.orderByRaw('is_default DESC, name ASC');
|
||||
return layouts;
|
||||
}
|
||||
@@ -54,11 +60,11 @@ export class PageLayoutService {
|
||||
return layout;
|
||||
}
|
||||
|
||||
async findDefaultByObject(tenantId: string, objectId: string) {
|
||||
async findDefaultByObject(tenantId: string, objectId: string, layoutType: PageLayoutType = 'detail') {
|
||||
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||
|
||||
const layout = await knex('page_layouts')
|
||||
.where({ object_id: objectId, is_default: true })
|
||||
.where({ object_id: objectId, is_default: true, layout_type: layoutType })
|
||||
.first();
|
||||
|
||||
return layout || null;
|
||||
@@ -68,13 +74,12 @@ export class PageLayoutService {
|
||||
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||
|
||||
// Check if layout exists
|
||||
await this.findOne(tenantId, id);
|
||||
const layout = await this.findOne(tenantId, id);
|
||||
|
||||
// If setting as default, unset other defaults for the same object
|
||||
// If setting as default, unset other defaults for the same object and layout type
|
||||
if (updateDto.isDefault) {
|
||||
const layout = await this.findOne(tenantId, id);
|
||||
await knex('page_layouts')
|
||||
.where({ object_id: layout.object_id })
|
||||
.where({ object_id: layout.object_id, layout_type: layout.layout_type })
|
||||
.whereNot({ id })
|
||||
.update({ is_default: false });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user