{"id":37901,"date":"2024-04-22T17:30:43","date_gmt":"2024-04-22T12:00:43","guid":{"rendered":"https:\/\/www.inogic.com\/blog\/?p=37901"},"modified":"2024-12-10T12:25:59","modified_gmt":"2024-12-10T06:55:59","slug":"building-a-custom-ai-text-editor-using-microsofts-azure-openai","status":"publish","type":"post","link":"https:\/\/www.inogic.com\/blog\/2024\/04\/building-a-custom-ai-text-editor-using-microsofts-azure-openai\/","title":{"rendered":"Building a Custom AI Text Editor using Microsoft\u2019s Azure OpenAI"},"content":{"rendered":"<p style=\"text-align: justify;\">In today&#8217;s world, the demand for intelligent applications that can analyze and transform text is increasing rapidly. AI-powered text editors are becoming popular for generating content as well as data restructuring tools for many developers and businesses.<\/p>\n<p style=\"text-align: justify;\">For any business, it is important to have data stored in the CRM system consistently and according to business standards. Users need to be trained to follow these standards or formats when entering data into the CRM system. However, mistakes are often made, leading to inconsistencies in the data.<\/p>\n<p style=\"text-align: justify;\"><strong>Artificial Intelligence (AI)<\/strong> can be a solution to this problem. We aim to develop an <strong>AI Text Editor<\/strong> capable of analyzing user-input text and offering suggestions for restructuring it based on predefined formats.<\/p>\n<p style=\"text-align: justify;\">We&#8217;ll use the capabilities of <a href=\"https:\/\/www.inogic.com\/services\/ai-solutions\/microsoft-dynamics365-powerplatform-chatgpt-azure-openai-consulting-services\/\" target=\"_blank\" rel=\"noopener\">Azure OpenAI service<\/a>, a platform for natural language processing tasks, that will allow our text to be analyzed and transformed into the desired format. In this blog, we&#8217;ll guide you on how to build a custom AI text editor using PCF (Power Apps Component Framework) and Azure OpenAI service.<\/p>\n<p style=\"text-align: justify;\"><strong>Create Azure OpenAI service and get Azure OpenAI API Key and Endpoint <\/strong><\/p>\n<ol>\n<li style=\"text-align: justify;\">Currently, at the time of writing this blog, access to Azure OpenAI service in Azure is limited. You need to apply <a href=\"https:\/\/customervoice.microsoft.com\/Pages\/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xUNTZBNzRKNlVQSFhZMU9aV09EVzYxWFdORCQlQCN0PWcu\">here<\/a> for access.<\/li>\n<li style=\"text-align: justify;\">Once you get access from Microsoft, login to the Azure Portal at <a href=\"https:\/\/portal.azure.com\">https:\/\/portal.azure.com<\/a> and create Azure OpenAI service.\n<div style=\"text-align: center;\"><img decoding=\"async\" class=\"alignnone wp-image-37903 size-full\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-1.png\" alt=\"AI Text Editor using Azure OpenAI \" width=\"224\" height=\"351\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-1.png 224w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-1-191x300.png 191w\" sizes=\"(max-width: 224px) 100vw, 224px\" \/><\/div>\n<\/li>\n<li style=\"text-align: justify;\">Configure the service according to your needs. This may include selecting a pricing tier, specifying a location for the service, and setting up resource groups and tags. You can refer to <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/ai-services\/openai\/how-to\/create-resource?pivots=web-portal\" target=\"_blank\" rel=\"noopener\">this<\/a> article to create an Azure OpenAI service.<\/li>\n<li style=\"text-align: left;\">Once the Azure OpenAI Service resource is created, navigate to it in the Azure portal and You should find your API key and endpoint listed in the Key and Endpoint section under Resource Management as shown below. These are the credentials you&#8217;ll use to authenticate and access the OpenAI API. <img decoding=\"async\" class=\"alignnone size-full wp-image-37904\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-2.png\" alt=\"AI Text Editor using Azure OpenAI\" width=\"990\" height=\"603\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-2.png 990w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-2-300x183.png 300w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-2-768x468.png 768w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-2-660x402.png 660w\" sizes=\"(max-width: 990px) 100vw, 990px\" \/><\/li>\n<li>To call Azure OpenAI API and generate text, you need to deploy an appropriate model that suits the requirement.<\/li>\n<li>To create and manage deployments, you can use Azure AI Studio (preview). <a href=\"https:\/\/oai.azure.com\" target=\"_blank\" rel=\"noopener\">https:\/\/oai.azure.com<\/a>. Create a new deployment and save the \u201cDeployment name\u201d to be used for calling Azure OpenAI. Here, we have deployed \u201cgpt-35-turbo\u201d model with version \u201c0613\u201d.<br \/>\n<img decoding=\"async\" class=\"alignnone size-full wp-image-37905\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-3.png\" alt=\"AI Text Editor using Azure OpenAI \" width=\"557\" height=\"390\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-3.png 557w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-3-300x210.png 300w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-3-200x140.png 200w\" sizes=\"(max-width: 557px) 100vw, 557px\" \/><\/li>\n<li>By following these steps, we have obtained three values: <strong>API Key, Endpoint, and Deployment ID<\/strong>\/<strong>Name<\/strong>, which are required for calling the Azure OpenAI service. In the next section, we will see how to use these values to use generative AI in the PCF control.<\/li>\n<\/ol>\n<h4><strong>Create field level Power Apps Control Framework control:<\/strong><\/h4>\n<p>Here, we will create a field-level PCF control that can be bound to a multiline or single line of text fields in the Power Apps. You can follow the steps outlined in the following article to create a field PCF control, <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-apps\/developer\/component-framework\/tutorial-create-model-driven-field-component?tabs=before\" target=\"_blank\" rel=\"noopener\">Create a field level model driven apps control<\/a>.<\/p>\n<ul>\n<li><strong>Install @azure\/openai module:<\/strong> Once the PCF control is created, use the \u201c<strong>npm install @azure\/openai\u201d<\/strong> command to install the Azure OpenAI libraries in the project.<\/li>\n<li>Create a property named &#8220;<strong>Prompt<\/strong>&#8221; in ControlManifest.Input.xml for the user to insert the format they want to convert the text into while configuring the control through Power Apps.<\/li>\n<\/ul>\n<div style=\"text-align: center;\">\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-37902\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-4.png\" alt=\"AI Text Editor using Azure OpenAI \" width=\"600\" height=\"273\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-4.png 600w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-4-300x137.png 300w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/p>\n<\/div>\n<ul>\n<li>Create a \u201cApp.tsx\u201d file to add Fluent UI components and call it in the index.tsx file.<\/li>\n<\/ul>\n<div style=\"text-align: center;\">\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-37909\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-5.png\" alt=\"AI Text Editor using Azure OpenAI \" width=\"533\" height=\"307\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-5.png 533w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-5-300x173.png 300w\" sizes=\"(max-width: 533px) 100vw, 533px\" \/><\/p>\n<\/div>\n<ul>\n<li>In the App.tsx file, create a UI which consists following elements,<\/li>\n<\/ul>\n<p style=\"padding-left: 40px;\"><strong>Multiline Text Field:<\/strong> Editable text field where user can enter their data.<\/p>\n<p style=\"padding-left: 40px;\"><strong>Submit button:<\/strong> A button to execute or call Azure OpenAI API to generate a text based on the instruction provided.<\/p>\n<p style=\"padding-left: 40px;\"><strong>Information Label:<\/strong> When utilizing AI-generated text, it is crucial to review the content carefully before use. This is a message to the user to review AI generated content before use.<\/p>\n<pre class=\"lang:css gutter:true start:1\"> \r\n&lt;div\r\nstyle={{\r\nwidth: \"100%\",\r\ndisplay: \"flex\",\r\nflexDirection: \"column\",\r\nposition: \"relative\",\r\n}}\r\n&gt;\r\n&lt;div\r\nstyle={{\r\ndisplay: \"flex\",\r\nflexDirection: \"row\",\r\npadding: \"10px 0px 10px 0px\",\r\n}}\r\n&gt;\r\n&lt;div style={{ flexGrow: 1, marginRight: \"10px\" }}&gt;\r\n&lt;TextField\r\ntype=\"\"\r\nmultiline\r\nrows={10}\r\nautoAdjustHeight\r\nvalue={inputValue}\r\ndisabled={isButtonDisabled}\r\nonChange={handleChange}\r\n\/&gt;\r\n&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n&lt;div&gt;\r\n&lt;PrimaryButton\r\ntext=\"Submit\"\r\nonClick={handleSubmit}\r\ndisabled={isButtonDisabled}\r\n\/&gt;\r\n&lt;\/div&gt;\r\n&lt;div\r\nstyle={{\r\nposition: \"absolute\",\r\ntop: \"50%\",\r\nleft: \"50%\",\r\ntransform: \"translate(-50%, -50%)\",\r\n}}\r\n&gt;\r\n{isLoading &amp;&amp; (\r\n&lt;Spinner\r\nlabel=\"Generating...\"\r\nstyles={{\r\nlabel: {\r\nfontWeight: \"bold\",\r\ncolor: \"blue\",\r\nfontFamily: \"Segoe UI\",\r\n},\r\n}}\r\n\/&gt;\r\n)}\r\n&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n<\/pre>\n<p><strong>Using of Azure OpenAI in PCF:<\/strong><\/p>\n<ul>\n<li>In the <strong>onSubmitClick<\/strong> function, ensure to input the endpoint and azureApiKey obtained from the <strong>Azure AI Studio<\/strong> as explained above. It is recommended to use a secure method to access or use Keys and endpoints in the code.<\/li>\n<li>Azure OpenAI needs a system instruction\/message and a user prompt to generate content. It generates the content based on the instruction provided in the natural language.<\/li>\n<li>In this demo, we are providing the static system message alongside the user prompt. Obtain the result using Azure OpenAI&#8217;s <strong>getChatCompletions(deploymentId, messages, options)<\/strong> Once the response is received, the output message will be sent back to the control and bound to the text field.<\/li>\n<\/ul>\n<pre class=\"lang:css gutter:true start:1\"> \r\n\/\/ Function to handle form submission\r\nconst onSubmitClick = async () =&gt; {\r\nconst endpoint = \"\"; \/\/ OpenAI endpoint\r\nconst azureApiKey = \"\"; \/\/ Azure API key\r\n\r\n\/\/ Chat messages to be sent to OpenAI\r\nconst messages: ChatRequestMessage[] = [\r\n{ role: \"system\", content:\"You are an AI Text Editor where you primary role is to analyze text and help users transform it into a desired structure. \" },\r\n{ role: \"user\", content: prompt +\" : \"+ inputValue },\r\n];\r\n\r\ntry {\r\n\/\/ Confirmation dialog before proceeding\r\nconst confirmationResult = await context.navigation.openConfirmDialog(\r\n{\r\ntext: \"Proceeding will result in the restructuring of data. Click 'Ok' to proceed with the process. Please ensure that you have backed up any important data before proceeding.\",\r\ntitle: \"Caution: Data Restructuring Ahead\",\r\n},\r\n{ height: 200, width: 450 }\r\n);\r\n\r\nif (!confirmationResult.confirmed) return;\r\n\r\n\/\/ Disable button and show loading spinner\r\nsetIsButtonDisabled(true);\r\nsetIsLoading(true);\r\n\r\n\/\/ Check for empty input or prompt\r\nif (!inputValue.trim() || inputValue == \"\" || prompt == \"\") {\r\nreturn;\r\n}\r\n\r\n\/\/ Initialize OpenAI client\r\nconst client = new OpenAIClient(\r\nendpoint,\r\nnew AzureKeyCredential(azureApiKey)\r\n);\r\nconst deploymentId = \"\"; \/\/ Deployment ID\r\nconst options: GetChatCompletionsOptions = {\r\ntemperature:0,\r\ntopP: 0,\r\nfrequencyPenalty: 0.0,\r\npresencePenalty: 0.0,\r\nmaxTokens: 800,\r\nstop: [],\r\n};\r\n\r\n\/\/ Get completion from OpenAI\r\nconst result = await client.getChatCompletions(\r\ndeploymentId,\r\nmessages,\r\noptions\r\n);\r\n\r\n\/\/ Extract result value\r\nconst resultValue = result.choices[0].message?.content;\r\n\r\n\/\/ Update input value and notify parent component\r\nsetInputValue(resultValue || \"\");\r\nnotifyChange?.(resultValue || \"\");\r\n\r\n\/\/ Enable button and hide loading spinner\r\nsetIsButtonDisabled(false);\r\nsetIsLoading(false);\r\n} catch (error:any) {\r\n\/\/ Show error dialog and log error\r\ncontext.navigation.openConfirmDialog(\r\n{\r\ntext: error.message,\r\ntitle: error.code,\r\n},\r\n{ height: 200, width: 450 }\r\n);\r\nconsole.error(\"An error occurred:\", error.message);\r\nsetIsButtonDisabled(false);\r\nsetIsLoading(false);\r\n}\r\n};\r\n<\/pre>\n<ul>\n<li>Build the solution and import the solution.zip file in Dataverse. While configuration of control in the description field we are providing the prompt to convert the paragraph into a number list as shown below:<\/li>\n<\/ul>\n<div style=\"text-align: center;\"><img decoding=\"async\" class=\"alignnone size-full wp-image-37906\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-6.png\" alt=\"AI Text Editor using Azure OpenAI\" width=\"285\" height=\"586\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-6.png 285w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-6-146x300.png 146w\" sizes=\"(max-width: 285px) 100vw, 285px\" \/><\/div>\n<ul>\n<li>In the description field of CRM, we are providing a random paragraph to demonstrate the conversion to number list.<\/li>\n<\/ul>\n<div style=\"text-align: center;\"><img decoding=\"async\" class=\"alignnone size-full wp-image-37907\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-7.png\" alt=\"AI Text Editor using Azure OpenAI\" width=\"429\" height=\"519\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-7.png 429w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-7-248x300.png 248w\" sizes=\"(max-width: 429px) 100vw, 429px\" \/><\/div>\n<ul>\n<li>Once the input has been submitted, the output from OpenAI is displayed in the description field as shown below.<\/li>\n<\/ul>\n<div style=\"text-align: center;\"><img decoding=\"async\" class=\"alignnone size-full wp-image-37908\" style=\"border: 1px solid #000000; padding: 1px; margin: 1px;\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-8.png\" alt=\"AI Text Editor using Azure OpenAI\" width=\"411\" height=\"537\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-8.png 411w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2024\/04\/AI-Text-Editor-using-Azure-OpenAI-8-230x300.png 230w\" sizes=\"(max-width: 411px) 100vw, 411px\" \/><\/div>\n<p><strong>index.ts<\/strong><\/p>\n<pre class=\"lang:css gutter:true start:1\">import { IInputs, IOutputs } from \".\/generated\/ManifestTypes\";\r\nimport * as React from \"react\";\r\nimport { ITextProps, AITextControl } from \".\/components\/App\";\r\n\r\nexport class AIDescriptionEditorControl\r\nimplements ComponentFramework.ReactControl&lt;IInputs, IOutputs&gt;\r\n{\r\nprivate theComponent: ComponentFramework.ReactControl&lt;IInputs, IOutputs&gt;;\r\nprivate _navigation: ComponentFramework.Navigation;\r\n\/**\r\n* Empty constructor.\r\n*\/\r\nconstructor() {}\r\nprivate notifyOutputChanged: () =&gt; void;\r\nprivate _container: HTMLDivElement;\r\nprivate _value: any;\r\nprivate _context: ComponentFramework.Context;\r\n\/**\r\n* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.\r\n* Data-set values are not initialized here, use updateView.\r\n* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.\r\n* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.\r\n* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.\r\n*\/\r\npublic init(\r\ncontext: ComponentFramework.Context,\r\nnotifyOutputChanged: () =&gt; void,\r\nstate: ComponentFramework.Dictionary,\r\ncontainer: HTMLDivElement\r\n): void {\r\nthis.notifyOutputChanged = notifyOutputChanged;\r\n\/\/ this._context = context;\r\n\/\/ this._container = document.createElement(\"div\");\r\n\/\/ this._value = context.parameters.sampleProperty.raw || \"\";\r\n\/\/ container.appendChild(this._container);\r\n}\r\npublic async notifyOnSelect(value: string) {\r\nthis._value = value != null || value != undefined ? value : null;\r\nthis.notifyOutputChanged();\r\n}\r\n\/**\r\n* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.\r\n* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions\r\n* @returns ReactElement root react element for the control\r\n*\/\r\npublic updateView(\r\ncontext: ComponentFramework.Context\r\n): React.ReactElement {\r\n\/\/ Prepare props for the AITextControl component\r\nlet descriptionProps: ITextProps = {};\r\ndescriptionProps = {\r\nvalue: context.parameters.AIDescriptionEditorControl.raw ?? \"\",\r\nprompt: context.parameters.Prompt.raw ?? \"\",\r\ncontext,\r\nnotifyChange: this.notifyOnSelect.bind(this),\r\n};\r\n\/\/Render the AITextControl component\r\nreturn (\r\nReact.createElement(AITextControl, descriptionProps)\r\n);\r\n}\r\n\r\n\/**\r\n* It is called by the framework prior to a control receiving new data.\r\n* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as \"bound\" or \"output\"\r\n*\/\r\npublic getOutputs(): IOutputs {\r\nreturn {\r\nAIDescriptionEditorControl: this._value != null ? this._value : null,\r\n};\r\n}\r\n\r\n\/**\r\n* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.\r\n* i.e. cancelling any pending remote calls, removing listeners, etc.\r\n*\/\r\npublic destroy(): void {\r\n\/\/ Add code to cleanup control if necessary\r\n}\r\n}\r\n<\/pre>\n<p><strong>App.tsx<\/strong><\/p>\n<pre class=\"lang:css gutter:true start:1\"> \r\nimport * as React from \"react\";\r\nimport { TextField, PrimaryButton, Spinner, Label } from \"@fluentui\/react\";\r\nimport { ChatRequestMessage, OpenAIClient, AzureKeyCredential, GetChatCompletionsOptions } from \"@azure\/openai\";\r\n\r\n\/\/ Props interface for the AI Text Control component\r\nexport interface ITextProps {\r\ncontext?: any; \/\/ Context for the component\r\nprompt ?: any; \/\/ prompt for the text\r\nvalue?: string; \/\/ Initial value for the text field\r\nnotifyChange?: (value: string) =&gt; void; \/\/ Callback function to notify parent component of changes\r\n}\r\n\r\n\/\/ AI Text Control component\r\nexport const AITextControl: React.FC&lt;ITextProps&gt; = ({\r\ncontext,\r\nprompt ,\r\nvalue,\r\nnotifyChange,\r\n}) =&gt; {\r\n\/\/ State variables\r\nconst [inputValue, setInputValue] = React.useState&lt;string&gt;(value || \"\"); \/\/ State for input value\r\nconst [isButtonDisabled, setIsButtonDisabled] = React.useState&lt;boolean&gt;(false); \/\/ State for button disabled state\r\nconst [isLoading, setIsLoading] = React.useState&lt;boolean&gt;(false); \/\/ State for loading spinner\r\n\r\n\/\/ Function to handle input change\r\nconst onFieldTextChange = (\r\nevent: React.FormEvent&lt;HTMLInputElement | HTMLTextAreaElement&gt;,\r\nnewValue?: string\r\n) =&gt; {\r\nif (newValue !== undefined) {\r\nsetInputValue(newValue);\r\nnotifyChange?.(newValue);\r\n}\r\n};\r\n\r\n\/\/ Function to handle form submission\r\nconst onSubmitClick = async () =&gt; {\r\nconst endpoint = \"&lt;YOUR_ENDPOINT_KEY_GOES_HERE&gt;\"; \/\/ OpenAI endpoint\r\nconst azureApiKey = \"&lt;YOUR_AZUREAPIKEY_GOES_HERE&gt;\"; \/\/ Azure API key\r\n\r\n\/\/ Chat messages to be sent to OpenAI\r\nconst messages: ChatRequestMessage[] = [\r\n{ role: \"system\", content:\"You are an AI Text Editor where you primary role is to analyze text and help users transform it into a desired structure. \" },\r\n{ role: \"user\", content: prompt +\" : \"+ inputValue },\r\n];\r\n\r\ntry {\r\n\/\/ Confirmation dialog before proceeding\r\nconst confirmationResult = await context.navigation.openConfirmDialog(\r\n{\r\ntext: \"Proceeding will result in the restructuring of data. Click 'Ok' to proceed with the process. Please ensure that you have backed up any important data before proceeding.\",\r\ntitle: \"Caution: Data Restructuring Ahead\",\r\n},\r\n{ height: 200, width: 450 }\r\n);\r\n\r\nif (!confirmationResult.confirmed) return;\r\n\r\n\/\/ Disable button and show loading spinner\r\nsetIsButtonDisabled(true);\r\nsetIsLoading(true);\r\n\r\n\/\/ Check for empty input or prompt\r\nif (!inputValue.trim() || inputValue == \"\" || prompt == \"\") {\r\nreturn;\r\n}\r\n\r\n\/\/ Initialize OpenAI client\r\nconst client = new OpenAIClient(\r\nendpoint,\r\nnew AzureKeyCredential(azureApiKey)\r\n);\r\nconst deploymentId = \"&lt;YOUR_DEPLOYMENTID_GOES_HERE&gt;\"; \/\/ Deployment ID\r\nconst options: GetChatCompletionsOptions = {\r\ntemperature:0,\r\ntopP: 0,\r\nfrequencyPenalty: 0.0,\r\npresencePenalty: 0.0,\r\nmaxTokens: 800,\r\nstop: [],\r\n};\r\n\r\n\/\/ Get completion from OpenAI\r\nconst result = await client.getChatCompletions(\r\ndeploymentId,\r\nmessages,\r\noptions\r\n);\r\n\r\n\/\/ Extract result value\r\nconst resultValue = result.choices[0].message?.content;\r\n\r\n\/\/ Update input value and notify parent component\r\nsetInputValue(resultValue || \"\");\r\nnotifyChange?.(resultValue || \"\");\r\n\r\n\/\/ Enable button and hide loading spinner\r\nsetIsButtonDisabled(false);\r\nsetIsLoading(false);\r\n} catch (error:any) {\r\n\/\/ Show error dialog and log error\r\ncontext.navigation.openConfirmDialog(\r\n{\r\ntext: error.message,\r\ntitle: error.code,\r\n},\r\n{ height: 200, width: 450 }\r\n);\r\nconsole.error(\"An error occurred:\", error.message);\r\nsetIsButtonDisabled(false);\r\nsetIsLoading(false);\r\n}\r\n};\r\n\/\/ Render component\r\nreturn (\r\n&lt;div\r\nstyle={{\r\nwidth: \"100%\",\r\ndisplay: \"flex\",\r\nflexDirection: \"column\",\r\nposition: \"relative\",\r\n}}\r\n&gt;\r\n&lt;div\r\nstyle={{\r\ndisplay: \"flex\",\r\nflexDirection: \"row\",\r\npadding: \"10px 0px 0px 0px\",\r\n}}\r\n&gt;\r\n&lt;div style={{ flexGrow: 1, marginRight: \"10px\" }}&gt;\r\n&lt;TextField\r\ntype=\"\"\r\nmultiline\r\nrows={10}\r\nautoAdjustHeight\r\nvalue={inputValue}\r\ndisabled={isButtonDisabled}\r\nonChange={onFieldTextChange}\r\n\/&gt;\r\n&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n&lt;Label styles={{ root: { fontSize: 11, fontFamily: \"Calibri\" } }}&gt;\r\n*The text generated by AI may not always be accurate or reliable. Verify\r\nthe inpromption before saving.\r\n&lt;\/Label&gt;\r\n&lt;div&gt;\r\n&lt;PrimaryButton\r\ntext=\"Submit\"\r\nonClick={onSubmitClick}\r\ndisabled={isButtonDisabled}\r\n\/&gt;\r\n&lt;\/div&gt;\r\n&lt;div\r\nstyle={{\r\nposition: \"absolute\",\r\ntop: \"50%\",\r\nleft: \"50%\",\r\ntransform: \"translate(-50%, -50%)\",\r\n}}\r\n&gt;\r\n{\/* Loading spinner *\/}\r\n{isLoading &amp;&amp; (\r\n&lt;Spinner\r\nlabel=\"Generating...\"\r\nstyles={{\r\nlabel: {\r\nfontWeight: \"bold\",\r\ncolor: \"blue\",\r\nfontFamily: \"Segoe UI\",\r\n},\r\n}}\r\n\/&gt;\r\n)}\r\n&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n);\r\n};\r\n \r\n<\/pre>\n<p><strong>Preview<\/strong><\/p>\n<p><iframe title=\"Building a Custom AI Text Editor using Microsoft\u2019s Azure OpenAI\" width=\"665\" height=\"374\" src=\"https:\/\/www.youtube.com\/embed\/FNCz6PJajkM?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2><strong>Conclusion<\/strong><\/h2>\n<p>In this blog post, we&#8217;ve explored how to build an AI Text Editor using Azure OpenAI. With this powerful tool, users can analyze and transform text with ease, making it a useful control across all dataverse tables for content generation, data restructuring, and more.<\/p>\n<p><a href=\"https:\/\/www.inogic.com\/services\/ai-solutions\/microsoft-dynamics365-powerplatform-chatgpt-copilot-consulting-services\/\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" class=\"aligncenter wp-image-36733 size-full\" src=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2023\/12\/copilot.png\" alt=\"copilot\" width=\"700\" height=\"200\" srcset=\"https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2023\/12\/copilot.png 700w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2023\/12\/copilot-300x86.png 300w, https:\/\/www.inogic.com\/blog\/wp-content\/uploads\/2023\/12\/copilot-660x189.png 660w\" sizes=\"(max-width: 700px) 100vw, 700px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In today&#8217;s world, the demand for intelligent applications that can analyze and transform text is increasing rapidly. AI-powered text editors are becoming popular for generating content as well as data restructuring tools for many developers and businesses. For any business, it is important to have data stored in the CRM system consistently and according to\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.inogic.com\/blog\/2024\/04\/building-a-custom-ai-text-editor-using-microsofts-azure-openai\/\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":11,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[2843],"tags":[2871],"class_list":["post-37901","post","type-post","status-publish","format-standard","hentry","category-azure-openai","tag-azure-openai"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/posts\/37901","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/comments?post=37901"}],"version-history":[{"count":0,"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/posts\/37901\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/media?parent=37901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/categories?post=37901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inogic.com\/blog\/wp-json\/wp\/v2\/tags?post=37901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}