Gemini AI Integration in Froala Editor: AI-powered Chat and Generate Feedback Features
- Posted on
- By Mostafa Yousef
- In Editor, General
Table of contents
- What is Gemini?
- How to integrate Gemini With Froala?
- Integrate Froala with Express Framework
- Create a new Node.js app
- Install Dependencies
- Set up the Express Framework
- Init Froala Editor
- Run your node.js App
- Integrate Gemini With Froala
- Install Dependencies
- Secure Your API Keys
- Initialize the generative model
- Define Route to Generate Text From Text-only Prompt Using Gemini API
- Build Froala Custom Plugin for Gemini
- Basic Structure
- Custom Froala popup
- Customize Froala Editor to Use Gemini Plugin
- Get The Application Code
- Unleash the Power of AI in Your Froala Editor
Would you like to give your users the power of using AI inside their WYSIWYG editor to help them compose content, rewrite some blocks, or get feedback on their writing? That would be easy if you have a powerful WYSIWYG editor that makes it easy to extend its functionality such as Froala. Since it was made by developers for developers, Froala has been built with a modular architecture based on plugins making it easy to add new functions to the editor. Choosing Froala helps you to provide your users with new features developed for your specific use case. Moreover, you will not miss new technologies waiting for the editor team to build it for you, you can easily build it for yourself.
Generative AI tools, such as Gemini, are becoming an essential part of any product, used to create content, looking for success. “Powered by AI” is a successful marketing statement that helps in promoting your product to many categories.
Integrating Froala with ChatGPT or Gemini is a magic recipe for incorporating success for your product and it’s a piece of cake. In this article, you will learn how to build a custom Froala plugin for integrating Gemini into your Froala editor. We will add a popup to chat with Gemini and a button to allow users to receive SEO feedback on various aspects of their writing.
What is Gemini?
Gemini is an AI-powered chat service developed by Google to enhance creativity and productivity. It’s designed to assist with writing, planning, learning, and more, leveraging Google’s AI technology. Initially introduced as Bard, it functions similarly to a conversational chatbot and uses information from the web to provide fresh, high-quality responses.
How to integrate Gemini With Froala?
To integrate Gemini into 3rd party tools, you need first to get a Gemini API key. This API key should be kept secret for security purposes. That’s why we strongly recommend that you call the Google AI Gemini API only server-side. If you embed your API key directly in your web app or fetch it remotely at runtime, you risk potentially exposing your API key to malicious actors. That’s why we will perform this tutorial in the Node.js environment using Express Framework.
Integrate Froala with Express Framework
We already created a tutorial about using the Froala editor in the Node.JS server using Express Framework. We will make a quick recap here but for more details, you can go back to that tutorial.
Create a new Node.js app
npm init
Set the entry point to “app.js”
Install Dependencies
Install the Express framework, Embedded JavaScript templates (EJS), and Froala WYSIWYG editor
npm install froala-editor ejs express
Set up the Express Framework
Create a new file named “app.js” in the root directory of our project. Open “app.js” and add the following code:
var express = require('express'); var app = express(); // Set EJS as the view engine app.set('view engine','ejs'); //Froala editor CSS & JS files app.use('/froalacss',express.static(__dirname+'/node_modules/froala-editor/css/froala_editor.pkgd.min.css')); app.use('/froalajs',express.static(__dirname+'/node_modules/froala-editor/js/froala_editor.pkgd.min.js')); // Define routes app.get('/',(req,res)=>{ res.render('editor'); }); var port = process.env.PORT || 3000; app.listen(port,()=>console.log('server run at port '+port));
Init Froala Editor
Create a new directory called “views”. Inside it, create a new file called “editor.ejs” with the following code
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="/froalacss"> <script src="/froalajs"></script> <title>Document</title> </head> <body> <h1>Froala Editor</h1> <textarea id="example"></textarea> <script> var editor = new FroalaEditor("#example"); </script> </body> </html>
Run your node.js App
node app.js
Open http://localhost:3000\
in your browser, your app should be running and you should be able to see and play with the Froala Editor.
If you feel you missed something or need more explanation refer to the “using the Froala editor in the Node.JS server using Express Framework” tutorial.
Integrate Gemini With Froala
The idea is we will create a custom Froala plugin that will introduce custom Froala buttons. Once the new custom button is clicked it will send a request to the node.js server with a custom AI prompt, the server should handle the request by making a Gemini API call and return a response with the returned output from the Gemini API call. The Froala custom plugin will display the server response, which is the text generated by Gemini API, after making any needed modifications.
Install Dependencies
First, we will need to install the following packages:
- Install the
GoogleGenerativeAI
package for Node.js - Install
dotenv
package so we can access the Gemini API key from the .env file. This package will make the.env
variables accessible throughprocess.env
. - Install the
marked
package. We will use it to convert Gemini API response from mark-down syntax to HTML code.
run
npm install @google/generative-ai dotenv marked
Secure Your API Keys
To keep your Gemini API key safe, create a new .env file at the root directory and add your API key to it
GEMINI_API_KEY=***
Replace *** with your API key value.
Initialize the generative model
Open app.js and add
require('dotenv').config(); const marked = require('marked'); const { GoogleGenerativeAI } = require("@google/generative-ai"); // Access your API key as an environment variable (see "Set up your API key" above) const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); // For text-only input, use the gemini-pro model const model = genAI.getGenerativeModel({ model: "gemini-pro"});
In the above code, we initialized the generative model for the Gemini API using the GoogleGenerativeAI package and accessed the API key securely from the .env file. We are using the “gemini-pro” model which allows text-only input. You can specify another model based on your use case.
Define Route to Generate Text From Text-only Prompt Using Gemini API
Define a POST route to handle user prompts using Gemini API and return the AI-generated text.
// Middleware to parse JSON bodies app.use(express.json()); // Define the /gemini POST route app.post('/gemini', async (req, res) => { // Extract the prompt variable from the request body const { prompt } = req.body; try { const result = await model.generateContent(prompt); const response = await result.response; const responseText = marked.parse(response.text()); // Send the response back to the client res.json({ response: responseText }); } catch (error) { // Handle any errors that occur during the API call res.status(500).json({ error: error.message }); } });
In the above code, the route extracts the prompt from the request body, generates content using the Gemini model, and sends the generated text back to the client after converting it from markdown syntax to HTML. Any errors that occur during the API call are handled appropriately.
Build Froala Custom Plugin for Gemini
Basic Structure
Froala custom plugin is a custom JavaScript function that is added to FroalaEditor.PLUGINS
object. Usually, it is defined inside a self-executed function that takes FroalaEditor
object as a parameter. The starting point for your plugin should be a public method with the name _init()
. The plugin should include a public method for sending requests to the server to generate text using AI. This method is an asynchronous function since we should wait for the response from Gemini API.
(function (FroalaEditor) { // Define the plugin. // The editor parameter is the current instance. FroalaEditor.PLUGINS.Gemini = function (editor) { async function generateText(prompt) { const response = await fetch('/gemini', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt }), }); return await response.json(); } // The start point for your plugin. function _init () {} return { _init: _init, generateText } } })(FroalaEditor);
Custom Froala popup
Now we need a custom popup that will contain an input for users to enter their prompts and display the AI response.
Also, it will contain a button to ask AI to give SEO feedback about the content in the editor.
The custom popup requires the following:
- Define the popup structure/template.
- This will contain a div for displaying the chat and an input to enter the user prompt
-
// Load popup template. var template = { buttons: popup_buttons, custom_layer: `<div id="chat-container"></div> <div id="chat-form"> <input id="chat-form-input" type="text" placeholder="Ask AI" /> <button id="chat-form-button" type="button">Send!</button> </div> ` };
initPopup
method for creating the popup-
// Create custom popup. function initPopup () { // Load popup template. var template = FroalaEditor.POPUP_TEMPLATES.Geminipopup; if (typeof template == 'function') template = template.apply(editor); // Popup buttons. var popup_buttons = ''; // Create the list of buttons. if (editor.opts.geminiPopupButtons.length > 1) { popup_buttons += '<div class="fr-buttons">'; popup_buttons += editor.button.buildList(editor.opts.geminiPopupButtons); popup_buttons += '</div>'; } // Load popup template. var template = { buttons: popup_buttons, custom_layer: `<div id="chat-container"></div> <div id="chat-form"> <input id="chat-form-input" type="text" placeholder="Ask AI" /> <button id="chat-form-button" type="button">Send!</button> </div> ` }; // Create popup. var $popup = editor.popups.create('Gemini.popup', template); return $popup; }
showPopup
method for displaying the popup-
// Show the popup function showPopup () { // Get the popup object defined above. var $popup = editor.popups.get('Gemini.popup'); // If popup doesn't exist then create it. // To improve performance it is best to create the popup when it is first needed // and not when the editor is initialized. if (!$popup) $popup = initPopup(); // Set the editor toolbar as the popup's container. editor.popups.setContainer('Gemini.popup', editor.$tb); // This custom popup is opened by pressing a button from the editor's toolbar. // Get the button's object in order to place the popup relative to it. var $btn = editor.$tb.find('.fr-command[data-cmd="AI"]'); // Compute the popup's position. var left = $btn.offset().left + $btn.outerWidth() / 2; var top = $btn.offset().top + (editor.opts.toolbarBottom ? 10 : $btn.outerHeight() - 10); // Show the custom popup. // The button's outerHeight is required in case the popup needs to be displayed above it. editor.popups.show('Gemini.popup', left, top, $btn.outerHeight()); }
- Inside
showPopup
method, we need to register a click event handler for the “Send prompt” button -
const chatButton = document.getElementById("chat-form-button"); chatButton.addEventListener('click', _chatButtonHandler);
The private method,
_chatButtonHandler
, handles the button click event by displaying the user prompt in the chat area and sending it to the Gemini API to show the response. A loading message will be displayed while waiting for the server response to keep the user informed. Implementing a validation process for user input is essential for app security, but it will be left for you to implement. -
function _chatButtonHandler () { const prompt = document.getElementById("chat-form-input").value; editor.Gemini.displayPrompet(prompt); editor.Gemini.loadingMessage(); editor.Gemini.generateText(prompt).then(result => { editor.Gemini.displayAiResponse(result); }).catch(error => { // Handle any errors here console.error('Error:', error); }); }
- Add a public method for displaying the loading message
-
// Display a loading message function loadingMessage(){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.createElement('p'); loadingMessage.id = 'chat-loading-response'; loadingMessage.textContent = 'Generating response... Please wait.'; chatContainer.append(loadingMessage); }
Add a public method for displaying user prompts in the chat area
-
// Display prompt function displayPrompet(prompt){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.createElement('p'); loadingMessage.className = 'chat-prompt'; loadingMessage.textContent = prompt; chatContainer.append(loadingMessage); }
Add a public method for displaying the response from Gemini AI in the chat area
-
function displayAiResponse(result){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.getElementById('chat-loading-response'); // Remove the loading message chatContainer.removeChild(loadingMessage); // Create a new paragraph element for the response const p = document.createElement('p'); p.className = 'chat-ai-response'; // Set the text content to the resolved data p.innerHTML = result.response; // Prepend the new paragraph to the chat container chatContainer.append(p); }
- Inside
hidePopup
method to hide the popup-
// Hide the custom popup. function hidePopup () { document.getElementById("chat-form-button").removeEventListener('click',_chatButtonHandler); editor.popups.hide('Gemini.popup'); }
- A custom Froala button for opening the popup
// Define an icon and command for the button that opens the custom popup. FroalaEditor.DefineIcon('AI', {NAME: 'AI', template: 'text'}); FroalaEditor.RegisterCommand('AI', { title: 'Display Gemini AI Popup', icon: 'AI', undo: false, focus: false, popup: true, // Buttons which are included in the editor toolbar should have the plugin property set. plugin: 'Gemini', callback: function () { if (!this.popups.isVisible('Gemini.popup')) { this.Gemini.showPopup(); } else { if (this.$el.find('.fr-marker')) { this.events.disableBlur(); this.selection.restore(); } this.popups.hide('Gemini.popup'); } } });
- Custom buttons will appear on the popup
- Define a custom button for closing the popup
-
FroalaEditor.DefineIcon('hideAI', { NAME: 'close', SVG_KEY: 'close'}); FroalaEditor.RegisterCommand('hideAI', { title: 'Close', icon: 'hideAI', undo: false, focus: false, callback: function () { this.Gemini.hidePopup(); } });
-
- Define a custom button for clearing the chat area
-
FroalaEditor.DefineIcon('clearChat', { NAME: 'clear', SVG_KEY: 'remove'}) FroalaEditor.RegisterCommand('clearChat', { title: 'Start A New Chat', icon: 'clearChat', undo: false, focus: false, callback: function () { this.Gemini.clearChat(); } });
- the
clearChat
public method -
function clearChat(){ const chatContainer = document.getElementById("chat-container"); // Prepend the new paragraph to the chat container chatContainer.textContent=""; }
- the
-
- Define a custom button for using Gemini to generate feedback about editor content
-
// Define custom popup 1. FroalaEditor.DefineIcon('getFeedback', { NAME: 'star', SVG_KEY: 'star'}) FroalaEditor.RegisterCommand('getFeedback', { title: 'Get Feedback on Your Writing', icon: 'getFeedback', undo: false, focus: false, callback: function () { const editorTextContent = this.$el[0].textContent; const proPrompt = "Hello AI, could you please provide SEO feedback on my writing? I am looking for insights on keyword optimization, readability, and meta description effectiveness. Here is the text:"+editorTextContent+". Thank you!" this.Gemini.displayPrompet("Generate feedback about my writing (editor content)"); this.Gemini.loadingMessage(); this.Gemini.generateText(proPrompt).then(result => { this.Gemini.displayAiResponse(result); }).catch(error => { // Handle any errors here console.error('Error:', error); }); } });
In the above code, we defined a custom button for requesting AI feedback on editor content. It retrieves the editor’s text content and creates a prompt for the AI to generate feedback on SEO aspects. The AI is asked to provide insights on keyword optimization, readability, and meta-description effectiveness. The `generateText` method is called with the generated prompt, and upon receiving the result, the
displayAiResponse
function is invoked to show the AI’s feedback in the chat area. Any errors encountered during this process are logged.
- styling the popup
-
<style> #chat-container{ width: 600px; overflow: scroll; height: 300px; } #chat-form{ padding: 15px; } #chat-form-input{ width: 80%; padding: 10px; border-radius: 5px; border: 1px solid #999; } #chat-form-button{ padding: 10px; background: #1978de; color: #fff; border: 1px solid; border-radius: 6px; cursor: pointer; } #chat-form-button:hover{ background: #065cb8; } .chat-prompt, .chat-ai-response{ padding: 15px; text-align: justify; } .chat-prompt{ background: #eee; width: auto; padding: 10px; margin: 15px 100px 0 0; border-radius: 15px; border-top-left-radius: 0; } </style>
Combining all the code creates the Gemini plugin script
<script> (function (FroalaEditor) { // Define popup template. Object.assign(FroalaEditor.POPUP_TEMPLATES, { 'Gemini.popup': '[_BUTTONS_][_CUSTOM_LAYER_]' }); // Define popup buttons. Object.assign(FroalaEditor.DEFAULTS, { geminiPopupButtons: ['hideAI', '|', 'getFeedback','clearChat'], }); // Define the plugin. // The editor parameter is the current instance. FroalaEditor.PLUGINS.Gemini = function (editor) { // Create custom popup. function initPopup () { // Load popup template. var template = FroalaEditor.POPUP_TEMPLATES.Geminipopup; if (typeof template == 'function') template = template.apply(editor); // Popup buttons. var popup_buttons = ''; // Create the list of buttons. if (editor.opts.geminiPopupButtons.length > 1) { popup_buttons += '<div class="fr-buttons">'; popup_buttons += editor.button.buildList(editor.opts.geminiPopupButtons); popup_buttons += '</div>'; } // Load popup template. var template = { buttons: popup_buttons, custom_layer: `<div id="chat-container"></div> <div id="chat-form"> <input id="chat-form-input" type="text" placeholder="Ask AI" /> <button id="chat-form-button" type="button">Send!</button> </div> ` }; // Create popup. var $popup = editor.popups.create('Gemini.popup', template); return $popup; } function _chatButtonHandler () { const prompt = document.getElementById("chat-form-input").value; editor.Gemini.displayPrompet(prompt); editor.Gemini.loadingMessage(); editor.Gemini.generateText(prompt).then(result => { editor.Gemini.displayAiResponse(result); }).catch(error => { // Handle any errors here console.error('Error:', error); }); } // Show the popup function showPopup () { // Get the popup object defined above. var $popup = editor.popups.get('Gemini.popup'); // If popup doesn't exist then create it. // To improve performance it is best to create the popup when it is first needed // and not when the editor is initialized. if (!$popup) $popup = initPopup(); // Set the editor toolbar as the popup's container. editor.popups.setContainer('Gemini.popup', editor.$tb); // This custom popup is opened by pressing a button from the editor's toolbar. // Get the button's object in order to place the popup relative to it. var $btn = editor.$tb.find('.fr-command[data-cmd="AI"]'); // Compute the popup's position. var left = $btn.offset().left + $btn.outerWidth() / 2; var top = $btn.offset().top + (editor.opts.toolbarBottom ? 10 : $btn.outerHeight() - 10); const chatButton = document.getElementById("chat-form-button"); chatButton.addEventListener('click', _chatButtonHandler); // Show the custom popup. // The button's outerHeight is required in case the popup needs to be displayed above it. editor.popups.show('Gemini.popup', left, top, $btn.outerHeight()); } // Hide the custom popup. function hidePopup () { document.getElementById("chat-form-button").removeEventListener('click',_chatButtonHandler); editor.popups.hide('Gemini.popup'); } async function generateText(prompt) { const response = await fetch('/gemini', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt }), }); return await response.json(); } // Display a loading message function loadingMessage(){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.createElement('p'); loadingMessage.id = 'chat-loading-response'; loadingMessage.textContent = 'Generating response... Please wait.'; chatContainer.append(loadingMessage); } // Display prompt function displayPrompet(prompt){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.createElement('p'); loadingMessage.className = 'chat-prompt'; loadingMessage.textContent = prompt; chatContainer.append(loadingMessage); } function displayAiResponse(result){ const chatContainer = document.getElementById("chat-container"); const loadingMessage = document.getElementById('chat-loading-response'); // Remove the loading message chatContainer.removeChild(loadingMessage); // Create a new paragraph element for the response const p = document.createElement('p'); p.className = 'chat-ai-response'; // Set the text content to the resolved data p.innerHTML = result.response; // Prepend the new paragraph to the chat container chatContainer.append(p); } function clearChat(){ const chatContainer = document.getElementById("chat-container"); // Prepend the new paragraph to the chat container chatContainer.textContent=""; } // The start point for your plugin. function _init () {} return { _init: _init, generateText, loadingMessage, displayPrompet, displayAiResponse, clearChat, showPopup, hidePopup } } // Define an icon and command for the button that opens the custom popup. FroalaEditor.DefineIcon('AI', {NAME: 'AI', template: 'text'}); FroalaEditor.RegisterCommand('AI', { title: 'Display Gemini AI Popup', icon: 'AI', undo: false, focus: false, popup: true, // Buttons which are included in the editor toolbar should have the plugin property set. plugin: 'Gemini', callback: function () { if (!this.popups.isVisible('Gemini.popup')) { this.Gemini.showPopup(); } else { if (this.$el.find('.fr-marker')) { this.events.disableBlur(); this.selection.restore(); } this.popups.hide('Gemini.popup'); } } }); // Define custom popup close button icon and command. FroalaEditor.DefineIcon('hideAI', { NAME: 'close', SVG_KEY: 'close'}); FroalaEditor.RegisterCommand('hideAI', { title: 'Close', icon: 'hideAI', undo: false, focus: false, callback: function () { this.Gemini.hidePopup(); } }); // Define custom popup 1. FroalaEditor.DefineIcon('getFeedback', { NAME: 'star', SVG_KEY: 'star'}) FroalaEditor.RegisterCommand('getFeedback', { title: 'Get Feedback on Your Writing', icon: 'getFeedback', undo: false, focus: false, callback: function () { const editorTextContent = this.$el[0].textContent; const proPrompt = "Hello AI, could you please provide SEO feedback on my writing? I am looking for insights on keyword optimization, readability, and meta description effectiveness. Here is the text:"+editorTextContent+". Thank you!" this.Gemini.displayPrompet("Generate feedback about my writing (editor content)"); this.Gemini.loadingMessage(); this.Gemini.generateText(proPrompt).then(result => { this.Gemini.displayAiResponse(result); }).catch(error => { // Handle any errors here console.error('Error:', error); }); } }); // clear chat button FroalaEditor.DefineIcon('clearChat', { NAME: 'clear', SVG_KEY: 'remove'}) FroalaEditor.RegisterCommand('clearChat', { title: 'Start A New Chat', icon: 'clearChat', undo: false, focus: false, callback: function () { this.Gemini.clearChat(); } }); })(FroalaEditor); </script>
Customize Froala Editor to Use Gemini Plugin
Finally, edit the Froala toolbar to display the new Gemini dropdown button.
<script> var editor = new FroalaEditor("#example",{ toolbarButtons: [['AI', 'bold', 'italic', 'underline', 'strikeThrough', 'subscript', 'superscript'], ['fontFamily', 'fontSize', 'textColor', 'backgroundColor'], ['inlineClass', 'inlineStyle', 'clearFormatting']] }); </script>
Now once you run the application again, you should see the “AI” button on the toolbar. By clicking the “AI” button, you can display the Gemini AI popup. The popup allows you to interact with the Gemini plugin, enabling users to prompt Gemini to get responses and generate feedback on their writing.
Similarly, you can code new custom buttons to add more AI functions within the Froala editor. These custom functionalities enhance the Froala Editor’s capabilities and provide a seamless user experience for content creation and editing.
Get The Application Code
We made this tutorial code available for free download from this GitHub repo. This way you can quickly implement these features, customize them to your needs, and improve your content creation workflows.
If you would like a tutorial on using the “gemini-pro-vision” model for generating text from text and image inputs (multimodal mode), please leave a comment.
Unleash the Power of AI in Your Froala Editor
Incorporating Gemini AI into your Froala Editor is not just an enhancement—it’s a transformation. By enabling AI-powered features like chat interfaces and instant feedback within your editor, you equip your users with the tools to elevate their content creation process, making it more efficient, engaging, and error-free. Don’t let your application lag behind in harnessing the capabilities of AI.
Take action today! Start by purchasing Froala and integrate the Gemini AI with your Froala Editor to see immediate improvements in user engagement and content quality. Enhance your product, empower your users, and lead the way in innovative content creation.
No comment yet, add your voice below!