In this lesson, we will build a simple React web app that uses the browser runtime to run a Natural Language Processing Rune.
Before we begin, we will need to create a new React app using
create-react-app and the TypeScript template.
If you are following along in GitPod, you might want to run this in a different
/tmp) so you can use the
lesson-4 app as a reference.
yarn create has finished, we can add Rune's browser runtime as a
Due to a bug in
@tensorflow/tfjs-tflite's NPM package, we need to
do some extra steps if we want to do inference with TensorFlow Lite models.
You should now be able to run
yarn start and see the familiar React logo!
You have already spent a couple of lessons writing Runefiles, so we won't be
trying anything fancy with this Rune. All it'll do is take some data from a
RAW capability and send it straight to a
We want this Rune to be accessible from the web app so we'll tell
to save it to the
Now we've got the project skeleton and a Rune out of the way, let's dive into some React code!
Our goal is to create a UI with a text box, a "Calculate" button, and an area to display the Rune's output.
At the very top of the
src/App.tsx file we'll need some imports and
This app won't need any extra components, so we'll put all the state at the
top of the
App functional component.
Notice that the
number has a default value of
65.0. We'll use this when
checking our work at the very end.
We can use the
useEffect() to make sure the
lesson-4.rune is downloaded and
the Rune runtime is initialized when the page is first opened. To keep things
tidy, all of that logic will be put in an
init() function that we'll define
in a bit.
We want to run the Rune whenever the user presses "Calculate", so let's define
submit function which will pass the user's input to the Rune and save
the outputs to the
result. Again, to keep things clean we'll be defining a
evaluate() function for running the Rune and reading results.
Keep in mind that we'll be loading the
runtime on startup so there is a short
window when it won't be available. If this happens, the
submit handler should
just return silently.
We also need to throw in a
e.preventDefault() to make sure hitting "Calculate"
<enter> won't cause the page to refresh.
Finally, we have the JSX which defines our UI. For those who have used React before this won't be anything special, but if you are new to React then you may want to check out the official tutorial.
Now we've defined our
App component we can come back to the
from before. The interesting bit is the
builder().build() call - this uses
@hotg-ai/rune's high level builder API to configure the Rune runtime before it
In our case we aren't changing any of the defaults, but if you want
to you can handle any log statements from inside the Rune by passing a callback
onDebug() method, or you can register your own model handlers with the
withModelHandler() method (e.g. to handle models Rune doesn't support out of
We also need to define the
evaluate() function from earlier.
The runtime will use our
generateInput() function if it wants to read from
a capability, using the
InputDescription parameter to let you know what type
of capability it is (e.g.
RAW) and any arguments that were specified in the
Normally you would inspect the
InputDescriptor and generate the requested
data, but because we know this Rune only uses a single capability we can be
lazy and return a 1D
int32 tensor (note: Rune uses
i32 for 32-bit signed
integers, while tf.js prefers to call them
If we pull up the browser's developer tools, we can see the messages that were logged as the runtime was loaded and then run.
We can actually double-check the output using the
rune CLI. The binary
representation of the number 65 is an
A followed by 3 null bytes, so we can
printf command to generate an
input.bin file containing exactly
From there, it's just a case of running
rune run like normal and inspecting
Look at that, they're identical!