Highlight diffs on validation changes
This commit is contained in:
211
inventory-server/package-lock.json
generated
211
inventory-server/package-lock.json
generated
@@ -9,9 +9,11 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/diff": "^7.0.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"csv-parse": "^5.6.0",
|
"csv-parse": "^5.6.0",
|
||||||
|
"diff": "^7.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
@@ -23,8 +25,6 @@
|
|||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"@types/pg": "^8.11.2",
|
|
||||||
"nodemon": "^3.0.2"
|
"nodemon": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -391,65 +391,10 @@
|
|||||||
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
|
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/diff": {
|
||||||
"version": "1.19.5",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.1.tgz",
|
||||||
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
|
"integrity": "sha512-R/BHQFripuhW6XPXy05hIvXJQdQ4540KnTvEFHSLjXfHYM41liOLKgIJEyYYiQe796xpaMHfe4Uj/p7Uvng2vA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/connect": "*",
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/connect": {
|
|
||||||
"version": "3.4.38",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
|
||||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/express": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/body-parser": "*",
|
|
||||||
"@types/express-serve-static-core": "^4.17.33",
|
|
||||||
"@types/qs": "*",
|
|
||||||
"@types/serve-static": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/express-serve-static-core": {
|
|
||||||
"version": "4.19.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
|
|
||||||
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*",
|
|
||||||
"@types/qs": "*",
|
|
||||||
"@types/range-parser": "*",
|
|
||||||
"@types/send": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/http-errors": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/mime": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
|
||||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
@@ -471,117 +416,6 @@
|
|||||||
"form-data": "^4.0.0"
|
"form-data": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg": {
|
|
||||||
"version": "8.11.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz",
|
|
||||||
"integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*",
|
|
||||||
"pg-protocol": "*",
|
|
||||||
"pg-types": "^4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg/node_modules/pg-types": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"pg-int8": "1.0.1",
|
|
||||||
"pg-numeric": "1.0.2",
|
|
||||||
"postgres-array": "~3.0.1",
|
|
||||||
"postgres-bytea": "~3.0.0",
|
|
||||||
"postgres-date": "~2.1.0",
|
|
||||||
"postgres-interval": "^3.0.0",
|
|
||||||
"postgres-range": "^1.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg/node_modules/postgres-array": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg/node_modules/postgres-bytea": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"obuf": "~1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg/node_modules/postgres-date": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/pg/node_modules/postgres-interval": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/qs": {
|
|
||||||
"version": "6.9.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
|
|
||||||
"integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/range-parser": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
|
||||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/send": {
|
|
||||||
"version": "0.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
|
||||||
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/mime": "^1",
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/serve-static": {
|
|
||||||
"version": "1.15.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
|
||||||
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/http-errors": "*",
|
|
||||||
"@types/node": "*",
|
|
||||||
"@types/send": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/abbrev": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
@@ -1285,6 +1119,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.7",
|
"version": "16.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
@@ -2755,13 +2598,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/obuf": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/on-finished": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@@ -2953,16 +2789,6 @@
|
|||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-numeric": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pg-pool": {
|
"node_modules/pg-pool": {
|
||||||
"version": "3.7.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz",
|
||||||
@@ -3285,13 +3111,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres-range": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
|||||||
@@ -18,9 +18,11 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/diff": "^7.0.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"csv-parse": "^5.6.0",
|
"csv-parse": "^5.6.0",
|
||||||
|
"diff": "^7.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
|||||||
@@ -770,12 +770,18 @@ router.post("/validate", async (req, res) => {
|
|||||||
// Compare original and corrected data
|
// Compare original and corrected data
|
||||||
if (aiResponse.correctedData) {
|
if (aiResponse.correctedData) {
|
||||||
console.log("📊 Changes summary:");
|
console.log("📊 Changes summary:");
|
||||||
|
|
||||||
|
// Debug: Log the first product's fields
|
||||||
|
if (products.length > 0) {
|
||||||
|
console.log("🔍 First product fields:", Object.keys(products[0]));
|
||||||
|
}
|
||||||
|
|
||||||
products.forEach((original, index) => {
|
products.forEach((original, index) => {
|
||||||
const corrected = aiResponse.correctedData[index];
|
const corrected = aiResponse.correctedData[index];
|
||||||
if (corrected) {
|
if (corrected) {
|
||||||
const productChanges = {
|
const productChanges = {
|
||||||
productIndex: index,
|
productIndex: index,
|
||||||
title: original.title || `Product ${index + 1}`,
|
title: original.name || original.title || `Product ${index + 1}`,
|
||||||
changes: []
|
changes: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -817,8 +823,7 @@ router.post("/validate", async (req, res) => {
|
|||||||
const endTime = new Date();
|
const endTime = new Date();
|
||||||
let performanceMetrics = {
|
let performanceMetrics = {
|
||||||
promptLength,
|
promptLength,
|
||||||
productCount: products.length,
|
productCount: products.length
|
||||||
processingTimeSeconds: (endTime - startTime) / 1000
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -833,14 +838,13 @@ router.post("/validate", async (req, res) => {
|
|||||||
// Insert performance data into the local PostgreSQL database
|
// Insert performance data into the local PostgreSQL database
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO ai_validation_performance
|
`INSERT INTO ai_validation_performance
|
||||||
(prompt_length, product_count, start_time, end_time, duration_seconds)
|
(prompt_length, product_count, start_time, end_time)
|
||||||
VALUES ($1, $2, $3, $4, $5)`,
|
VALUES ($1, $2, $3, $4)`,
|
||||||
[
|
[
|
||||||
promptLength,
|
promptLength,
|
||||||
products.length,
|
products.length,
|
||||||
startTime,
|
startTime.toISOString(),
|
||||||
endTime,
|
endTime.toISOString()
|
||||||
(endTime - startTime) / 1000
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
10
inventory/package-lock.json
generated
10
inventory/package-lock.json
generated
@@ -65,6 +65,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"diff": "^7.0.0",
|
||||||
"framer-motion": "^12.4.4",
|
"framer-motion": "^12.4.4",
|
||||||
"js-levenshtein": "^1.1.6",
|
"js-levenshtein": "^1.1.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -6502,6 +6503,15 @@
|
|||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dlv": {
|
"node_modules/dlv": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"diff": "^7.0.0",
|
||||||
"framer-motion": "^12.4.4",
|
"framer-motion": "^12.4.4",
|
||||||
"js-levenshtein": "^1.1.6",
|
"js-levenshtein": "^1.1.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ import type { GlobalSelections } from "../MatchColumnsStep/MatchColumnsStep"
|
|||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { CheckIcon } from "lucide-react"
|
import { CheckIcon } from "lucide-react"
|
||||||
|
import * as Diff from 'diff'
|
||||||
|
|
||||||
// Template interface
|
// Template interface
|
||||||
interface Template {
|
interface Template {
|
||||||
@@ -2128,6 +2129,61 @@ export const ValidationStep = <T extends string>({
|
|||||||
return String(value);
|
return String(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function to highlight differences between two text values using the diff library
|
||||||
|
const highlightDifferences = (original: string | null | undefined, corrected: string | null | undefined): { originalHtml: string, correctedHtml: string } => {
|
||||||
|
// Handle null/undefined values
|
||||||
|
let originalStr = original === null || original === undefined ? '' : String(original);
|
||||||
|
let correctedStr = corrected === null || corrected === undefined ? '' : String(corrected);
|
||||||
|
|
||||||
|
// If they're identical, return without highlighting
|
||||||
|
if (originalStr === correctedStr) {
|
||||||
|
return {
|
||||||
|
originalHtml: originalStr,
|
||||||
|
correctedHtml: correctedStr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = Diff.diffWords(originalStr, correctedStr);
|
||||||
|
|
||||||
|
let originalHtml = '';
|
||||||
|
let correctedHtml = '';
|
||||||
|
|
||||||
|
diff.forEach((part: Diff.Change) => {
|
||||||
|
// Create escaped HTML
|
||||||
|
const escapedValue = part.value
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
|
if (part.added) {
|
||||||
|
// Added parts only show in the corrected version (green)
|
||||||
|
correctedHtml += `<span class="text-green-600 font-medium">${escapedValue}</span>`;
|
||||||
|
} else if (part.removed) {
|
||||||
|
// Removed parts only show in the original version (red)
|
||||||
|
originalHtml += `<span class="text-red-600 font-medium">${escapedValue}</span>`;
|
||||||
|
} else {
|
||||||
|
// Unchanged parts show in both versions
|
||||||
|
originalHtml += escapedValue;
|
||||||
|
correctedHtml += escapedValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalHtml,
|
||||||
|
correctedHtml
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get field display value with highlighted differences
|
||||||
|
const getFieldDisplayValueWithHighlight = (fieldKey: string, originalValue: any, correctedValue: any): { originalHtml: string, correctedHtml: string } => {
|
||||||
|
const originalDisplay = getFieldDisplayValue(fieldKey, originalValue);
|
||||||
|
const correctedDisplay = getFieldDisplayValue(fieldKey, correctedValue);
|
||||||
|
|
||||||
|
return highlightDifferences(originalDisplay, correctedDisplay);
|
||||||
|
};
|
||||||
|
|
||||||
// Add a function to revert a specific AI validation change
|
// Add a function to revert a specific AI validation change
|
||||||
const revertAiChange = (productIndex: number, fieldKey: string) => {
|
const revertAiChange = (productIndex: number, fieldKey: string) => {
|
||||||
// Ensure we have the original data
|
// Ensure we have the original data
|
||||||
@@ -2334,92 +2390,171 @@ export const ValidationStep = <T extends string>({
|
|||||||
{aiValidationDetails.changeDetails && aiValidationDetails.changeDetails.length > 0 ? (
|
{aiValidationDetails.changeDetails && aiValidationDetails.changeDetails.length > 0 ? (
|
||||||
<div className="mb-6 space-y-6">
|
<div className="mb-6 space-y-6">
|
||||||
<h3 className="font-semibold text-lg">Detailed Changes:</h3>
|
<h3 className="font-semibold text-lg">Detailed Changes:</h3>
|
||||||
{aiValidationDetails.changeDetails.map((product, i) => (
|
{aiValidationDetails.changeDetails.map((product, i) => {
|
||||||
<div key={i} className="border rounded-md p-4">
|
// Find the title change if it exists
|
||||||
<h4 className="font-medium text-base mb-3">{product.title}</h4>
|
const titleChange = product.changes.find(change => change.field === 'title');
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="w-1/4">Field</TableHead>
|
|
||||||
<TableHead className="w-3/8">Original Value</TableHead>
|
|
||||||
<TableHead className="w-3/8">Corrected Value</TableHead>
|
|
||||||
<TableHead className="w-1/8 text-right">Action</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{product.changes.map((change, j) => {
|
|
||||||
const field = fields.find(f => f.key === change.field);
|
|
||||||
const isReverted = isChangeReverted(product.productIndex, change.field);
|
|
||||||
|
|
||||||
return (
|
// Get the best title to display:
|
||||||
<TableRow key={j} className={isReverted ? "bg-muted/30" : ""}>
|
// 1. Use corrected title if available
|
||||||
<TableCell className="font-medium">
|
// 2. Use original title if available
|
||||||
{field?.label || change.field}
|
// 3. Try to find a descriptive field like SKU, barcode, or description
|
||||||
{isReverted && (
|
// 4. Fall back to "Product X" only as a last resort
|
||||||
<Badge variant="outline" className="ml-2 bg-green-50 text-green-700 border-green-200">
|
let displayTitle = "Product";
|
||||||
Reverted
|
|
||||||
</Badge>
|
// First check for a name field, which is commonly used
|
||||||
)}
|
const nameChange = product.changes.find(change =>
|
||||||
</TableCell>
|
change.field.toLowerCase() === 'name' ||
|
||||||
<TableCell>
|
change.field.toLowerCase() === 'title');
|
||||||
<div className="space-y-1">
|
|
||||||
<div className={isReverted ? "font-medium" : ""}>
|
// Debug: Log the product changes to see what fields are available
|
||||||
{getFieldDisplayValue(change.field, change.original)}
|
console.log(`Product ${i} changes:`, product.changes.map(c => c.field));
|
||||||
</div>
|
console.log(`Product ${i} title:`, product.title);
|
||||||
{/* Show raw value if it's an ID */}
|
|
||||||
{change.original && typeof change.original === 'string' &&
|
// First use the product title directly if it's available and not a generic "Product X"
|
||||||
!isNaN(Number(change.original)) &&
|
if (product.title && !product.title.startsWith('Product ')) {
|
||||||
getFieldDisplayValue(change.field, change.original) !== change.original && (
|
displayTitle = product.title;
|
||||||
<div className="text-xs text-muted-foreground">ID: {change.original}</div>
|
} else if (nameChange && nameChange.corrected) {
|
||||||
)}
|
// Next option: Use the corrected name/title
|
||||||
</div>
|
displayTitle = String(nameChange.corrected);
|
||||||
</TableCell>
|
} else if (nameChange && nameChange.original) {
|
||||||
<TableCell>
|
// Next option: Use the original name/title
|
||||||
<div className="space-y-1">
|
displayTitle = String(nameChange.original);
|
||||||
<div className={isReverted ? "line-through text-muted-foreground" : ""}>
|
} else {
|
||||||
{getFieldDisplayValue(change.field, change.corrected)}
|
// Try to find another identifying field
|
||||||
</div>
|
const identifyingFields = ['sku', 'barcode', 'upc', 'description', 'shortdescription'];
|
||||||
{/* Show raw value if it's an ID */}
|
for (const fieldName of identifyingFields) {
|
||||||
{change.corrected && typeof change.corrected === 'string' &&
|
const fieldChange = product.changes.find(change =>
|
||||||
!isNaN(Number(change.corrected)) &&
|
change.field.toLowerCase() === fieldName.toLowerCase());
|
||||||
getFieldDisplayValue(change.field, change.corrected) !== change.corrected && (
|
|
||||||
<div className="text-xs text-muted-foreground">ID: {change.corrected}</div>
|
if (fieldChange) {
|
||||||
)}
|
const value = fieldChange.corrected || fieldChange.original;
|
||||||
</div>
|
if (value) {
|
||||||
</TableCell>
|
// Use the field name and value as the title
|
||||||
<TableCell className="text-right">
|
displayTitle = `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}: ${String(value).substring(0, 30)}${String(value).length > 30 ? '...' : ''}`;
|
||||||
<div className="mt-2">
|
break;
|
||||||
{isReverted ? (
|
}
|
||||||
<Button
|
}
|
||||||
variant="ghost"
|
}
|
||||||
size="sm"
|
|
||||||
className="text-green-600 bg-green-50 hover:bg-green-100 hover:text-green-700"
|
// If we still don't have a good title, use the index
|
||||||
disabled
|
if (displayTitle === "Product") {
|
||||||
>
|
displayTitle = `Product ${i + 1}`;
|
||||||
<CheckIcon className="w-4 h-4 mr-1" />
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={i} className="border rounded-md p-4">
|
||||||
|
<h4 className="font-medium text-base mb-3">
|
||||||
|
{displayTitle}
|
||||||
|
</h4>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-1/5">Field</TableHead>
|
||||||
|
<TableHead className="w-2/5">Original Value</TableHead>
|
||||||
|
<TableHead className="w-2/5">Corrected Value</TableHead>
|
||||||
|
<TableHead className="w-1/12 text-right">Action</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{product.changes.map((change: ChangeDetail, j: number) => {
|
||||||
|
const field = fields.find(f => f.key === change.field);
|
||||||
|
const isReverted = isChangeReverted(product.productIndex, change.field);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={j} className={isReverted ? "bg-muted/30" : ""}>
|
||||||
|
<TableCell className="font-medium">
|
||||||
|
{field?.label || change.field}
|
||||||
|
{isReverted && (
|
||||||
|
<Badge variant="outline" className="ml-2 bg-green-50 text-green-700 border-green-200">
|
||||||
Reverted
|
Reverted
|
||||||
</Button>
|
</Badge>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
// Call the revert function directly
|
|
||||||
revertAiChange(product.productIndex, change.field);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Revert Change
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
</TableRow>
|
<div className="space-y-1">
|
||||||
);
|
{isReverted ? (
|
||||||
})}
|
<div className="font-medium">
|
||||||
</TableBody>
|
{getFieldDisplayValue(change.field, change.original)}
|
||||||
</Table>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
))}
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: getFieldDisplayValueWithHighlight(
|
||||||
|
change.field,
|
||||||
|
change.original,
|
||||||
|
change.corrected
|
||||||
|
).originalHtml
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Show raw value if it's an ID */}
|
||||||
|
{change.original && typeof change.original === 'string' &&
|
||||||
|
!isNaN(Number(change.original)) &&
|
||||||
|
getFieldDisplayValue(change.field, change.original) !== change.original && (
|
||||||
|
<div className="text-xs text-muted-foreground">ID: {change.original}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{isReverted ? (
|
||||||
|
<div className="line-through text-muted-foreground">
|
||||||
|
{getFieldDisplayValue(change.field, change.corrected)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: getFieldDisplayValueWithHighlight(
|
||||||
|
change.field,
|
||||||
|
change.original,
|
||||||
|
change.corrected
|
||||||
|
).correctedHtml
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Show raw value if it's an ID */}
|
||||||
|
{change.corrected && typeof change.corrected === 'string' &&
|
||||||
|
!isNaN(Number(change.corrected)) &&
|
||||||
|
getFieldDisplayValue(change.field, change.corrected) !== change.corrected && (
|
||||||
|
<div className="text-xs text-muted-foreground">ID: {change.corrected}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<div className="mt-2">
|
||||||
|
{isReverted ? (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-green-600 bg-green-50 hover:bg-green-100 hover:text-green-700"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<CheckIcon className="w-4 h-4 mr-1" />
|
||||||
|
Reverted
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
// Call the revert function directly
|
||||||
|
revertAiChange(product.productIndex, change.field);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Revert Change
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
aiValidationDetails.changes.length > 0 && (
|
aiValidationDetails.changes.length > 0 && (
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -8,9 +8,17 @@
|
|||||||
"shadcn": "^1.0.0"
|
"shadcn": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/diff": "^7.0.1",
|
||||||
"ts-essentials": "^10.0.4"
|
"ts-essentials": "^10.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/diff": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-R/BHQFripuhW6XPXy05hIvXJQdQ4540KnTvEFHSLjXfHYM41liOLKgIJEyYYiQe796xpaMHfe4Uj/p7Uvng2vA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shadcn": {
|
"node_modules/shadcn": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shadcn/-/shadcn-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shadcn/-/shadcn-1.0.0.tgz",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"shadcn": "^1.0.0"
|
"shadcn": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/diff": "^7.0.1",
|
||||||
"ts-essentials": "^10.0.4"
|
"ts-essentials": "^10.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user