diff --git a/custom/imageGenerator.vue b/custom/imageGenerator.vue index 5a68fdc..8c9e979 100644 --- a/custom/imageGenerator.vue +++ b/custom/imageGenerator.vue @@ -181,6 +181,7 @@ import { callAdminForthApi } from '@/utils'; import { useI18n } from 'vue-i18n'; import adminforth from '@/adminforth'; import { ProgressBar } from '@/afcl'; +import * as Handlebars from 'handlebars'; const { t: $t } = useI18n(); @@ -213,28 +214,9 @@ onMounted(async () => { } // iterate over all variables in template and replace them with their values from props.record[field]. // if field is not present in props.record[field] then replace it with empty string and drop warning - const regex = /{{(.*?)}}/g; - const matches = template.match(regex); - if (matches) { - matches.forEach((match) => { - const field = match.replace(/{{|}}/g, '').trim(); - if (field in context) { - return; - } else if (field in props.record) { - context[field] = minifyField(props.record[field]); - } else { - adminforth.alert({ - message: $t('Field {{field}} defined in template but not found in record', { field }), - variant: 'warning', - timeout: 15, - }); - } - }); - } - - prompt.value = template.replace(regex, (_, field) => { - return context[field.trim()] || ''; - }); + const tpl = Handlebars.compile(template); + const compiledTemplate = tpl(props.record); + prompt.value = compiledTemplate; const recordId = props.record[props.meta.recorPkFieldName]; if (!recordId) return; diff --git a/custom/package-lock.json b/custom/package-lock.json index 84c997b..76ca29b 100644 --- a/custom/package-lock.json +++ b/custom/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@iconify-prerendered/vue-mdi": "^0.25.1718880438", + "handlebars": "^4.7.8", "medium-zoom": "^1.1.0" } }, @@ -201,6 +202,27 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "peer": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -215,6 +237,15 @@ "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.1.0.tgz", "integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==" }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -233,6 +264,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -267,6 +304,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -285,6 +331,19 @@ "node": ">=4" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/vue": { "version": "3.5.10", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.10.tgz", @@ -305,6 +364,12 @@ "optional": true } } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" } } } diff --git a/custom/package.json b/custom/package.json index 91c3e22..772b5e5 100644 --- a/custom/package.json +++ b/custom/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "@iconify-prerendered/vue-mdi": "^0.25.1718880438", + "handlebars": "^4.7.8", "medium-zoom": "^1.1.0" } } diff --git a/index.ts b/index.ts index e01bfe9..14a0b94 100644 --- a/index.ts +++ b/index.ts @@ -56,16 +56,6 @@ export default class UploadPlugin extends AdminForthPlugin { throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.label}"`); } - if (this.options.generation?.fieldsForContext) { - this.options.generation?.fieldsForContext.forEach((field: string) => { - if (!resourceConfig.columns.find((column: any) => column.name === field)) { - const similar = suggestIfTypo(resourceConfig.columns.map((column: any) => column.name), field); - throw new Error(`Field "${field}" specified in fieldsForContext not found in - resource "${resourceConfig.label}". ${similar ? `Did you mean "${similar}"?` : ''}`); - } - }); - } - const pluginFrontendOptions = { allowedExtensions: this.options.allowedFileExtensions, maxFileSize: this.options.maxFileSize, @@ -257,6 +247,26 @@ export default class UploadPlugin extends AdminForthPlugin { validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: any) { this.adminforth = adminforth; + + if (this.options.generation) { + const template = this.options.generation?.generationPrompt; + const regex = /{{(.*?)}}/g; + const matches = template.match(regex); + if (matches) { + matches.forEach((match) => { + const field = match.replace(/{{|}}/g, '').trim(); + if (!resourceConfig.columns.find((column: any) => column.name === field)) { + const similar = suggestIfTypo(resourceConfig.columns.map((column: any) => column.name), field); + throw new Error(`Field "${field}" specified in generationPrompt not found in resource "${resourceConfig.label}". ${similar ? `Did you mean "${similar}"?` : ''}`); + } else { + let column = resourceConfig.columns.find((column: any) => column.name === field); + if (column.backendOnly === true) { + throw new Error(`Field "${field}" specified in generationPrompt is marked as backendOnly in resource "${resourceConfig.label}". Please remove backendOnly or choose another field.`); + } + } + }); + } + } // called here because modifyResourceConfig can be called in build time where there is no environment and AWS secrets this.setupLifecycleRule(); } diff --git a/types.ts b/types.ts index b3efd5c..1e800d9 100644 --- a/types.ts +++ b/types.ts @@ -102,12 +102,6 @@ export type PluginOptions = { */ outputSize?: string, - /** - * Fields for conetext which will be used to generate the image. - * If specified, the plugin will use fields from the record to provide additional context to the AI model. - */ - fieldsForContext?: string[], - /** * The number of images to generate * in one request