diff --git a/AI Bot Starter App.mpr b/AI Bot Starter App.mpr index b8337ae..d88a82f 100644 Binary files a/AI Bot Starter App.mpr and b/AI Bot Starter App.mpr differ diff --git a/javascriptsource/feedbackmodule/actions/GetStorageItemObject.js b/javascriptsource/feedbackmodule/actions/GetStorageItemObject.js index 7f73a5b..5e0744e 100644 --- a/javascriptsource/feedbackmodule/actions/GetStorageItemObject.js +++ b/javascriptsource/feedbackmodule/actions/GetStorageItemObject.js @@ -1,104 +1,104 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import "mx-global"; -import { Big } from "big.js"; -import AsyncStorage from '@react-native-community/async-storage'; - -// BEGIN EXTRA CODE -// END EXTRA CODE - -/** - * What does this JavaScript action do? - * - * Get locally stored JSON object stored in clients internet browser. Identified by a unique key. Can be accessed by the GetStorageItemObject action. Please note that users can clear the device storage. - * @param {string} key - This field is required. - * @param {string} entity - This field is required. - * @returns {Promise.} - */ -export async function GetStorageItemObject(key, entity) { - // BEGIN USER CODE - if (!key) { - return Promise.reject(new Error("Input parameter 'Key' is required")); - } - if (!entity) { - return Promise.reject(new Error("Input parameter 'Entity' is required")); - } - return getItem(key).then(result => { - if (result === null) { - return Promise.reject(new Error(`Storage item '${key}' does not exist`)); - } - const value = JSON.parse(result); - return getOrCreateMxObject(entity, value).then(newObject => { - const newValue = serializeMxObject(newObject); - return setItem(key, JSON.stringify(newValue)).then(() => newObject); - }); - }); - function getItem(key) { - if (navigator && navigator.product === "ReactNative") { - return AsyncStorage.getItem(key); - } - if (window) { - const value = window.localStorage.getItem(key); - return Promise.resolve(value); - } - return Promise.reject(new Error("No storage API available")); - } - function setItem(key, value) { - if (navigator && navigator.product === "ReactNative") { - return AsyncStorage.setItem(key, value); - } - if (window) { - window.localStorage.setItem(key, value); - return Promise.resolve(); - } - return Promise.reject(new Error("No storage API available")); - } - function getOrCreateMxObject(entity, value) { - return getMxObject(value.guid).then(existingObject => { - if (existingObject) { - return existingObject; - } - else { - return createMxObject(entity, value); - } - }); - } - function getMxObject(guid) { - return new Promise((resolve, reject) => { - mx.data.get({ - guid, - callback: mxObject => resolve(mxObject), - error: error => reject(error) - }); - }); - } - function createMxObject(entity, value) { - return new Promise((resolve, reject) => { - mx.data.create({ - entity, - callback: mxObject => { - Object.keys(value) - .filter(attribute => attribute !== "guid") - .forEach(attributeName => { - const attributeValue = value[attributeName]; - mxObject.set(attributeName, attributeValue); - }); - resolve(mxObject); - }, - error: () => reject(new Error(`Could not create '${entity}' object`)) - }); - }); - } - function serializeMxObject(object) { - return object.getAttributes().reduce((accumulator, attributeName) => { - accumulator[attributeName] = object.get(attributeName); - return accumulator; - }, { guid: object.getGuid() }); - } - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import "mx-global"; +import { Big } from "big.js"; +import AsyncStorage from '@react-native-community/async-storage'; + +// BEGIN EXTRA CODE +// END EXTRA CODE + +/** + * What does this JavaScript action do? + * + * Get locally stored JSON object stored in clients internet browser. Identified by a unique key. Can be accessed by the GetStorageItemObject action. Please note that users can clear the device storage. + * @param {string} key - This field is required. + * @param {string} entity - This field is required. + * @returns {Promise.} + */ +export async function GetStorageItemObject(key, entity) { + // BEGIN USER CODE + if (!key) { + return Promise.reject(new Error("Input parameter 'Key' is required")); + } + if (!entity) { + return Promise.reject(new Error("Input parameter 'Entity' is required")); + } + return getItem(key).then(result => { + if (result === null) { + return Promise.reject(new Error(`Storage item '${key}' does not exist`)); + } + const value = JSON.parse(result); + return getOrCreateMxObject(entity, value).then(newObject => { + const newValue = serializeMxObject(newObject); + return setItem(key, JSON.stringify(newValue)).then(() => newObject); + }); + }); + function getItem(key) { + if (navigator && navigator.product === "ReactNative") { + return AsyncStorage.getItem(key); + } + if (window) { + const value = window.localStorage.getItem(key); + return Promise.resolve(value); + } + return Promise.reject(new Error("No storage API available")); + } + function setItem(key, value) { + if (navigator && navigator.product === "ReactNative") { + return AsyncStorage.setItem(key, value); + } + if (window) { + window.localStorage.setItem(key, value); + return Promise.resolve(); + } + return Promise.reject(new Error("No storage API available")); + } + function getOrCreateMxObject(entity, value) { + return getMxObject(value.guid).then(existingObject => { + if (existingObject) { + return existingObject; + } + else { + return createMxObject(entity, value); + } + }); + } + function getMxObject(guid) { + return new Promise((resolve, reject) => { + mx.data.get({ + guid, + callback: mxObject => resolve(mxObject), + error: error => reject(error) + }); + }); + } + function createMxObject(entity, value) { + return new Promise((resolve, reject) => { + mx.data.create({ + entity, + callback: mxObject => { + Object.keys(value) + .filter(attribute => attribute !== "guid") + .forEach(attributeName => { + const attributeValue = value[attributeName]; + mxObject.set(attributeName, attributeValue); + }); + resolve(mxObject); + }, + error: () => reject(new Error(`Could not create '${entity}' object`)) + }); + }); + } + function serializeMxObject(object) { + return object.getAttributes().reduce((accumulator, attributeName) => { + accumulator[attributeName] = object.get(attributeName); + return accumulator; + }, { guid: object.getGuid() }); + } + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/JS_PopulateFeedbackMetadata.js b/javascriptsource/feedbackmodule/actions/JS_PopulateFeedbackMetadata.js index 79ff1f2..ce850ef 100644 --- a/javascriptsource/feedbackmodule/actions/JS_PopulateFeedbackMetadata.js +++ b/javascriptsource/feedbackmodule/actions/JS_PopulateFeedbackMetadata.js @@ -1,72 +1,72 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import "mx-global"; -import { Big } from "big.js"; - -// BEGIN EXTRA CODE - const handleUserRoles = async () => { - if(!mx) return undefined; - try { - return await mx.session.getUserRoleNames(); - } catch (error){ - console.error("Error getting user role names", error); - return undefined; - } - }; - const handlePagePath = async () => { - if(!mx) return undefined; - try { - const mendixVersion = mx.version - const mendixMajorVersion = mendixVersion.split('.')[0]; - switch(mendixMajorVersion) { - case '9': - case '10': - return mx.ui.getContentForm().path.toString(); - default: - return undefined; - }; - } catch { - console.error("Error getting page path", error); - return undefined - } - }; -// END EXTRA CODE - -/** - * What does this JavaScript action do? - * - * Returns meta data from the clients internet browser. - * - * This includes; - * - * ActiveUserRoles - * PageName - * EnvironmentURL - * Browser - * ScreenWidth - * ScreenHeight - * @param {MxObject} feedback - * @returns {Promise.} - */ -export async function JS_PopulateFeedbackMetadata(feedback) { - // BEGIN USER CODE - try { - const userRoles = await handleUserRoles(); - const pagePath = await handlePagePath(); - feedback.set("ActiveUserRoles", userRoles); - feedback.set("PageName", pagePath); - feedback.set("EnvironmentURL", window.location.href); - feedback.set("Browser", navigator.userAgent); - feedback.set("ScreenWidth", window.screen.width); - feedback.set("ScreenHeight", window.screen.height); - return feedback; - } catch (error) { - console.error("Error setting meta data", error); - } - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import "mx-global"; +import { Big } from "big.js"; + +// BEGIN EXTRA CODE + const handleUserRoles = async () => { + if(!mx) return undefined; + try { + return await mx.session.getUserRoleNames(); + } catch (error){ + console.error("Error getting user role names", error); + return undefined; + } + }; + const handlePagePath = async () => { + if(!mx) return undefined; + try { + const mendixVersion = mx.version + const mendixMajorVersion = mendixVersion.split('.')[0]; + switch(mendixMajorVersion) { + case '9': + case '10': + return mx.ui.getContentForm().path.toString(); + default: + return undefined; + }; + } catch { + console.error("Error getting page path", error); + return undefined + } + }; +// END EXTRA CODE + +/** + * What does this JavaScript action do? + * + * Returns meta data from the clients internet browser. + * + * This includes; + * + * ActiveUserRoles + * PageName + * EnvironmentURL + * Browser + * ScreenWidth + * ScreenHeight + * @param {MxObject} feedback + * @returns {Promise.} + */ +export async function JS_PopulateFeedbackMetadata(feedback) { + // BEGIN USER CODE + try { + const userRoles = await handleUserRoles(); + const pagePath = await handlePagePath(); + feedback.set("ActiveUserRoles", userRoles); + feedback.set("PageName", pagePath); + feedback.set("EnvironmentURL", window.location.href); + feedback.set("Browser", navigator.userAgent); + feedback.set("ScreenWidth", window.screen.width); + feedback.set("ScreenHeight", window.screen.height); + return feedback; + } catch (error) { + console.error("Error setting meta data", error); + } + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/JS_RevokeUploadedFileFromMemory.js b/javascriptsource/feedbackmodule/actions/JS_RevokeUploadedFileFromMemory.js index 6791c87..fb21dff 100644 --- a/javascriptsource/feedbackmodule/actions/JS_RevokeUploadedFileFromMemory.js +++ b/javascriptsource/feedbackmodule/actions/JS_RevokeUploadedFileFromMemory.js @@ -1,50 +1,50 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import "mx-global"; -import { Big } from "big.js"; - -// BEGIN EXTRA CODE -// END EXTRA CODE - -/** - * What does this JavaScript action do? - * - * After you have uploaded an image it removes locally stored image from memory. This is a custom build action. - * - * Dependency Note: - * This JavaScript action should be used only when you have inserted the Image Upload JavaScript Action called 'JS_UploadAndConvertToFileBlobURL' into your nanoflow. - * - * More detailed explanation: Memory management. - * - * To upload a image we use a custom build Javascript action called 'JS_UploadAndConvertToFileBlobURL'. - * Inside this action we use a JavaScript method called createObjectURL() to upload and store files in local memory. We can access and cosume this in memory image resource via the URL path that is returned from the createObjectURL() method. - * - * However, each time you call createObjectURL(), a new object is created in memory, even if you've already created one for the same object. - * So each of these must be released by calling this action called 'JS_RevokeUploadedFileFromMemory' when you no longer need them. - * - * Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so with the JavaScriptAction called 'JS_RevokeUploadedFileFromMemory'. - * @param {string} fileBlobURL - You have to pass the fileBlobURL that was created using the URL.createObjectURL() in the JS Action called 'JS_UploadAndConvertToFileBlobURL' - * @returns {Promise.} - */ -export async function JS_RevokeUploadedFileFromMemory(fileBlobURL) { - // BEGIN USER CODE - /* We use the URL.createObjectURL() static method which creates a string containing a URL representing the - image uploaded. - The image blob is stored in the clients browser and takes up memory whilst the session is active. So here we - revoke the image when the user deletes the image. Note that the image is automaticlly revoked when the browser refreshes - or closes. - - You have to pass the fileBlobURL that was created using the URL.createObjectURL() in the JS Action called 'JS_UploadAndConvertToFileBlobURL' - */ - if(fileBlobURL && typeof fileBlobURL === "string"){ - URL.revokeObjectURL(fileBlobURL); - } else { - throw new Error("Image was not removed from browser memory"); - } - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import "mx-global"; +import { Big } from "big.js"; + +// BEGIN EXTRA CODE +// END EXTRA CODE + +/** + * What does this JavaScript action do? + * + * After you have uploaded an image it removes locally stored image from memory. This is a custom build action. + * + * Dependency Note: + * This JavaScript action should be used only when you have inserted the Image Upload JavaScript Action called 'JS_UploadAndConvertToFileBlobURL' into your nanoflow. + * + * More detailed explanation: Memory management. + * + * To upload a image we use a custom build Javascript action called 'JS_UploadAndConvertToFileBlobURL'. + * Inside this action we use a JavaScript method called createObjectURL() to upload and store files in local memory. We can access and cosume this in memory image resource via the URL path that is returned from the createObjectURL() method. + * + * However, each time you call createObjectURL(), a new object is created in memory, even if you've already created one for the same object. + * So each of these must be released by calling this action called 'JS_RevokeUploadedFileFromMemory' when you no longer need them. + * + * Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so with the JavaScriptAction called 'JS_RevokeUploadedFileFromMemory'. + * @param {string} fileBlobURL - You have to pass the fileBlobURL that was created using the URL.createObjectURL() in the JS Action called 'JS_UploadAndConvertToFileBlobURL' + * @returns {Promise.} + */ +export async function JS_RevokeUploadedFileFromMemory(fileBlobURL) { + // BEGIN USER CODE + /* We use the URL.createObjectURL() static method which creates a string containing a URL representing the + image uploaded. + The image blob is stored in the clients browser and takes up memory whilst the session is active. So here we + revoke the image when the user deletes the image. Note that the image is automaticlly revoked when the browser refreshes + or closes. + + You have to pass the fileBlobURL that was created using the URL.createObjectURL() in the JS Action called 'JS_UploadAndConvertToFileBlobURL' + */ + if(fileBlobURL && typeof fileBlobURL === "string"){ + URL.revokeObjectURL(fileBlobURL); + } else { + throw new Error("Image was not removed from browser memory"); + } + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackAnnotateWidget.js b/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackAnnotateWidget.js index 35382f2..5cf9078 100644 --- a/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackAnnotateWidget.js +++ b/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackAnnotateWidget.js @@ -1,74 +1,74 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import "mx-global"; -import { Big } from "big.js"; - -// BEGIN EXTRA CODE -// messageActionTypes are to identify the postMessage types between the JS Action & Feedback Widget. -const messageAction_toggleAnnotateMode = "mxFeedbackWidget_toggleAnnotateMode"; // The Feedback widget reads this to trigger a specific screenshot mode stage. -const messageAction_isBase64 = "mxFeedbackWidget_convertedToBase64"; // We expect this string from the widget when screenshot mode in enabled. -const messageAction_actionCancelled = "mxFeedbackWidget_actionCancelled" // The Feedback widget will send this back if screenshot/annotation actions are cancelled by the user. - -const parseJSON = (event) => { - try { - return JSON.parse(event); - } catch { - return undefined; - } -}; -// END EXTRA CODE - -/** - * What does this JavaScript action do? - * - * When you upload a screenshot manually the image can be annotatated. - * - * More detailed explanation: - * The Mendix Feedback Widget handles annotation and also renders a default styled button on your page. - * - * In order to trigger the annotation mode you have to use this JavaScript action to send the widget the correct image payload. - * - * Return Type: - * Will return base 64 image string - * @param {string} fileBlobURL - * @returns {Promise.} - */ -export async function JS_ToggleFeedbackAnnotateWidget(fileBlobURL) { - // BEGIN USER CODE - /* - The widget and JS action communicate with the following postMessage object structure: - messageObject = {messageActionType: string;messageData: string;} - */ - const messageObject = { - "messageActionType": messageAction_toggleAnnotateMode, // The widget reads this to trigger the Annotate Mode. - "messageData": fileBlobURL // The widget uses this URL reference to get access to the locally stored image blob. - }; - - postMessage(JSON.stringify(messageObject), window.origin); // Send the serialized message object to Feedback Wiget to trigger annotate mode. - - return new Promise(resolve => { - function handleEvent(event) { - const parsedData = parseJSON(event.data); // Convert the received string to an object. - - if(event.origin === window.origin) { - if (parsedData && parsedData.messageActionType === messageAction_isBase64) { - window.removeEventListener("message", handleEvent); - resolve(parsedData.messageData); // Resolve & return the base64 image to the nanoflow. - }; - if(parsedData && parsedData.messageActionType === messageAction_actionCancelled) { - resolve(); - } - } - - }; - - window.addEventListener("message", handleEvent); // Listen and wait for the Feedback Widget to send the base64 image. - - }); - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import "mx-global"; +import { Big } from "big.js"; + +// BEGIN EXTRA CODE +// messageActionTypes are to identify the postMessage types between the JS Action & Feedback Widget. +const messageAction_toggleAnnotateMode = "mxFeedbackWidget_toggleAnnotateMode"; // The Feedback widget reads this to trigger a specific screenshot mode stage. +const messageAction_isBase64 = "mxFeedbackWidget_convertedToBase64"; // We expect this string from the widget when screenshot mode in enabled. +const messageAction_actionCancelled = "mxFeedbackWidget_actionCancelled" // The Feedback widget will send this back if screenshot/annotation actions are cancelled by the user. + +const parseJSON = (event) => { + try { + return JSON.parse(event); + } catch { + return undefined; + } +}; +// END EXTRA CODE + +/** + * What does this JavaScript action do? + * + * When you upload a screenshot manually the image can be annotatated. + * + * More detailed explanation: + * The Mendix Feedback Widget handles annotation and also renders a default styled button on your page. + * + * In order to trigger the annotation mode you have to use this JavaScript action to send the widget the correct image payload. + * + * Return Type: + * Will return base 64 image string + * @param {string} fileBlobURL + * @returns {Promise.} + */ +export async function JS_ToggleFeedbackAnnotateWidget(fileBlobURL) { + // BEGIN USER CODE + /* + The widget and JS action communicate with the following postMessage object structure: + messageObject = {messageActionType: string;messageData: string;} + */ + const messageObject = { + "messageActionType": messageAction_toggleAnnotateMode, // The widget reads this to trigger the Annotate Mode. + "messageData": fileBlobURL // The widget uses this URL reference to get access to the locally stored image blob. + }; + + postMessage(JSON.stringify(messageObject), window.origin); // Send the serialized message object to Feedback Wiget to trigger annotate mode. + + return new Promise(resolve => { + function handleEvent(event) { + const parsedData = parseJSON(event.data); // Convert the received string to an object. + + if(event.origin === window.origin) { + if (parsedData && parsedData.messageActionType === messageAction_isBase64) { + window.removeEventListener("message", handleEvent); + resolve(parsedData.messageData); // Resolve & return the base64 image to the nanoflow. + }; + if(parsedData && parsedData.messageActionType === messageAction_actionCancelled) { + resolve(); + } + } + + }; + + window.addEventListener("message", handleEvent); // Listen and wait for the Feedback Widget to send the base64 image. + + }); + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackScreenshotWidget.js b/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackScreenshotWidget.js index fafc71c..4fea19f 100644 --- a/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackScreenshotWidget.js +++ b/javascriptsource/feedbackmodule/actions/JS_ToggleFeedbackScreenshotWidget.js @@ -1,73 +1,73 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import "mx-global"; -import { Big } from "big.js"; - -// BEGIN EXTRA CODE -// messageActionTypes are to identify the postMessage types between the JS Action & Feedback Widget. -const messageAction_toggleAnnotateMode = "mxFeedbackWidget_toggleScreenshotMode"; // The Feedback widget reads this to trigger a specific screenshot mode stage. -const messageAction_isBase64 = "mxFeedbackWidget_convertedToBase64"; // We expect this string from the widget when screenshot mode in enabled. -const messageAction_actionCancelled = "mxFeedbackWidget_actionCancelled" // The Feedback widget will send this back if screenshot/annotation actions are cancelled by the user. - -/* - The widget and JS action communicate with the following postMessage object structure: - messageObject = {messageActionType: string;messageData: string;} -*/ -const messageObject = { - "messageActionType": messageAction_toggleAnnotateMode //The Feedback widget reads this to trigger a specific Mode. -}; - -const parseJson = (event) => { - try { - return JSON.parse(event); - } catch { - return undefined; - } -}; -// END EXTRA CODE - -/** - * What does this JavaScript action do? - * - * Lets to take a screenshot of the current visible page - * - * More detailed explanation: - * The Mendix Feedback Widget handles annotation, screenshot and also renders a default styled button on your page. - * - * Usage: - * You should use this JavaScript action to trigger the screenshot functionality. - * - * Return Type: - * Will return a image base64 string - * - * @returns {Promise.} - */ -export async function JS_ToggleFeedbackScreenshotWidget() { - // BEGIN USER CODE - postMessage(JSON.stringify(messageObject), window.origin); // Send a message to the Feedback Wiget to trigger screenshot mode. - - return new Promise(resolve => { - - function handleEvent(event){ - const parsedData = parseJson(event.data); - if(parsedData && event.origin === window.origin) { - if (parsedData.messageActionType === messageAction_isBase64) { - window.removeEventListener("message", handleEvent); - resolve(parsedData.messageData); // Resolve & return the base64 image to the nanoflow. - }; - if(parsedData.messageActionType === messageAction_actionCancelled){ - resolve("uploadCancelled"); - } - } - }; - - window.addEventListener("message", handleEvent); // Listen and wait for the Feedback Widget to send back the edited screenshot as base64. - - }); - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import "mx-global"; +import { Big } from "big.js"; + +// BEGIN EXTRA CODE +// messageActionTypes are to identify the postMessage types between the JS Action & Feedback Widget. +const messageAction_toggleAnnotateMode = "mxFeedbackWidget_toggleScreenshotMode"; // The Feedback widget reads this to trigger a specific screenshot mode stage. +const messageAction_isBase64 = "mxFeedbackWidget_convertedToBase64"; // We expect this string from the widget when screenshot mode in enabled. +const messageAction_actionCancelled = "mxFeedbackWidget_actionCancelled" // The Feedback widget will send this back if screenshot/annotation actions are cancelled by the user. + +/* + The widget and JS action communicate with the following postMessage object structure: + messageObject = {messageActionType: string;messageData: string;} +*/ +const messageObject = { + "messageActionType": messageAction_toggleAnnotateMode //The Feedback widget reads this to trigger a specific Mode. +}; + +const parseJson = (event) => { + try { + return JSON.parse(event); + } catch { + return undefined; + } +}; +// END EXTRA CODE + +/** + * What does this JavaScript action do? + * + * Lets to take a screenshot of the current visible page + * + * More detailed explanation: + * The Mendix Feedback Widget handles annotation, screenshot and also renders a default styled button on your page. + * + * Usage: + * You should use this JavaScript action to trigger the screenshot functionality. + * + * Return Type: + * Will return a image base64 string + * + * @returns {Promise.} + */ +export async function JS_ToggleFeedbackScreenshotWidget() { + // BEGIN USER CODE + postMessage(JSON.stringify(messageObject), window.origin); // Send a message to the Feedback Wiget to trigger screenshot mode. + + return new Promise(resolve => { + + function handleEvent(event){ + const parsedData = parseJson(event.data); + if(parsedData && event.origin === window.origin) { + if (parsedData.messageActionType === messageAction_isBase64) { + window.removeEventListener("message", handleEvent); + resolve(parsedData.messageData); // Resolve & return the base64 image to the nanoflow. + }; + if(parsedData.messageActionType === messageAction_actionCancelled){ + resolve("uploadCancelled"); + } + } + }; + + window.addEventListener("message", handleEvent); // Listen and wait for the Feedback Widget to send back the edited screenshot as base64. + + }); + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/JS_UploadAndConvertToFileBlobURL.js b/javascriptsource/feedbackmodule/actions/JS_UploadAndConvertToFileBlobURL.js index 2e50fe3..9ed080b 100644 --- a/javascriptsource/feedbackmodule/actions/JS_UploadAndConvertToFileBlobURL.js +++ b/javascriptsource/feedbackmodule/actions/JS_UploadAndConvertToFileBlobURL.js @@ -1,113 +1,113 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import { Big } from "big.js"; -import "mx-global"; - -// BEGIN EXTRA CODE - var isUploading = false; - -    async function storeFileAndGetResourceUrl(file) { -        return URL.createObjectURL(file); // Saves the file to locally memory and returns a URL path to the Blob object. -    }; - -    function removeDomElements({fileInput, progressId}) { -        if(progressId) mx.ui.hideProgress(progressId); -        if(fileInput) document.body.removeChild(fileInput); - isUploading = false;     -    } - -    function validateFileTypes ({acceptedTypes, fileType}) { -        if(!acceptedTypes && !fileType) return false; -        const accepted = acceptedTypes.split(","); -        return accepted.some(type => new RegExp(type).test(fileType)); -    } - -    function validateFileSize ({uploadedFile, maxSize}) { -        if(!uploadedFile && !maxSize) return false; -        const uploadedSize = uploadedFile.size / 1024 / 1024; // Convert to MB -        return uploadedSize < (maxSize.c[0] + 0.1); // 0.1 MB extra tolerance -    } -// END EXTRA CODE - -/** - * What does this JavaScript Action do? - * - * This is a custom build JavaScript Action that triggers the file upload dialog box to open in your internet browser. - * - * Dependency Note: - * This JavaScript action should be used with the JavaScript Action called 'JS_RevokeUploadedFileFromMemory' so that the image uploaded is removed from local memory :) - * - * Explanation of this JavaScript Action & Memory management. - * - * We use createObjectURL() to upload and store files in local memory. We can access and cosume this in memory image resource via the URL path that is returned from the createObjectURL() method. - * - * However, each time you call createObjectURL(), a new object is created in memory, even if you've already created one for the same object. - * So each of these must be released by calling the JS Action called 'JS_RevokeUploadedFileFromMemory' when you no longer need them. - * - * Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so with the JavaScriptAction called 'JS_RevokeUploadedFileFromMemory'. - * @param {string} userDefined_mimeTypes - * @param {Big} userDefined_fileUploadSize - * @returns {Promise.} - */ -export async function JS_UploadAndConvertToFileBlobURL(userDefined_mimeTypes, userDefined_fileUploadSize) { - // BEGIN USER CODE -    return new Promise((resolve, reject) => { -        try { -            // Create and append the HTML input element to the body -            const fileInput = document.createElement("input"); -            fileInput.style.position = "absolute"; -            fileInput.style.left = "-9999px"; -            fileInput.name = "fileupload"; -            fileInput.id = "fileupload"; -            fileInput.type = "file"; -            if(userDefined_mimeTypes){ -                fileInput.accept = userDefined_mimeTypes; -            } -            fileInput.multiple = false; -            fileInput.onchange = handleFileUpload; -            document.body.appendChild(fileInput); - fileInput.addEventListener("cancel", () => resolve("uploadCancelled")); -            fileInput.click(); - -            // A function used to validate & store the uploaded file to local memory. -            function handleFileUpload(event) { - isUploading = true; - -                const newFile = event.target.files[0]; -                const progressId = mx.ui.showProgress(null, true); - -                // Check if the uploaded file type matches the user defined accepted types. -                if (!validateFileTypes({acceptedTypes: fileInput.accept, fileType: newFile.type})) { -                    removeDomElements({fileInput, progressId}); -                    resolve("fileTypeNotAccepted"); -                    return; -                } -                // Check if the uploaded file matches the user defined upload size. -                if (!validateFileSize({uploadedFile: newFile, maxSize: userDefined_fileUploadSize})) { -                    removeDomElements({fileInput, progressId}); -                    resolve("fileSizeNotAccepted"); -                    return; -                } -                // Store file locally on users device and return path to resource. -                storeFileAndGetResourceUrl(newFile).then((fileBlobURL) => { -                    if(fileBlobURL && typeof fileBlobURL === "string") { -                        removeDomElements({fileInput, progressId}); -                        resolve(fileBlobURL); -                    } else { -                        removeDomElements({fileInput, progressId}); -                        resolve("fileNotConverted"); -                    } -                }) -                return; -            }; -        } catch (error) { -            reject(error); -        } -    }); - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import { Big } from "big.js"; +import "mx-global"; + +// BEGIN EXTRA CODE + var isUploading = false; + +    async function storeFileAndGetResourceUrl(file) { +        return URL.createObjectURL(file); // Saves the file to locally memory and returns a URL path to the Blob object. +    }; + +    function removeDomElements({fileInput, progressId}) { +        if(progressId) mx.ui.hideProgress(progressId); +        if(fileInput) document.body.removeChild(fileInput); + isUploading = false;     +    } + +    function validateFileTypes ({acceptedTypes, fileType}) { +        if(!acceptedTypes && !fileType) return false; +        const accepted = acceptedTypes.split(","); +        return accepted.some(type => new RegExp(type).test(fileType)); +    } + +    function validateFileSize ({uploadedFile, maxSize}) { +        if(!uploadedFile && !maxSize) return false; +        const uploadedSize = uploadedFile.size / 1024 / 1024; // Convert to MB +        return uploadedSize < (maxSize.c[0] + 0.1); // 0.1 MB extra tolerance +    } +// END EXTRA CODE + +/** + * What does this JavaScript Action do? + * + * This is a custom build JavaScript Action that triggers the file upload dialog box to open in your internet browser. + * + * Dependency Note: + * This JavaScript action should be used with the JavaScript Action called 'JS_RevokeUploadedFileFromMemory' so that the image uploaded is removed from local memory :) + * + * Explanation of this JavaScript Action & Memory management. + * + * We use createObjectURL() to upload and store files in local memory. We can access and cosume this in memory image resource via the URL path that is returned from the createObjectURL() method. + * + * However, each time you call createObjectURL(), a new object is created in memory, even if you've already created one for the same object. + * So each of these must be released by calling the JS Action called 'JS_RevokeUploadedFileFromMemory' when you no longer need them. + * + * Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so with the JavaScriptAction called 'JS_RevokeUploadedFileFromMemory'. + * @param {string} userDefined_mimeTypes + * @param {Big} userDefined_fileUploadSize + * @returns {Promise.} + */ +export async function JS_UploadAndConvertToFileBlobURL(userDefined_mimeTypes, userDefined_fileUploadSize) { + // BEGIN USER CODE +    return new Promise((resolve, reject) => { +        try { +            // Create and append the HTML input element to the body +            const fileInput = document.createElement("input"); +            fileInput.style.position = "absolute"; +            fileInput.style.left = "-9999px"; +            fileInput.name = "fileupload"; +            fileInput.id = "fileupload"; +            fileInput.type = "file"; +            if(userDefined_mimeTypes){ +                fileInput.accept = userDefined_mimeTypes; +            } +            fileInput.multiple = false; +            fileInput.onchange = handleFileUpload; +            document.body.appendChild(fileInput); + fileInput.addEventListener("cancel", () => resolve("uploadCancelled")); +            fileInput.click(); + +            // A function used to validate & store the uploaded file to local memory. +            function handleFileUpload(event) { + isUploading = true; + +                const newFile = event.target.files[0]; +                const progressId = mx.ui.showProgress(null, true); + +                // Check if the uploaded file type matches the user defined accepted types. +                if (!validateFileTypes({acceptedTypes: fileInput.accept, fileType: newFile.type})) { +                    removeDomElements({fileInput, progressId}); +                    resolve("fileTypeNotAccepted"); +                    return; +                } +                // Check if the uploaded file matches the user defined upload size. +                if (!validateFileSize({uploadedFile: newFile, maxSize: userDefined_fileUploadSize})) { +                    removeDomElements({fileInput, progressId}); +                    resolve("fileSizeNotAccepted"); +                    return; +                } +                // Store file locally on users device and return path to resource. +                storeFileAndGetResourceUrl(newFile).then((fileBlobURL) => { +                    if(fileBlobURL && typeof fileBlobURL === "string") { +                        removeDomElements({fileInput, progressId}); +                        resolve(fileBlobURL); +                    } else { +                        removeDomElements({fileInput, progressId}); +                        resolve("fileNotConverted"); +                    } +                }) +                return; +            }; +        } catch (error) { +            reject(error); +        } +    }); + // END USER CODE +} diff --git a/javascriptsource/feedbackmodule/actions/SetStorageItemObject.js b/javascriptsource/feedbackmodule/actions/SetStorageItemObject.js index f4ecff9..c6c4b7c 100644 --- a/javascriptsource/feedbackmodule/actions/SetStorageItemObject.js +++ b/javascriptsource/feedbackmodule/actions/SetStorageItemObject.js @@ -1,47 +1,47 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -import { Big } from "big.js"; -import AsyncStorage from '@react-native-community/async-storage'; - -// BEGIN EXTRA CODE -// END EXTRA CODE - -/** - * Store a Mendix object in device storage, identified by a unique key. Can be accesed by the GetStargeItemObject action. Please note that users can clear the device storage. - * @param {string} key - This field is required. - * @param {MxObject} value - This field is required. - * @returns {Promise.} - */ -export async function SetStorageItemObject(key, value) { - // BEGIN USER CODE - if (!key) { - return Promise.reject(new Error("Input parameter 'Key' is required")); - } - if (!value) { - return Promise.reject(new Error("Input parameter 'Value' is required")); - } - const serializedObject = serializeMxObject(value); - return setItem(key, JSON.stringify(serializedObject)); - function setItem(key, value) { - if (navigator && navigator.product === "ReactNative") { - return AsyncStorage.setItem(key, value); - } - if (window) { - window.localStorage.setItem(key, value); - return Promise.resolve(); - } - return Promise.reject(new Error("No storage API available")); - } - function serializeMxObject(object) { - return object.getAttributes().reduce((accumulator, attributeName) => { - accumulator[attributeName] = object.get(attributeName); - return accumulator; - }, { guid: object.getGuid() }); - } - // END USER CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +import { Big } from "big.js"; +import AsyncStorage from '@react-native-community/async-storage'; + +// BEGIN EXTRA CODE +// END EXTRA CODE + +/** + * Store a Mendix object in device storage, identified by a unique key. Can be accesed by the GetStargeItemObject action. Please note that users can clear the device storage. + * @param {string} key - This field is required. + * @param {MxObject} value - This field is required. + * @returns {Promise.} + */ +export async function SetStorageItemObject(key, value) { + // BEGIN USER CODE + if (!key) { + return Promise.reject(new Error("Input parameter 'Key' is required")); + } + if (!value) { + return Promise.reject(new Error("Input parameter 'Value' is required")); + } + const serializedObject = serializeMxObject(value); + return setItem(key, JSON.stringify(serializedObject)); + function setItem(key, value) { + if (navigator && navigator.product === "ReactNative") { + return AsyncStorage.setItem(key, value); + } + if (window) { + window.localStorage.setItem(key, value); + return Promise.resolve(); + } + return Promise.reject(new Error("No storage API available")); + } + function serializeMxObject(object) { + return object.getAttributes().reduce((accumulator, attributeName) => { + accumulator[attributeName] = object.get(attributeName); + return accumulator; + }, { guid: object.getGuid() }); + } + // END USER CODE +} diff --git a/javasource/amazonbedrockconnector/actions/InvokeModel.java b/javasource/amazonbedrockconnector/actions/InvokeModel.java index 2b07b57..b561102 100644 --- a/javasource/amazonbedrockconnector/actions/InvokeModel.java +++ b/javasource/amazonbedrockconnector/actions/InvokeModel.java @@ -10,13 +10,17 @@ package amazonbedrockconnector.actions; import static java.util.Objects.requireNonNull; +import java.util.Optional; import com.mendix.systemwideinterfaces.core.IContext; import com.mendix.systemwideinterfaces.core.IMendixObject; import com.mendix.webui.CustomJavaAction; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import amazonbedrockconnector.impl.AmazonBedrockClient; import amazonbedrockconnector.impl.MxLogger; import amazonbedrockconnector.proxies.InvokeModelResponse; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; +import software.amazon.awssdk.http.SdkHttpResponse; public class InvokeModel extends CustomJavaAction { @@ -63,6 +67,7 @@ public IMendixObject executeAction() throws Exception LOGGER.info("AWS request: " + awsRequest); software.amazon.awssdk.services.bedrockruntime.model.InvokeModelResponse awsResponse = client.invokeModel(awsRequest); + LOGGER.info("AWS response: " + awsResponse); InvokeModelResponse mxResponse = getMxResponse(awsResponse); @@ -88,6 +93,7 @@ public java.lang.String toString() // BEGIN EXTRA CODE private static final MxLogger LOGGER = new MxLogger(InvokeModel.class); + private static final ObjectMapper objectMapper = new ObjectMapper(); private void validateRequest() { if (InvokeModelRequest.getModelID() == null|| InvokeModelRequest.getModelID().isBlank()) { @@ -106,10 +112,34 @@ private software.amazon.awssdk.services.bedrockruntime.model.InvokeModelRequest .build(); } - private InvokeModelResponse getMxResponse(software.amazon.awssdk.services.bedrockruntime.model.InvokeModelResponse awsResponse) { + private InvokeModelResponse getMxResponse(software.amazon.awssdk.services.bedrockruntime.model.InvokeModelResponse awsResponse) throws Exception { var mxResponse = new InvokeModelResponse(getContext()); - - mxResponse.setResponseBody(awsResponse.body().asUtf8String()); + + SdkHttpResponse httpResponse = awsResponse.sdkHttpResponse(); + Optional inputTokenCountHeader = httpResponse.firstMatchingHeader("X-Amzn-Bedrock-Input-Token-Count"); + + // Retrieve the existing JSON body + String responseBody = awsResponse.body().asUtf8String(); + + try { + // Parse the existing JSON body + ObjectNode jsonNode = (ObjectNode) objectMapper.readTree(responseBody); + + // If the header is present, add it to the JSON as a key-value pair + inputTokenCountHeader.ifPresent(headerValue -> { + jsonNode.put("request_tokens", headerValue); + }); + + // Convert the updated JSON back to a string + String updatedResponseBody = jsonNode.toString(); + + // Set the updated JSON string as the response body + mxResponse.setResponseBody(updatedResponseBody); + + } catch (Exception e) { + LOGGER.error("Error processing JSON response: " + e.getMessage()); + throw e; + } return mxResponse; } diff --git a/javasource/amazonbedrockconnector/actions/JA_CohereEmbed_ModifyJson_Response.java b/javasource/amazonbedrockconnector/actions/JA_CohereEmbed_ModifyJson_Response.java index 9340a9c..993438d 100644 --- a/javasource/amazonbedrockconnector/actions/JA_CohereEmbed_ModifyJson_Response.java +++ b/javasource/amazonbedrockconnector/actions/JA_CohereEmbed_ModifyJson_Response.java @@ -57,6 +57,7 @@ public java.lang.String executeAction() throws Exception // Create output JSON object outputNode = mapper.createObjectNode(); outputNode.set("id", inputNode.get("id")); + outputNode.set("request_tokens", inputNode.get("request_tokens")); outputNode.set("response_type", inputNode.get("response_type")); // Create the embeddings array for the output diff --git a/javasource/amazonbedrockconnector/genaicommons_impl/ReferenceImpl.java b/javasource/amazonbedrockconnector/genaicommons_impl/ReferenceImpl.java index 11048e9..9500157 100644 --- a/javasource/amazonbedrockconnector/genaicommons_impl/ReferenceImpl.java +++ b/javasource/amazonbedrockconnector/genaicommons_impl/ReferenceImpl.java @@ -1,95 +1,95 @@ -package amazonbedrockconnector.genaicommons_impl; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.mendix.systemwideinterfaces.core.IContext; - -import amazonbedrockconnector.impl.MxLocation; -import amazonbedrockconnector.proxies.ENUM_DataSourceType; -import genaicommons.proxies.ENUM_SourceType; -import genaicommons.proxies.Reference; -import software.amazon.awssdk.services.bedrockagentruntime.model.Citation; -import software.amazon.awssdk.services.bedrockagentruntime.model.RetrievalResultLocation; - -public class ReferenceImpl { - - // Create References and Citations from AWS Citations (Retrieve And Generate, Agents) - public static List getMxReferences(List awsCitations, IContext ctx) { - - List mxReferences = new ArrayList<>(); - - awsCitations.forEach(awsCitation -> { - genaicommons.proxies.Citation mxCitation = new genaicommons.proxies.Citation(ctx); - setMxCitation(mxCitation, awsCitation); - - List singleCitationList = Arrays.asList(mxCitation); - if (awsCitation.hasRetrievedReferences()) { - - awsCitation.retrievedReferences().forEach(awsReference -> { - Reference mxReference = new Reference(ctx); - - ENUM_DataSourceType mxDataSourceType = MxLocation.getMxDataSourceType(awsReference.location().type()); - String sourceUrl = getSourceUrl(mxDataSourceType, awsReference.location()); - ENUM_SourceType sourceType = getSourceType(mxDataSourceType); - - setMxReference(mxReference, awsReference.content().text(), sourceUrl, sourceType, awsReference.location()); - mxReference.setReference_Citation(singleCitationList); - mxReferences.add(mxReference); - }); - } - }); - - return mxReferences; - } - - public static String getSourceUrl(ENUM_DataSourceType type, RetrievalResultLocation awsLocation) { - switch (type) { - case S3: { - return awsLocation.s3Location().uri(); - } - // TODO: This needs to be extended with other location types when updating the SDK - default: - return null; - } - } - - public static ENUM_SourceType getSourceType(ENUM_DataSourceType type) { - switch (type) { - case S3: { - return ENUM_SourceType.Url; - } - // TODO: This needs to be extended with other location types when updating the SDK - default: - return null; // SourceType attribute will be empty for unknown types. In the future, a "Unknown / Unsupported" Enum value might be introduced in GenAI Commons - } - } - - public static void setMxReference(Reference mxReference, String content, String sourceUrl, ENUM_SourceType sourceType, RetrievalResultLocation awsLocation) { - mxReference.setContent(content); - mxReference.setSource(sourceUrl); - mxReference.setSourceType(sourceType); - mxReference.setTitle(getReferenceTitle(sourceUrl, awsLocation)); - } - - private static void setMxCitation(genaicommons.proxies.Citation mxCitation, Citation awsCitation) { - mxCitation.setStartIndex(awsCitation.generatedResponsePart().textResponsePart().span().start()); - mxCitation.setEndIndex(awsCitation.generatedResponsePart().textResponsePart().span().end()); - mxCitation.setText(awsCitation.generatedResponsePart().textResponsePart().text()); - } - - private static String getReferenceTitle(String url, RetrievalResultLocation awsLocation) { - if (url == null || url.isBlank()) { - return awsLocation.typeAsString(); - } - - if (url.contains("/")) { - int last = url.lastIndexOf("/"); - return url.substring(last + 1); - - } else return url; - - } - -} +package amazonbedrockconnector.genaicommons_impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.mendix.systemwideinterfaces.core.IContext; + +import amazonbedrockconnector.impl.MxLocation; +import amazonbedrockconnector.proxies.ENUM_DataSourceType; +import genaicommons.proxies.ENUM_SourceType; +import genaicommons.proxies.Reference; +import software.amazon.awssdk.services.bedrockagentruntime.model.Citation; +import software.amazon.awssdk.services.bedrockagentruntime.model.RetrievalResultLocation; + +public class ReferenceImpl { + + // Create References and Citations from AWS Citations (Retrieve And Generate, Agents) + public static List getMxReferences(List awsCitations, IContext ctx) { + + List mxReferences = new ArrayList<>(); + + awsCitations.forEach(awsCitation -> { + genaicommons.proxies.Citation mxCitation = new genaicommons.proxies.Citation(ctx); + setMxCitation(mxCitation, awsCitation); + + List singleCitationList = Arrays.asList(mxCitation); + if (awsCitation.hasRetrievedReferences()) { + + awsCitation.retrievedReferences().forEach(awsReference -> { + Reference mxReference = new Reference(ctx); + + ENUM_DataSourceType mxDataSourceType = MxLocation.getMxDataSourceType(awsReference.location().type()); + String sourceUrl = getSourceUrl(mxDataSourceType, awsReference.location()); + ENUM_SourceType sourceType = getSourceType(mxDataSourceType); + + setMxReference(mxReference, awsReference.content().text(), sourceUrl, sourceType, awsReference.location()); + mxReference.setReference_Citation(singleCitationList); + mxReferences.add(mxReference); + }); + } + }); + + return mxReferences; + } + + public static String getSourceUrl(ENUM_DataSourceType type, RetrievalResultLocation awsLocation) { + switch (type) { + case S3: { + return awsLocation.s3Location().uri(); + } + // TODO: This needs to be extended with other location types when updating the SDK + default: + return null; + } + } + + public static ENUM_SourceType getSourceType(ENUM_DataSourceType type) { + switch (type) { + case S3: { + return ENUM_SourceType.Url; + } + // TODO: This needs to be extended with other location types when updating the SDK + default: + return null; // SourceType attribute will be empty for unknown types. In the future, a "Unknown / Unsupported" Enum value might be introduced in GenAI Commons + } + } + + public static void setMxReference(Reference mxReference, String content, String sourceUrl, ENUM_SourceType sourceType, RetrievalResultLocation awsLocation) { + mxReference.setContent(content); + mxReference.setSource(sourceUrl); + mxReference.setSourceType(sourceType); + mxReference.setTitle(getReferenceTitle(sourceUrl, awsLocation)); + } + + private static void setMxCitation(genaicommons.proxies.Citation mxCitation, Citation awsCitation) { + mxCitation.setStartIndex(awsCitation.generatedResponsePart().textResponsePart().span().start()); + mxCitation.setEndIndex(awsCitation.generatedResponsePart().textResponsePart().span().end()); + mxCitation.setText(awsCitation.generatedResponsePart().textResponsePart().text()); + } + + private static String getReferenceTitle(String url, RetrievalResultLocation awsLocation) { + if (url == null || url.isBlank()) { + return awsLocation.typeAsString(); + } + + if (url.contains("/")) { + int last = url.lastIndexOf("/"); + return url.substring(last + 1); + + } else return url; + + } + +} diff --git a/javasource/feedbackmodule/actions/ValidateEmail.java b/javasource/feedbackmodule/actions/ValidateEmail.java index 0f78588..210032f 100644 --- a/javasource/feedbackmodule/actions/ValidateEmail.java +++ b/javasource/feedbackmodule/actions/ValidateEmail.java @@ -1,54 +1,54 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -// Special characters, e.g., é, ö, à, etc. are supported in comments. - -package feedbackmodule.actions; - -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.webui.CustomJavaAction; - -public class ValidateEmail extends CustomJavaAction -{ - private final java.lang.String EmailAddress; - - public ValidateEmail( - IContext context, - java.lang.String _emailAddress - ) - { - super(context); - this.EmailAddress = _emailAddress; - } - - @java.lang.Override - public java.lang.Boolean executeAction() throws Exception - { - // BEGIN USER CODE - - // The regex used in this code is the same used in App Insights. If you want to apply more restricted rule, you can change it here. - String emailPattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"; - java.util.regex.Pattern p = java.util.regex.Pattern.compile(emailPattern); - java.util.regex.Matcher m = p.matcher(EmailAddress); - return m.matches(); - - // END USER CODE - } - - /** - * Returns a string representation of this action - * @return a string representation of this action - */ - @java.lang.Override - public java.lang.String toString() - { - return "ValidateEmail"; - } - - // BEGIN EXTRA CODE - // END EXTRA CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package feedbackmodule.actions; + +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; + +public class ValidateEmail extends CustomJavaAction +{ + private final java.lang.String EmailAddress; + + public ValidateEmail( + IContext context, + java.lang.String _emailAddress + ) + { + super(context); + this.EmailAddress = _emailAddress; + } + + @java.lang.Override + public java.lang.Boolean executeAction() throws Exception + { + // BEGIN USER CODE + + // The regex used in this code is the same used in App Insights. If you want to apply more restricted rule, you can change it here. + String emailPattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"; + java.util.regex.Pattern p = java.util.regex.Pattern.compile(emailPattern); + java.util.regex.Matcher m = p.matcher(EmailAddress); + return m.matches(); + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "ValidateEmail"; + } + + // BEGIN EXTRA CODE + // END EXTRA CODE +} diff --git a/javasource/feedbackmodule/actions/XSS_Sanitizer.java b/javasource/feedbackmodule/actions/XSS_Sanitizer.java index d5d5ffb..67dd0bb 100644 --- a/javasource/feedbackmodule/actions/XSS_Sanitizer.java +++ b/javasource/feedbackmodule/actions/XSS_Sanitizer.java @@ -1,90 +1,90 @@ -// This file was generated by Mendix Studio Pro. -// -// WARNING: Only the following code will be retained when actions are regenerated: -// - the import list -// - the code between BEGIN USER CODE and END USER CODE -// - the code between BEGIN EXTRA CODE and END EXTRA CODE -// Other code you write will be lost the next time you deploy the project. -// Special characters, e.g., é, ö, à, etc. are supported in comments. - -package feedbackmodule.actions; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.mendix.systemwideinterfaces.core.IContext; -import com.mendix.webui.CustomJavaAction; - -public class XSS_Sanitizer extends CustomJavaAction -{ - private final java.lang.String stringToSanitize; - - public XSS_Sanitizer( - IContext context, - java.lang.String _stringToSanitize - ) - { - super(context); - this.stringToSanitize = _stringToSanitize; - } - - @java.lang.Override - public java.lang.String executeAction() throws Exception - { - // BEGIN USER CODE - - //It's a simple XSS sanitation with regex replace. - String sanitizedString = sanitize(stringToSanitize); - return sanitizedString; - - // END USER CODE - } - - /** - * Returns a string representation of this action - * @return a string representation of this action - */ - @java.lang.Override - public java.lang.String toString() - { - return "XSS_Sanitizer"; - } - - // BEGIN EXTRA CODE - public static String sanitize(String input) { - if (input == null || input.isEmpty()) { - return input; - } - - // Remove all occurrences of ", ""); - - // Remove all event handler attributes (e.g., onclick, onmouseover) - input = input.replaceAll("(?i)\\bon\\w+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)", ""); - - // Remove all javascript: URLs - input = input.replaceAll("(?i)javascript:", ""); - - // Remove all ", ""); - - // Remove all tags - input = input.replaceAll("(?i)]*>.*?", ""); - - // Remove all tags - input = input.replaceAll("(?i)]*>.*?", ""); - - // Remove all tags - input = input.replaceAll("(?i)]*>.*?", ""); - - // Remove all tags that could potentially cause redirection - input = input.replaceAll("(?i)]*http-equiv[^>]*>", ""); - - // Remove all other potentially dangerous HTML tags - Pattern pattern = Pattern.compile("<[^>]*(>|$)"); - Matcher matcher = pattern.matcher(input); - input = matcher.replaceAll(""); - - return input; - } - // END EXTRA CODE -} +// This file was generated by Mendix Studio Pro. +// +// WARNING: Only the following code will be retained when actions are regenerated: +// - the import list +// - the code between BEGIN USER CODE and END USER CODE +// - the code between BEGIN EXTRA CODE and END EXTRA CODE +// Other code you write will be lost the next time you deploy the project. +// Special characters, e.g., é, ö, à, etc. are supported in comments. + +package feedbackmodule.actions; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.mendix.systemwideinterfaces.core.IContext; +import com.mendix.webui.CustomJavaAction; + +public class XSS_Sanitizer extends CustomJavaAction +{ + private final java.lang.String stringToSanitize; + + public XSS_Sanitizer( + IContext context, + java.lang.String _stringToSanitize + ) + { + super(context); + this.stringToSanitize = _stringToSanitize; + } + + @java.lang.Override + public java.lang.String executeAction() throws Exception + { + // BEGIN USER CODE + + //It's a simple XSS sanitation with regex replace. + String sanitizedString = sanitize(stringToSanitize); + return sanitizedString; + + // END USER CODE + } + + /** + * Returns a string representation of this action + * @return a string representation of this action + */ + @java.lang.Override + public java.lang.String toString() + { + return "XSS_Sanitizer"; + } + + // BEGIN EXTRA CODE + public static String sanitize(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + // Remove all occurrences of ", ""); + + // Remove all event handler attributes (e.g., onclick, onmouseover) + input = input.replaceAll("(?i)\\bon\\w+\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)", ""); + + // Remove all javascript: URLs + input = input.replaceAll("(?i)javascript:", ""); + + // Remove all ", ""); + + // Remove all tags + input = input.replaceAll("(?i)]*>.*?", ""); + + // Remove all tags + input = input.replaceAll("(?i)]*>.*?", ""); + + // Remove all tags + input = input.replaceAll("(?i)]*>.*?", ""); + + // Remove all tags that could potentially cause redirection + input = input.replaceAll("(?i)]*http-equiv[^>]*>", ""); + + // Remove all other potentially dangerous HTML tags + Pattern pattern = Pattern.compile("<[^>]*(>|$)"); + Matcher matcher = pattern.matcher(input); + input = matcher.replaceAll(""); + + return input; + } + // END EXTRA CODE +} diff --git a/widgets/com.mendix.widget.web.Combobox.mpk b/widgets/com.mendix.widget.web.Combobox.mpk index bd5d539..75159bc 100644 Binary files a/widgets/com.mendix.widget.web.Combobox.mpk and b/widgets/com.mendix.widget.web.Combobox.mpk differ