We will cover briefly:
- Creating a ChatGPT server locally
- Creating ChatGPT UI
- Embed ChatGPT UI in Flutter
Creating a ChatGPT server locally
To create our local host server, we’ll utilize NodeJS. We create a folder called server and run
npm init -yThis creates a package.json file in the server Next, we install the dependencies like
npm install @openai/api express cors body-parserAfter that, we create a file called server.js and start writing our server-side code in it. But first, before we start programming
we require the OpenAI API key.
Create an account on the OpenAI website by clicking here. Next, we go to View API Keys

Here, you create your secret key, and that’s it!!!!!
Coming back to the codebase, we first import the dependencies using
import express from 'express'
import * as dotenv from 'dotenv'
import cors from 'cors'
import { Configuration, OpenAIApi } from 'openai'
dotenv.config()Next, we generate a file called .env and within it, we create a variable named OPENAI_API_KEY with the secretKey obtained from the OpenAI keys as its value.
Now, we create a configuration object using the OpenAI package
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const app = express()
app.use(cors())
app.use(express.json())
app.post('/', async (req, res) => {
try {
const prompt = req.body.prompt;
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});
res.status(200).send({
bot: response.data.choices[0].text
});
} catch (error) {
console.error(error)
res.status(500).send(error || 'Something went wrong');
}
})
app.listen(5001, () => console.log('http://localhost:5001'))The configuration settings for sending API calls to the OpenAI API are stored and managed by the Configuration object. For making successful API requests, it comprises parameters like the API key, endpoint URL, and request timeouts. We specify our apiKey which we got from the Open AI website

The OpenAIApi the object is a class in the OpenAI Node.js library that provides methods for interacting with the OpenAI API. It is used to authenticate the API key, configure the API request, and make API calls to the OpenAI servers
Next, we set up the express server and add the required middleware. There are various models available from OpenAI, but we choose text-davinci-003 them for our case.
A variation of OpenAI’s GPT (Generative Pre-trained Transformer) language model is the text-davinci-003 model. It is specifically trained for natural language generation tasks and has a large capacity to understand and produce human-like text. The model has been optimized for a variety of tasks, including text completion, summarization, question-answering, and more. It was trained on a vast amount of diverse text data.

Next, we create a POSTroute for ChatGPT called / and send the response back to the client.
await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});This snippet is using the OpenAI API to generate text completions based on a given prompt. It creates a completion by sending a request to the OpenAI API with the following parameters:
model: OpenAI model to usetext-davinci-003in our caseprompt: text prompt to generate the completion.temperature: between 0 and 1. Higher values mean that the model will take more risks in generating the text.max_tokens: maximum number of tokens (words or punctuation marks) that the model can generate.top_p: selects the most likely tokens until a certain threshold probability is reached.frequency_penalty: penalty value between -2.0 and 2.0 that penalizes the model for generating tokens already appeared in the generated textpresence_penalty: penalty value between -2.0 and 2.0 that penalizes the model for generating tokens similar to the text in the prompt.
We then send the JSON response inside a property called “bot” whose value is extracted from the “text” property of the first element in the “choices” array of the “response” data object.
We listen to the incoming requests on the port 5001 Finally, we start the server by running the following command in the terminal
# LISTEN TO THE PORT
app.listen(5001, () => console.log('http://localhost:5001'))
# TO RUN THE SERVER
npm run serverAnd if we try to curl the URL above with a sample request, we get the following
# SEND REQUEST TO LOCALHOST
curl -X \
POST "http://localhost:5001" -H \
"accept: application/json" -H \
"Content-Type: application/json" -d "{\"prompt\" : \"Hello\"}"We get the following:

Creating ChatGPT UI
We start by creating a Flutter Web project
Pre-Requisite
- We should be using the flutter
masterchannel - The Dart version should be
3.0.0or above
Let’s create a project using
flutter create chatgpt_embedding --platforms webNote: We specify the
platformto bewebmeaning the project only supportsweb
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 jsChanges to the Counter App
Our project has only 2 dart files as shown below

We refactor the main.dart and remove all the counter logic. Next, we create a file called gpt.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 is annotated with the @JSExportattribute, since we need to pass the user, queries to the JavaScript side. This makes the Dart object _MyHomePageState as exportable
Inside our initState we call the createDartExport for the _MyHomePageState which creates a JS object literal that forwards to our exported Dart class (which in our case is _MyHomePageState)
The createDartExport() method is used to create a JavaScript object that has a reference to a Dart object, which can then be accessed from JavaScript code. The setProperty() and callMethod() methods are used to set up JavaScript properties and call JavaScript functions, respectively
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', []);
}A JsObject instance export is created by calling the createDartExport() method provided by 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 set using the setProperty() method provided by js_util. The first property _appState, which is set to the export an object created in the previous step. This property can be accessed from JavaScript code to interact with the Dart object.
The second property_stateSet, which is a JavaScript function that is defined in another file (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:
_appStateand_stateSetare present inside the js file, which we will discuss later.
We create a TextEditingController and FloatingActionButton inside our gpt.dart Basically, this will take the user queries and pass them onto the ChatGPT 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.

All the interop with Dart is present inside this file. We make use of IIFE to create a function
(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;Integrate ChatGPT in Flutter
Integrate ChatGPT in Flutter
We will cover briefly:
- Creating a ChatGPT server locally
- Creating ChatGPT UI
- Embed ChatGPT UI in Flutter
https://flatteredwithflutter.com/media/30de1f56141c190bff4987a02af1ede8Integrate ChatGPT in Flutter
Creating a ChatGPT 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 @openai/api express cors body-parser
After that, we create a file called server.js and start writing our server-side code in it. But first, before we start programming
we require the OpenAI API key.
Create an account on the OpenAI website by clicking here. Next, we go to View API Keys

Here, you create your secret key, and that’s it!!!!!
Coming back to the codebase, we first import the dependencies using
import express from 'express'
import * as dotenv from 'dotenv'
import cors from 'cors'
import { Configuration, OpenAIApi } from 'openai'
dotenv.config()
Next, we generate a file called .env and within it, we create a variable named OPENAI_API_KEY with the secretKey obtained from the OpenAI keys as its value.
Now, we create a configuration object using the OpenAI package
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const app = express()
app.use(cors())
app.use(express.json())
app.post('/', async (req, res) => {
try {
const prompt = req.body.prompt;
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});
res.status(200).send({
bot: response.data.choices[0].text
});
} catch (error) {
console.error(error)
res.status(500).send(error || 'Something went wrong');
}
})
app.listen(5001, () => console.log('http://localhost:5001'))
The configuration settings for sending API calls to the OpenAI API are stored and managed by the Configuration object. For making successful API requests, it comprises parameters like the API key, endpoint URL, and request timeouts. We specify our apiKey which we got from the Open AI website.

The OpenAIApi the object is a class in the OpenAI Node.js library that provides methods for interacting with the OpenAI API. It is used to authenticate the API key, configure the API request, and make API calls to the OpenAI servers.
Special thanks to FeedSpot, for nominating me on their website! Checkout FeedSpot here for exciting Flutter content !!🙌 🎉🎉
Next, we set up the express server and add the required middleware. There are various models available from OpenAI, but we choose text-davinci-003 them for our case.
A variation of OpenAI’s GPT (Generative Pre-trained Transformer) language model is the text-davinci-003 model. It is specifically trained for natural language generation tasks and has a large capacity to understand and produce human-like text. The model has been optimized for a variety of tasks, including text completion, summarization, question-answering, and more. It was trained on a vast amount of diverse text data.

Next, we create a POSTroute for ChatGPT called / and send the response back to the client.
await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});
This snippet is using the OpenAI API to generate text completions based on a given prompt. It creates a completion by sending a request to the OpenAI API with the following parameters:
model: OpenAI model to usetext-davinci-003in our caseprompt: text prompt to generate the completion.temperature: between 0 and 1. Higher values mean that the model will take more risks in generating the text.max_tokens: maximum number of tokens (words or punctuation marks) that the model can generate.top_p: selects the most likely tokens until a certain threshold probability is reached.frequency_penalty: penalty value between -2.0 and 2.0 that penalizes the model for generating tokens already appeared in the generated textpresence_penalty: penalty value between -2.0 and 2.0 that penalizes the model for generating tokens similar to the text in the prompt.
We then send the JSON response inside a property called “bot” whose value is extracted from the “text” property of the first element in the “choices” array of the “response” data object.
We listen to the incoming requests on the port 5001 Finally, we start the server by running the following command in the terminal
# LISTEN TO THE PORT
app.listen(5001, () => console.log('http://localhost:5001'))
# TO RUN THE SERVER
npm run server
And if we try to curl the URL above with a sample request, we get the following
# SEND REQUEST TO LOCALHOST
curl -X \
POST "http://localhost:5001" -H \
"accept: application/json" -H \
"Content-Type: application/json" -d "{\"prompt\" : \"Hello\"}"
We get the following:

Creating ChatGPT UI
We start by creating a Flutter Web project
Pre-Requisite
- We should be using the flutter
masterchannel - The Dart version should be
3.0.0or above
Let’s create a project using
flutter create chatgpt_embedding --platforms web
Note: We specify the
platformto bewebmeaning the project only supportsweb
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

We refactor the main.dart and remove all the counter logic. Next, we create a file called gpt.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 is annotated with the @JSExportattribute, since we need to pass the user, queries to the JavaScript side. This makes the Dart object _MyHomePageState as exportable
Inside our initState we call the createDartExport for the _MyHomePageState which creates a JS object literal that forwards to our exported Dart class (which in our case is _MyHomePageState)
The createDartExport() method is used to create a JavaScript object that has a reference to a Dart object, which can then be accessed from JavaScript code. The setProperty() and callMethod() methods are used to set up JavaScript properties and call JavaScript functions, respectively.
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', []);
}
A JsObject instance export is created by calling the createDartExport() method provided by 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 set using the setProperty() method provided by js_util. The first property _appState, which is set to the export an object created in the previous step. This property can be accessed from JavaScript code to interact with the Dart object.
The second property_stateSet, which is a JavaScript function that is defined in another file (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:
_appStateand_stateSetare present inside the js file, which we will discuss later.
We create a TextEditingController and FloatingActionButton inside our gpt.dart Basically, this will take the user queries and pass them onto the ChatGPT 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.

All the interop with Dart is present inside this file. We make use of IIFE to create a function
(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 ChatGPT

For the sake of not making this article very long, attaching the css and html files for ChatGPT UI.
Embed ChatGPT UI in Flutter
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.loaderJavaScript API offered byflutter.js, we modify how a Flutter app is launched on the web.
The following phases make up the initialization process:
- The entry point script is loaded, and the service worker is initialized after retrieving the
main.dart.jsscript. - Initializing the Flutter engine which downloads the necessary files, including CanvasKit, fonts, and assets, to launch the Flutter web engine.
- Running the application which executes your Flutter app after preparing the DOM for it.
Interoperability 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.
Then, we create a new instance of the FormData object using the form variable. This FormData the object is used to retrieve the data from the form when it is submitted.
We generate a div with a unique ideach time the user submits their queries from the Flutter app.
Note: The id is basically the current timestamp
function chatStripe(isAi, value, uniqueId) {
return (
`
<div class="wrapper ${isAi && 'ai'}">
<div class="chat">
<div class="profile">
<img
src=${isAi ? './assets/bot.svg' : './assets/user.svg'}
alt="${isAi ? 'bot' : 'user'}"
/>
</div>
<div class="message" id=${uniqueId}>${value}</div>
</div>
</div>
`
)
}We also have a function named chatStripe() that takes three arguments: isAi, value, and uniqueId.

- The
isAithe argument is a boolean value that determines whether the chat stripe is for the bot or the user. If it’strue, then it’s for the bot, and if it’sfalse, then it’s for the user. - The
valuethe argument is the message text to be displayed in the chat stripe. - The
uniqueIdthe argument is the unique id that we generate from the above function
The
imgelement’ssrcthe attribute is set to the path of thebot.svgoruser.svgicon depending on theisAiargument.
The id attribute of the message div is set to the uniqueId argument.
Finally, we have a JavaScript function that handles form submissions. The function is triggered when the user submits a message to the bot by clicking a submit button or hitting the enter key in a text input field.
- We add the user’s chat stripe to the chat container using the
chatStripe()function - We then generate a unique ID using the
generateUniqueId()function
Add Server
We create a POST request to a local server at http://localhost:5001/ 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.
And, we type out the bot’s response in the message div using the typeText() function.
function typeText(element, text) {
let index = 0
let interval = setInterval(() => {
if (index < text.length) {
element.innerHTML += text.charAt(index)
index++
} else {
clearInterval(interval)
}
}, 20)
}If the response from the server is not OK, we display an error message in the message div and alert the error message.
Integrating with Flutter

We define a function called updateTextState, which sets the prompt property of the form element to the value of appState.textQuery. The updateTextState the function is not called directly but is instead registered as a callback using the appState.addHandler() method. This means that the updateState the function will be called every time appState changes.
let updateTextState = function () {
formData.set('prompt', appState.textQuery);
handleSubmit.call(form)
};
// Register a callback to update the text field from Flutter.
appState.addHandler(updateTextState);
// CHAT GPT FUNCTIONS
form.addEventListener("submit", (e) => {
handleSubmit(e)
});
// CHAT GPT FUNCTIONS
form.addEventListener("keyup", (e) => {
if (e.keyCode === 13) {
handleSubmit(e)
}
});We also attach event listeners for the
submitevent of theformelement. It listens for the form submission and calls thehandleSubmit()function with theeventobject as an argument.keyupevent of theformelement. It listens for the user to press the Enter key (keyCode13) and, it calls thehandleSubmit()function with theeventobject as an argument, which has the same effect as the first event listener.
The
handleSubmit()function is responsible for handling the form submission, displaying the user’s message, sending it to the server, and displaying the server’s response.
On the Flutter side,
A StreamController instance _streamController is defined by the type StreamController<void>.broadcast(). The broadcast() the method creates a stream controller that can handle multiple subscribers.
final _streamController = StreamController<void>.broadcast();
@js.JSExport()
void addHandler(void Function() handler) {
// This registers the handler we wrote in [js/js-interop.js]
_streamController.stream.listen((event) {
handler();
});
}Inside the method, a StreamSubscription is created by calling the listen() method on _streamController.stream. The listen() the method takes a callback function as an argument, which will be called each time an event is added to the stream.
Next, we modify our existing functions and add an event to the streamController to make sure the handler we wrote in the js-interop.js gets invoked.
Inside the setState() method, the _textQuery value is set by the text controller value, and then the StreamController instance _streamController is notified of the change by adding a null value to the stream using _streamController.add(null).
@js.JSExport()
void textInputCallback(String value) {
setState(() {
_textQuery = value;
// This line makes sure the handler gets invoked
_streamController.add(null);
});
}In this manner, the UI is updated whenever the textQuery is passed, from the Dart side and sent to the JS side.
