Integrate Google Bard in Flutter

Integrate Google Bard in Flutter

We will cover briefly:

  1. Creating a Google Bard server locally
  2. Integrate Google Bard UI in Flutter
  3. Send queries/prompts from Dart (Flutter) to JS (Google Bard)

Creating a Google Bard server locally

To create our local host server, we’ll utilize NodeJS. We create a folder called server and run

npm init -y

This creates a package.json file in the server Next, we install the dependencies like

npm install @google-ai/generativelanguage google-auth-library express

After that, we create a file called text-prompt.js and start writing our server-side code in it. But first, before we start programming

we require the Makersuite API key.

What is MakerSuite?

MakerSuite

MakerSuite is a software suite or platform designed to facilitate and enhance the process of creating, designing, and prototyping various products, projects, or solutions.

Go to the Get API Key to generate your Makersuite Key

MakerSuite API Key
MakerSuite API Key

Subsequently, you have three choices within the MakerSuite platform:

  • text-based prompt,
  • a data prompt,
  • or a chat prompt.
MakerSuite Prompts
MakerSuite Prompts

Coming back to the codebase, we first import the dependencies using

import { TextServiceClient } from "@google-ai/generativelanguage";
import { GoogleAuth } from "google-auth-library";

import * as dotenv from "dotenv";
dotenv.config();

Next, we generate a file called .env and within it, we create a variable named API_KEY with the secretKey obtained from the MakerSuite keys as its value.

Now, we create a text service client object using the generative AI package

const client = new TextServiceClient({
  authClient: new GoogleAuth().fromAPIKey(process.env.API_KEY),
});

const generateText = async (promptString) => {
  const result = await client.generateText({
    model: "models/text-bison-001",
    top_k: 40,
    top_p: 0.95,
    prompt: {
      text: promptString,
    },
  });

  return result;
};

The snippet sets up a client (TextServiceClient) and it configures the authentication for this client using an API key stored in the API_KEY environment variable.

We call the client.generateText : which generates a response from the model given an input prompt.

  • model : The model name to use with the format name=models/{model} We use the text-bison-001
Palm API Models
Palm API Models
  • top_k : The maximum number of tokens to consider when sampling, and defaults to 40
  • top_p : The maximum cumulative probability of tokens to consider when sampling.
  • prompt : The free-form input text is given to the model as a prompt. The model will generate a TextCompletion response it predicts as the completion of the input text.
app.post("/textPrompt", async (req, res) => {
  try {
    const prompt = req.body.prompt;
    const response = await textApi.callTextApi(prompt);
    res.setHeader("Content-Type", "application/json");

    let respContent = response[0]?.candidates[0]?.output;

    res.status(200).send({
      bot: respContent || "No output available",
    });
  } catch (error) {
    console.error("❌ ❌ Error", error);
  }
});
  • Next, we set up a POST route /textPrompt using Express.js We extract the prompt property from the request’s body.
  • We call thecallTextApi function, passing the extracted prompt
  • We extract the data from the response object and send the HTTP response. If the respContent variable has a value, it sends that as the “bot” property in a JSON response. If respContent is undefined or falsy, it sends “No output available” as the “bot” property.

We listen to the incoming requests on the port 1234 Finally, we start the server by running the following command in the terminal

# LISTEN TO THE PORT
app.listen(1234, () => console.log('http://localhost:1234'))
# TO RUN THE SERVER
npm run server

We get the following:

Response from the localhost server

Integrate Google Bard UI in Flutter

We start by creating a Flutter Web project

Pre-Requisite

  • We should be using the flutter master channel
  • The Dart version should be 3.0.0 or above

Let’s create a project using

flutter create bard_embedding --platforms web

Note: We specify the platform to be web meaning the project only supports web

By default, Flutter gives us the counter app.

We include the js package in our project to enable seamless interoperability between JavaScript and Dart code. Any function in your Dart code can be annotated with a @JSExportproperty using JS, allowing you to invoke it from JavaScript code.

flutter pub add js

Changes to the Counter App

Our project has only 2 dart files as shown below

Dart Files
Dart Files

We refactor the main.dart and remove all the counterlogic. Next, we create a file called bard.dart This contains the MyHomePage which is a stateful widget and _MyHomePageState is the state class for the MyHomePage widget.

class MyHomePage extends StatefulWidget {
  
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
@js.JSExport()
class _MyHomePageState extends State<MyHomePage> {}

Next, we import the package js and js_util as

import 'package:js/js.dart' as js;
import 'package:js/js_util.dart' as js_util;

The _MyHomePageState class is marked with the @JSExport attribute because we need to make it accessible from JavaScript in order to pass user data and queries. This annotation essentially exports the Dart object _MyHomePageState for JavaScript interaction.

In the initState method, we invoke createDartExport for _MyHomePageState. This function generates a JavaScript object literal that acts as a bridge to our exported Dart class, which is _MyHomePageState in this case.

The createDartExport() method is used to create a JavaScript object that has a reference to a Dart object, enabling it to be used from JavaScript code. This is achieved using methods like setProperty() and callMethod(). These methods facilitate the setup of JavaScript properties and the invocation of JavaScript functions associated with the Dart object.

void initState() {
  super.initState();
  final export = js_util.createDartExport(this);
  
  // These two are used inside the [js/js-interop.js]
  js_util.setProperty(js_util.globalThis, '_appState', export);
  js_util.callMethod<void>(js_util.globalThis, '_stateSet', []);
}

An exported JsObject instance is generated by invoking the createDartExport() function from the js_util library. This creates a JavaScript object that has a reference to the Dart object that is being exported.

Two JavaScript properties are configured using the setProperty() method from js_util. The first property, _appState, is assigned the exported object created in the previous step. This property can be accessed from JavaScript code, allowing interaction with the associated Dart object.

The second property _stateSetrepresents a JavaScript function defined in js-interop.js. This function is called using the callMethod() method provided by js_util. The empty array [] passed as the second argument indicates that no arguments are being passed to the function.

Note: _appState and _stateSet are present inside the js file, which we will discuss later.

We create a TextEditingController and FloatingActionButton inside our bard.dart Basically, this will take the user queries and pass them onto the Bard UI. There is a function that gets invoked when the user finishes the query or when the user presses the FAB

// This stores the user query
String _textQuery = '';

void textInputCallback(String value) {
   textFocusNode.requestFocus();
   setState(() {
     _textQuery = value;
     // This line makes sure the handler gets invoked
     _streamController.add(null);
   });
}
<script src="js/js-interop.js" defer></script>

The src the attribute specifies the URL of the JavaScript file to be loaded, in this case js-interop.js.

JavaScript File
JavaScript File

All the interop with Dart is present inside this file.

All interactions and interoperability with Dart are present within this file. We make use of IIFE to create a function.

An IIFE is a JavaScript function that is immediately executed as soon as it is defined.

(function () {
  window._stateSet = function () {
    console.log('HELLO From Flutter!!')
  };
}());

This code sets the _stateSet function as a property of the global window object. Next, we access the _appState and save it inside a variable inside the JS

let appState = window._appState;

HTML and CSS for Google Bard

Bard UI in Flutter
Bard UI in Flutter

Attaching the css and html files for Bard UI.

Send prompts from Dart (Flutter) to JS (Google Bard)

This section is split into two segments:

  • Customizing the Initialization of a Flutter Web App and
  • sending prompts from Dart (Flutter) to JavaScript (Google Bard)

Customizing Flutter Web App Initialization

Since we want to show the Flutter app, we create a div with ID as flutter_target

We tweak the script tag inside the index.html by first adding an event listener to the window Get the div ID, which represents the flutter app in our case flutter_target, using the query selector

window.addEventListener("load", function (ev) {
  let target = document.querySelector("#flutter_target");
  
  _flutter.loader.loadEntrypoint({
    onEntrypointLoaded: async function (engineInitializer) {
      let appRunner = await engineInitializer.initializeEngine({
        hostElement: target,
      });
      await appRunner.runApp();
    },
  });
});
  • Using the _flutter.loader JavaScript API offered by flutter.js, we modify how a Flutter app is launched on the web.

Sending prompts between Javascript and Dart

To pass the _textQuery value from Flutter to JS, we create the textQuery function inside of the gpt.dart file and annotate it with the @JSExport property (making it accessible from the JS side)

@js.JSExport()
String get textQuery => _textQuery;

JS Changes

const form = document.querySelector('form')
const chatContainer = document.querySelector('#chat_container')
const formData = new FormData(form)

We select the first form element on the page using the document.querySelector() method. Next, we select an HTML element with an ID of chat_container using the document.querySelector() method.

We have a function named chatStripe() that takes three arguments: isAi, value, and uniqueId.

Chat Stripe
Chat Stripe

The img element’s src the attribute is set to the path of the bard.svg or user.svg icon depending on the isAi argument.

The id attribute of the message div is set to the uniqueId argument.

Finally, we have a JavaScript function responsible for managing form submissions. The function is triggered when the user submits a message to the bot, either by clicking a submit button or pressing the enter key within a text input field.

Add Server

We create a POST request to a local server at http://localhost:1234/ with the user’s message as a JSON payload.

Note: The server was created in the first step above

If the response from the server is OK, we retrieve the bot’s response from the JSON data and trim any trailing spaces or newlines using the trim() method.

Flutter and Google Bard
Flutter and Google Bard