added translate textadmin excel
This commit is contained in:
Generated
+444
-5
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@lenne.tech/gluegun-menu": "^0.0.7",
|
||||
"@mailchimp/mailchimp_transactional": "^1.0.59",
|
||||
"@octokit/core": "4.2.0",
|
||||
@@ -18,9 +19,11 @@
|
||||
"enquirer": "^2.4.1",
|
||||
"gluegun": "latest",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"openai": "^4.73.0",
|
||||
"pg": "^8.11.3",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"terminal-table-output": "^1.4.0"
|
||||
"terminal-table-output": "^1.4.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"bin": {
|
||||
"pwcli": "bin/pwcli"
|
||||
@@ -850,6 +853,15 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz",
|
||||
"integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"dev": true,
|
||||
@@ -1718,12 +1730,21 @@
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
|
||||
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"license": "MIT"
|
||||
@@ -2006,6 +2027,18 @@
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"dev": true,
|
||||
@@ -2036,6 +2069,27 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
|
||||
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"humanize-ms": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"dev": true,
|
||||
@@ -2365,6 +2419,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
@@ -2402,6 +2469,19 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"license": "MIT",
|
||||
@@ -2661,6 +2741,15 @@
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/collect-v8-coverage": {
|
||||
"version": "1.0.2",
|
||||
"dev": true,
|
||||
@@ -2720,6 +2809,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
@@ -2813,6 +2914,20 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"dev": true,
|
||||
@@ -2888,6 +3003,51 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
@@ -3157,6 +3317,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
@@ -3403,17 +3572,49 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data-encoder": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
|
||||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/formdata-node": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
||||
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "1.0.0",
|
||||
"web-streams-polyfill": "4.0.0-beta.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-jetpack": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-4.3.1.tgz",
|
||||
@@ -3440,6 +3641,15 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"dev": true,
|
||||
@@ -3456,6 +3666,30 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"dev": true,
|
||||
@@ -3464,6 +3698,19 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"license": "MIT",
|
||||
@@ -3611,6 +3858,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"dev": true,
|
||||
@@ -3628,6 +3887,45 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"dev": true,
|
||||
@@ -3640,6 +3938,15 @@
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
"dev": true,
|
||||
@@ -4706,6 +5013,15 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT"
|
||||
@@ -4776,7 +5092,6 @@
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-postinstall": {
|
||||
@@ -4802,6 +5117,26 @@
|
||||
"version": "1.0.5",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"license": "MIT",
|
||||
@@ -4875,6 +5210,51 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "4.104.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
|
||||
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.2.1",
|
||||
"form-data-encoder": "1.7.2",
|
||||
"formdata-node": "^4.3.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
},
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/openai/node_modules/@types/node": {
|
||||
"version": "18.19.130",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/openai/node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"dev": true,
|
||||
@@ -5579,6 +5959,18 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"dev": true,
|
||||
@@ -6044,7 +6436,6 @@
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.8.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
@@ -6162,6 +6553,15 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "4.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"license": "BSD-2-Clause"
|
||||
@@ -6187,6 +6587,24 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"dev": true,
|
||||
@@ -6323,6 +6741,27 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
+6
-2
@@ -13,7 +13,8 @@
|
||||
"compile": "tsc -p .",
|
||||
"copy-templates": "if [ -e ./src/templates ]; then cp -a ./src/templates ./build/; fi",
|
||||
"copy-promptcontextlibrary-txts": "mkdir -p build/commands/ai/promptcontextlibrary && cp src/commands/ai/promptcontextlibrary/*.txt build/commands/ai/promptcontextlibrary/ || true",
|
||||
"build": "yarn format && yarn clean-build && yarn compile && yarn copy-templates && yarn copy-promptcontextlibrary-txts",
|
||||
"copy-translate-excel-txts": "mkdir -p build/commands/ai/translate_excel && cp src/commands/ai/translate_excel/*.txt build/commands/ai/translate_excel/ || true",
|
||||
"build": "yarn format && yarn clean-build && yarn compile && yarn copy-templates && yarn copy-promptcontextlibrary-txts && yarn copy-translate-excel-txts",
|
||||
"prepublishOnly": "yarn build",
|
||||
"test": "jest",
|
||||
"watch": "jest --watch",
|
||||
@@ -31,6 +32,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@lenne.tech/gluegun-menu": "^0.0.7",
|
||||
"@mailchimp/mailchimp_transactional": "^1.0.59",
|
||||
"@octokit/core": "4.2.0",
|
||||
@@ -40,9 +42,11 @@
|
||||
"enquirer": "^2.4.1",
|
||||
"gluegun": "latest",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"openai": "^4.73.0",
|
||||
"pg": "^8.11.3",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"terminal-table-output": "^1.4.0"
|
||||
"terminal-table-output": "^1.4.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
You are a translation expert and copywriter working with translating text for the company Photowall.
|
||||
Photowall is a company with a global e-commerce website selling custom printed wallpaper, canvas, posters and paint.
|
||||
Photowall have two different types of wallpaper. One type is non repeating patterns (wall murals) and one is repeating patterns (wallpapers).
|
||||
Photowall sell a highend wallpaper product of exclusive quality.
|
||||
|
||||
Translate to natural sounding language.
|
||||
Make the text in the translated text some and flow naturally for that specific language.
|
||||
Use the correct casing for the translated language.
|
||||
|
||||
The tone of voice should be premium and highend.
|
||||
|
||||
When translating the word "wallpaper" use these translations
|
||||
"Danish (Denmark)": "tapet",
|
||||
"German (Germany)": "tapete",
|
||||
"English (Europe)": "wallpaper",
|
||||
"English (United Kingdom)": "wallpaper",
|
||||
"English (United States)": "wallpaper",
|
||||
"Spanish (Spain)": "papel pintado",
|
||||
"Finnish (Finland)": "tapetti",
|
||||
"French (France)": "papier peint",
|
||||
"Italian (Italy)": "carta da parati",
|
||||
"Japanese": "壁紙",
|
||||
"Dutch (Netherlands)": "behang",
|
||||
"Norwegian Nynorsk (Norway)": "tapet",
|
||||
"Polish (Poland)": "tapeta",
|
||||
"Portuguese (Portugal)": "papel de parede",
|
||||
"Romanian (Romania)": "tapet",
|
||||
"Swedish (Sweden)": "tapet"
|
||||
|
||||
When translating the word "wallpapers" use these translations
|
||||
"Danish (Denmark)": "tapeter",
|
||||
"German (Germany)": "tapeten",
|
||||
"English (Europe)": "wallpapers",
|
||||
"English (United Kingdom)": "wallpapers",
|
||||
"English (United States)": "wallpapers",
|
||||
"Spanish (Spain)": "papeles pintados",
|
||||
"Finnish (Finland)": "tapetit",
|
||||
"French (France)": "papiers peints",
|
||||
"Italian (Italy)": "carte da parati",
|
||||
"Japanese": "壁紙",
|
||||
"Dutch (Netherlands)": "behang",
|
||||
"Norwegian Nynorsk (Norway)": "tapeter",
|
||||
"Polish (Poland)": "tapety",
|
||||
"Portuguese (Portugal)": "papéis de parede",
|
||||
"Romanian (Romania)": "tapete",
|
||||
"Swedish (Sweden)": "tapeter"
|
||||
|
||||
When translating the word "wall mural" use these translations
|
||||
"Danish (Denmark)": "fototapet",
|
||||
"German (Germany)": "fototapete",
|
||||
"English (Europe)": "wall mural",
|
||||
"English (United Kingdom)": "wall mural",
|
||||
"English (United States)": "wall mural",
|
||||
"Spanish (Spain)": "fotomural",
|
||||
"Finnish (Finland)": "valokuvatapetti",
|
||||
"French (France)": "décoration murale",
|
||||
"Italian (Italy)": "decorazione murale",
|
||||
"Japanese": "ウォールミューラル",
|
||||
"Dutch (Netherlands)": "fotobehang",
|
||||
"Norwegian Nynorsk (Norway)": "fototapet",
|
||||
"Polish (Poland)": "fototapeta",
|
||||
"Portuguese (Portugal)": "mural de parede",
|
||||
"Romanian (Romania)": "fototapet",
|
||||
"Swedish (Sweden)": "fototapet"
|
||||
|
||||
When translating the word "wall murals" use these translations
|
||||
"Danish (Denmark)": "fototapeter",
|
||||
"German (Germany)": "fototapeten",
|
||||
"English (Europe)": "wall murals",
|
||||
"English (United Kingdom)": "wall murals",
|
||||
"English (United States)": "wall murals",
|
||||
"Spanish (Spain)": "fotomurales",
|
||||
"Finnish (Finland)": "valokuvatapetit",
|
||||
"French (France)": "papiers peints panoramiques",
|
||||
"Italian (Italy)": "fotomurali",
|
||||
"Japanese": "ウォールミューラル",
|
||||
"Dutch (Netherlands)": "fotobehang",
|
||||
"Norwegian Nynorsk (Norway)": "fototapeter",
|
||||
"Polish (Poland)": "fototapety",
|
||||
"Portuguese (Portugal)": "murais de parede",
|
||||
"Romanian (Romania)": "fototapete",
|
||||
"Swedish (Sweden)": "fototapeter"
|
||||
|
||||
The languages are: Danish, German, English, Spanish, Finnish, French, Italian, Japanese, Dutch, Norwegian, Polish, Portuguese, Romanian, Swedish
|
||||
|
||||
The english input text for translation is
|
||||
<INPUT START>
|
||||
{input}
|
||||
</INPUT END>
|
||||
|
||||
!IMPORTANT!
|
||||
Return only the result in a JSON format defined within the <OUTPUT> and </OUTPUT>:
|
||||
<OUTPUT>
|
||||
{
|
||||
"Danish (Denmark)": "string",
|
||||
"German (Germany)": "string",
|
||||
"English (Europe)": "string",
|
||||
"English (United Kingdom)": "string",
|
||||
"English (United States)": "string",
|
||||
"Spanish (Spain)": "string",
|
||||
"Finnish (Finland)": "string",
|
||||
"French (France)": "string",
|
||||
"Italian (Italy)": "string",
|
||||
"Japanese": "string",
|
||||
"Dutch (Netherlands)": "string",
|
||||
"Norwegian Nynorsk (Norway)": "string",
|
||||
"Polish (Poland)": "string",
|
||||
"Portuguese (Portugal)": "string",
|
||||
"Romanian (Romania)": "string",
|
||||
"Swedish (Sweden)": "string"
|
||||
}
|
||||
</OUTPUT>
|
||||
|
||||
Output the JSON string directly without any additional formatting or delimiters.
|
||||
@@ -0,0 +1,589 @@
|
||||
import { GluegunMenuToolbox } from '@lenne.tech/gluegun-menu';
|
||||
import chalk = require('chalk');
|
||||
import { getSettings } from '../../../globals';
|
||||
const XLSX = require('xlsx');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Select } = require('enquirer');
|
||||
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
// Debug mode: set to true to save all AI model responses to JSON files
|
||||
const DEBUG_MODE = false;
|
||||
|
||||
// Function to save debug response to JSON file
|
||||
function saveDebugResponse(
|
||||
response: string,
|
||||
debugDir: string,
|
||||
filename: string,
|
||||
metadata: any = {},
|
||||
): void {
|
||||
if (!DEBUG_MODE) return;
|
||||
|
||||
try {
|
||||
// Ensure debug directory exists
|
||||
if (!fs.existsSync(debugDir)) {
|
||||
fs.mkdirSync(debugDir, { recursive: true });
|
||||
}
|
||||
|
||||
const debugData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
response: response,
|
||||
...metadata,
|
||||
};
|
||||
|
||||
const filePath = path.join(debugDir, filename);
|
||||
fs.writeFileSync(filePath, JSON.stringify(debugData, null, 2), 'utf8');
|
||||
} catch (error) {
|
||||
console.error('Error saving debug response:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to extract JSON from text that might be wrapped in markdown code blocks
|
||||
function extractJSON(text: string): string {
|
||||
const jsonRegex = /```(?:json)?\s*([\s\S]*?)```/;
|
||||
const match = text.match(jsonRegex);
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
// Function to validate and fix JSON if needed
|
||||
async function validateAndFixJSON(
|
||||
response: string,
|
||||
processorFunction: (
|
||||
input: string,
|
||||
prompt: string,
|
||||
apiKey: string,
|
||||
model: string,
|
||||
debugDir?: string,
|
||||
debugMetadata?: any,
|
||||
) => Promise<string | null>,
|
||||
apiKey: string,
|
||||
model: string,
|
||||
debugDir?: string,
|
||||
debugMetadata?: any,
|
||||
): Promise<{ isValid: boolean; json: any; rawText: string } | null> {
|
||||
// Try to extract and parse JSON from the response
|
||||
const extractedJSON = extractJSON(response);
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(extractedJSON);
|
||||
return { isValid: true, json: parsed, rawText: extractedJSON };
|
||||
} catch (error) {
|
||||
// JSON is invalid, try to fix it once
|
||||
console.log(
|
||||
chalk.yellow('⚠️ Invalid JSON detected, attempting to fix...'),
|
||||
);
|
||||
|
||||
const fixPrompt = `The following text should be valid JSON but contains errors. Please fix it to be valid JSON format.
|
||||
|
||||
IMPORTANT INSTRUCTIONS:
|
||||
- Output ONLY valid JSON, nothing else
|
||||
- Do not add any explanations, comments, or markdown formatting
|
||||
- Do not wrap the output in code blocks or backticks
|
||||
- Preserve all the data from the original text
|
||||
- Fix any syntax errors like missing commas, quotes, brackets, or braces
|
||||
- Ensure all string values are properly quoted
|
||||
- Ensure all keys are properly quoted
|
||||
- Remove any trailing commas
|
||||
- Make sure the JSON structure is complete and properly closed
|
||||
|
||||
BROKEN TEXT TO FIX:
|
||||
${response}
|
||||
|
||||
OUTPUT ONLY THE FIXED JSON:`;
|
||||
|
||||
const fixedResponse = await processorFunction(
|
||||
'',
|
||||
fixPrompt,
|
||||
apiKey,
|
||||
model,
|
||||
debugDir,
|
||||
{ ...debugMetadata, isFixAttempt: true, originalResponse: response },
|
||||
);
|
||||
|
||||
if (!fixedResponse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to parse the fixed response
|
||||
const fixedExtractedJSON = extractJSON(fixedResponse);
|
||||
try {
|
||||
const parsed = JSON.parse(fixedExtractedJSON);
|
||||
console.log(chalk.green('✓ Successfully fixed invalid JSON'));
|
||||
return { isValid: true, json: parsed, rawText: fixedExtractedJSON };
|
||||
} catch (fixError) {
|
||||
console.error(chalk.red('✗ Failed to fix JSON even after retry'));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process text with Gemini AI
|
||||
async function processWithGemini(
|
||||
inputText: string,
|
||||
promptTemplate: string,
|
||||
apiKey: string,
|
||||
model: string,
|
||||
debugDir?: string,
|
||||
debugMetadata?: any,
|
||||
) {
|
||||
const generationConfig = {
|
||||
temperature: 0.7, // Slightly lower for more consistent JSON output
|
||||
topP: 0.95,
|
||||
topK: 40,
|
||||
maxOutputTokens: 32768, // Significantly increased for large translation responses (Gemini 2.5 Pro supports up to 65,536)
|
||||
};
|
||||
|
||||
const genAI = new GoogleGenerativeAI(apiKey);
|
||||
const geminiModel = genAI.getGenerativeModel({
|
||||
model: model,
|
||||
generationConfig,
|
||||
});
|
||||
|
||||
try {
|
||||
// If inputText is provided, replace {input} placeholder, otherwise use prompt as-is
|
||||
const prompt = inputText
|
||||
? promptTemplate.replace('{input}', inputText)
|
||||
: promptTemplate;
|
||||
const result = await geminiModel.generateContent(prompt);
|
||||
const response = result.response;
|
||||
const responseText = response.text();
|
||||
|
||||
// Save debug response if debug mode is enabled
|
||||
if (DEBUG_MODE && debugDir) {
|
||||
const filename = `gemini_${Date.now()}_${debugMetadata?.rowNumber || 'unknown'}.json`;
|
||||
saveDebugResponse(responseText, debugDir, filename, {
|
||||
provider: 'Gemini',
|
||||
model: model,
|
||||
inputText: inputText,
|
||||
prompt: prompt,
|
||||
...debugMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
return responseText;
|
||||
} catch (error) {
|
||||
console.error('Error processing with Gemini:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Process text with OpenAI
|
||||
async function processWithOpenAI(
|
||||
inputText: string,
|
||||
promptTemplate: string,
|
||||
apiKey: string,
|
||||
model: string,
|
||||
debugDir?: string,
|
||||
debugMetadata?: any,
|
||||
) {
|
||||
const openai = new OpenAI({ apiKey });
|
||||
|
||||
try {
|
||||
// If inputText is provided, replace {input} placeholder, otherwise use prompt as-is
|
||||
const prompt = inputText
|
||||
? promptTemplate.replace('{input}', inputText)
|
||||
: promptTemplate;
|
||||
const response = await openai.chat.completions.create({
|
||||
model: model,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
});
|
||||
|
||||
const responseText = response.choices[0]?.message?.content || null;
|
||||
|
||||
// Save debug response if debug mode is enabled
|
||||
if (DEBUG_MODE && debugDir && responseText) {
|
||||
const filename = `openai_${Date.now()}_${debugMetadata?.rowNumber || 'unknown'}.json`;
|
||||
saveDebugResponse(responseText, debugDir, filename, {
|
||||
provider: 'OpenAI',
|
||||
model: model,
|
||||
inputText: inputText,
|
||||
prompt: prompt,
|
||||
...debugMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
return responseText;
|
||||
} catch (error) {
|
||||
console.error('Error processing with OpenAI:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'translate_excel',
|
||||
alias: ['te'],
|
||||
description: 'Translate Excel files using AI (te)',
|
||||
hidden: false,
|
||||
run: async (toolbox: GluegunMenuToolbox) => {
|
||||
const { print, filesystem } = toolbox;
|
||||
|
||||
print.info(chalk.cyan('📊 Excel Translation Tool'));
|
||||
print.info('');
|
||||
|
||||
// Get settings to check for API keys
|
||||
const config = await getSettings(toolbox);
|
||||
if (!config) {
|
||||
print.error(
|
||||
chalk.red('❌ No configuration found. Please run pwcli setup settings'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check which API keys are configured
|
||||
const hasGemini = config.llm_api_keys?.gemini_api_key;
|
||||
const hasOpenAI = config.llm_api_keys?.openai_api_key;
|
||||
|
||||
if (!hasGemini && !hasOpenAI) {
|
||||
print.error(
|
||||
chalk.red(
|
||||
'❌ No AI API keys configured. Please run pwcli setup settings',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Find Excel files in current directory
|
||||
const currentDir = process.cwd();
|
||||
const files = fs.readdirSync(currentDir);
|
||||
const excelFiles = files.filter((file: string) =>
|
||||
file.toLowerCase().endsWith('.xlsx'),
|
||||
);
|
||||
|
||||
if (excelFiles.length === 0) {
|
||||
print.error(
|
||||
chalk.red('❌ No Excel (.xlsx) files found in current directory'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Let user select an Excel file
|
||||
const filePrompt = new Select({
|
||||
name: 'excelFile',
|
||||
message: 'Select the Excel file to translate:',
|
||||
choices: excelFiles,
|
||||
});
|
||||
|
||||
const selectedFile = await filePrompt.run();
|
||||
print.info(chalk.gray(`Selected file: ${selectedFile}`));
|
||||
print.info('');
|
||||
|
||||
// Step 3: Find prompt .txt files in translate_excel folder
|
||||
const promptDir = path.join(__dirname, '.');
|
||||
const promptFiles = fs
|
||||
.readdirSync(promptDir)
|
||||
.filter((file: string) => file.toLowerCase().endsWith('.txt'));
|
||||
|
||||
if (promptFiles.length === 0) {
|
||||
print.error(
|
||||
chalk.red('❌ No prompt (.txt) files found in translate_excel folder'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a mapping of display names to actual filenames
|
||||
const promptDisplayNames = promptFiles.map((file: string) => {
|
||||
// Remove .txt extension and convert to Title Case
|
||||
const nameWithoutExt = file.replace(/\.txt$/i, '');
|
||||
// Replace underscores and hyphens with spaces, then title case
|
||||
return nameWithoutExt
|
||||
.replace(/[_-]/g, ' ')
|
||||
.split(' ')
|
||||
.map(
|
||||
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
|
||||
)
|
||||
.join(' ');
|
||||
});
|
||||
|
||||
// Create mapping object for later use
|
||||
const promptMapping = {};
|
||||
promptFiles.forEach((file: string, index: number) => {
|
||||
promptMapping[promptDisplayNames[index]] = file;
|
||||
});
|
||||
|
||||
// Step 4: Let user select a prompt file
|
||||
const promptPrompt = new Select({
|
||||
name: 'promptFile',
|
||||
message: 'Select the translation prompt to use:',
|
||||
choices: promptDisplayNames,
|
||||
});
|
||||
|
||||
const selectedPromptDisplay = await promptPrompt.run();
|
||||
const selectedPromptFile = promptMapping[selectedPromptDisplay];
|
||||
const promptPath = path.join(promptDir, selectedPromptFile);
|
||||
const promptTemplate = fs.readFileSync(promptPath, 'utf8');
|
||||
print.info(chalk.gray(`Selected prompt: ${selectedPromptDisplay}`));
|
||||
print.info('');
|
||||
|
||||
// Step 5: Let user select AI provider
|
||||
const providerChoices = [];
|
||||
if (hasGemini) providerChoices.push('Gemini');
|
||||
if (hasOpenAI) providerChoices.push('OpenAI');
|
||||
|
||||
const providerPrompt = new Select({
|
||||
name: 'provider',
|
||||
message: 'Select AI provider:',
|
||||
choices: providerChoices,
|
||||
});
|
||||
|
||||
const selectedProvider = await providerPrompt.run();
|
||||
print.info(chalk.gray(`Selected provider: ${selectedProvider}`));
|
||||
print.info('');
|
||||
|
||||
// Step 6: Let user select model based on provider
|
||||
let modelChoices: string[] = [];
|
||||
let selectedModel = '';
|
||||
|
||||
if (selectedProvider === 'Gemini') {
|
||||
modelChoices = [
|
||||
'gemini-flash-latest',
|
||||
'gemini-flash-lite-latest',
|
||||
'gemini-2.5-pro',
|
||||
];
|
||||
const modelPrompt = new Select({
|
||||
name: 'model',
|
||||
message: 'Select Gemini model:',
|
||||
choices: modelChoices,
|
||||
});
|
||||
selectedModel = await modelPrompt.run();
|
||||
} else if (selectedProvider === 'OpenAI') {
|
||||
modelChoices = ['gpt-5-nano', 'gpt-5-mini', 'gpt-5.1'];
|
||||
const modelPrompt = new Select({
|
||||
name: 'model',
|
||||
message: 'Select OpenAI model:',
|
||||
choices: modelChoices,
|
||||
});
|
||||
selectedModel = await modelPrompt.run();
|
||||
}
|
||||
|
||||
print.info(chalk.gray(`Selected model: ${selectedModel}`));
|
||||
print.info('');
|
||||
|
||||
// Step 7: Read and process the Excel file
|
||||
const filePath = path.join(currentDir, selectedFile);
|
||||
const workbook = XLSX.readFile(filePath);
|
||||
|
||||
// Use first sheet
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
|
||||
// Convert to JSON
|
||||
const data = XLSX.utils.sheet_to_json(worksheet);
|
||||
|
||||
if (data.length === 0) {
|
||||
print.error(chalk.red('❌ Excel file is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get original column order
|
||||
const originalColumns = Object.keys(data[0]);
|
||||
|
||||
// Check if "English (Europe)" column exists
|
||||
const inputColumn = 'English (Europe)';
|
||||
if (!data[0].hasOwnProperty(inputColumn)) {
|
||||
print.error(
|
||||
chalk.red(
|
||||
`❌ Column "${inputColumn}" not found. Available columns: ${originalColumns.join(', ')}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
print.info(
|
||||
chalk.cyan(`Processing ${data.length} rows from sheet "${sheetName}"...`),
|
||||
);
|
||||
print.info('');
|
||||
|
||||
// Set up debug directory if debug mode is enabled
|
||||
const debugDir = DEBUG_MODE
|
||||
? path.join(currentDir, `${path.parse(selectedFile).name}_debug`)
|
||||
: undefined;
|
||||
|
||||
if (DEBUG_MODE && debugDir) {
|
||||
print.info(
|
||||
chalk.gray(`Debug mode enabled. Saving responses to: ${debugDir}`),
|
||||
);
|
||||
print.info('');
|
||||
}
|
||||
|
||||
// Step 8: Process each row with AI
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const row = data[i];
|
||||
const inputText = row[inputColumn];
|
||||
|
||||
if (!inputText || inputText.trim() === '') {
|
||||
print.info(
|
||||
chalk.gray(`Skipping row ${i + 1}/${data.length} (empty input text)`),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
print.info(
|
||||
chalk.yellow(
|
||||
`Processing row ${i + 1}/${data.length}: "${inputText.substring(0, 50)}${inputText.length > 50 ? '...' : ''}"`,
|
||||
),
|
||||
);
|
||||
|
||||
let aiResult = null;
|
||||
let processorFunction = null;
|
||||
let apiKey = null;
|
||||
|
||||
const debugMetadata = {
|
||||
rowNumber: i + 1,
|
||||
totalRows: data.length,
|
||||
inputColumn: inputColumn,
|
||||
selectedFile: selectedFile,
|
||||
selectedPrompt: selectedPromptDisplay,
|
||||
};
|
||||
|
||||
if (selectedProvider === 'Gemini') {
|
||||
processorFunction = processWithGemini;
|
||||
apiKey = config.llm_api_keys.gemini_api_key;
|
||||
aiResult = await processWithGemini(
|
||||
inputText,
|
||||
promptTemplate,
|
||||
apiKey,
|
||||
selectedModel,
|
||||
debugDir,
|
||||
debugMetadata,
|
||||
);
|
||||
} else if (selectedProvider === 'OpenAI') {
|
||||
processorFunction = processWithOpenAI;
|
||||
apiKey = config.llm_api_keys.openai_api_key;
|
||||
aiResult = await processWithOpenAI(
|
||||
inputText,
|
||||
promptTemplate,
|
||||
apiKey,
|
||||
selectedModel,
|
||||
debugDir,
|
||||
debugMetadata,
|
||||
);
|
||||
}
|
||||
|
||||
if (aiResult) {
|
||||
// Validate and fix JSON if needed
|
||||
const validationResult = await validateAndFixJSON(
|
||||
aiResult,
|
||||
processorFunction,
|
||||
apiKey,
|
||||
selectedModel,
|
||||
debugDir,
|
||||
debugMetadata,
|
||||
);
|
||||
|
||||
if (validationResult && validationResult.json) {
|
||||
const translationResult = validationResult.json;
|
||||
|
||||
// Update the row with translations from the LLM
|
||||
for (const [country, translation] of Object.entries(
|
||||
translationResult,
|
||||
)) {
|
||||
// Add translation if it's valid
|
||||
if (translation !== null && translation !== undefined) {
|
||||
row[country] = translation;
|
||||
}
|
||||
}
|
||||
|
||||
print.success(chalk.green(`✓ Translated row ${i + 1}`));
|
||||
} else {
|
||||
print.error(
|
||||
chalk.red(
|
||||
`❌ Error parsing AI response for row ${i + 1}: Invalid JSON even after retry attempt`,
|
||||
),
|
||||
);
|
||||
console.error('AI response:', aiResult);
|
||||
}
|
||||
} else {
|
||||
print.error(chalk.red(`❌ AI processing failed for row ${i + 1}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9: Write the translated data to a new Excel file
|
||||
const parsedPath = path.parse(filePath);
|
||||
const outputFilePath = path.join(
|
||||
parsedPath.dir,
|
||||
`${parsedPath.name}_translated${parsedPath.ext}`,
|
||||
);
|
||||
|
||||
// Define the expected column order for output
|
||||
const expectedColumnOrder = [
|
||||
'name',
|
||||
'category',
|
||||
'Danish (Denmark)',
|
||||
'German (Austria)',
|
||||
'German (Switzerland)',
|
||||
'German (Germany)',
|
||||
'German (Luxembourg)',
|
||||
'English (Australia)',
|
||||
'English (Canada)',
|
||||
'English (Estonia)',
|
||||
'English (Europe)',
|
||||
'English (United Kingdom)',
|
||||
'English (Ireland)',
|
||||
'English (New Zealand)',
|
||||
'English (Singapore)',
|
||||
'English (United States)',
|
||||
'Spanish (Spain)',
|
||||
'Finnish (Finland)',
|
||||
'French (Belgium)',
|
||||
'French (Canada)',
|
||||
'French (Switzerland)',
|
||||
'French (France)',
|
||||
'French (Luxembourg)',
|
||||
'Italian (Switzerland)',
|
||||
'Italian (Italy)',
|
||||
'Japanese',
|
||||
'Dutch (Belgium)',
|
||||
'Dutch (Netherlands)',
|
||||
'Norwegian Nynorsk (Norway)',
|
||||
'Polish (Poland)',
|
||||
'Portuguese (Portugal)',
|
||||
'Romanian (Romania)',
|
||||
'Swedish (Sweden)',
|
||||
];
|
||||
|
||||
// Build final column order: all expected columns + any extra columns from input
|
||||
const finalColumnOrder = [
|
||||
...expectedColumnOrder,
|
||||
...originalColumns.filter((col) => !expectedColumnOrder.includes(col)),
|
||||
];
|
||||
|
||||
// Ensure each row has all columns in the correct order (even if empty)
|
||||
const normalizedData = data.map((row) => {
|
||||
const normalizedRow = {};
|
||||
for (const col of finalColumnOrder) {
|
||||
normalizedRow[col] = row.hasOwnProperty(col) ? row[col] : '';
|
||||
}
|
||||
return normalizedRow;
|
||||
});
|
||||
|
||||
// Write with the specified column order
|
||||
const newWorksheet = XLSX.utils.json_to_sheet(normalizedData, {
|
||||
header: finalColumnOrder,
|
||||
});
|
||||
|
||||
const newWorkbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(newWorkbook, newWorksheet, sheetName);
|
||||
|
||||
XLSX.writeFile(newWorkbook, outputFilePath);
|
||||
|
||||
print.info('');
|
||||
print.success(
|
||||
chalk.green(
|
||||
`✅ Successfully exported translated data to ${outputFilePath}`,
|
||||
),
|
||||
);
|
||||
print.info(
|
||||
chalk.gray(`Processed ${data.length} rows from sheet "${sheetName}"`),
|
||||
);
|
||||
print.info('');
|
||||
|
||||
// Return to AI menu
|
||||
await toolbox.menu.showMenu('ai');
|
||||
},
|
||||
};
|
||||
@@ -114,6 +114,18 @@ module.exports = {
|
||||
localhost_url: '',
|
||||
localhost_username: '',
|
||||
localhost_password: '',
|
||||
llm_api_keys: {
|
||||
gemini_api_key: '',
|
||||
openai_api_key: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure llm_api_keys exists in existing configs
|
||||
if (!config.llm_api_keys) {
|
||||
config.llm_api_keys = {
|
||||
gemini_api_key: '',
|
||||
openai_api_key: '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,6 +161,16 @@ module.exports = {
|
||||
initial: config.localhost_password,
|
||||
}).run();
|
||||
|
||||
config.llm_api_keys.gemini_api_key = await new Input({
|
||||
message: `If you want to use Gemini AI features, please insert\nyour Gemini API key. Otherwise leave blank >`,
|
||||
initial: config.llm_api_keys.gemini_api_key,
|
||||
}).run();
|
||||
|
||||
config.llm_api_keys.openai_api_key = await new Input({
|
||||
message: `If you want to use OpenAI features, please insert\nyour OpenAI API key. Otherwise leave blank >`,
|
||||
initial: config.llm_api_keys.openai_api_key,
|
||||
}).run();
|
||||
|
||||
print.info(`
|
||||
|
||||
${JSON.stringify(config)}
|
||||
|
||||
@@ -14,6 +14,10 @@ export type TConfig = {
|
||||
localhost_url?: string;
|
||||
localhost_username?: string;
|
||||
localhost_password?: string;
|
||||
llm_api_keys?: {
|
||||
gemini_api_key: string;
|
||||
openai_api_key: string;
|
||||
};
|
||||
};
|
||||
export const getSettings = async (toolbox): Promise<TConfig | null> => {
|
||||
const { filesystem, print } = toolbox;
|
||||
|
||||
Reference in New Issue
Block a user