Building an AI-Powered iOS App with LiveView Native
notebook_path = __ENV__.file |> String.split("#") |> hd()
Mix.install(
[
{:kino_live_view_native, github: "liveview-native/kino_live_view_native"},
{:kino_bumblebee, "~> 0.5.0"},
{:exla, ">= 0.0.0"},
{:req, "~> 0.5.8"}
],
config: [
kino_live_view_native: [
qr_enabled: true
],
server: [
{ServerWeb.Endpoint,
[
server: true,
url: [host: "localhost"],
adapter: Phoenix.Endpoint.Cowboy2Adapter,
render_errors: [
formats: [html: ServerWeb.ErrorHTML, json: ServerWeb.ErrorJSON],
layout: false
],
pubsub_server: Server.PubSub,
live_view: [signing_salt: "JSgdVVL6"],
http: [ip: {0, 0, 0, 0}, port: 4000],
secret_key_base: String.duplicate("a", 64),
live_reload: [
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg|styles)$",
~r/#{notebook_path}$/
]
],
code_reloader: true
]}
],
kino: [
group_leader: Process.group_leader()
],
phoenix: [
template_engines: [neex: LiveViewNative.Engine]
],
phoenix_template: [format_encoders: [swiftui: Phoenix.HTML.Engine]],
mime: [
types: %{"text/swiftui" => ["swiftui"], "text/styles" => ["styles"]}
],
live_view_native: [plugins: [LiveViewNative.SwiftUI]],
live_view_native_stylesheet: [
content: [
swiftui: [
"lib/**/*swiftui*",
notebook_path
]
],
output: "priv/static/assets",
attribute_parsers: [
style: [
livemd: &Server.AttributeParsers.Style.parse/2
]
]
],
# Ensures that app.js compiles to avoid the switch to longpolling
# when a LiveView doesn't exist yet
esbuild: [
version: "0.17.11",
server_web: [
args:
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
],
nx: [default_backend: EXLA.Backend]
],
force: true
)
Introduction
What We’re Building
Today we will be building and testing an iOS app that uses machine learning to perform token classification. We’ll be using Livebook to build and run the application.
Before we start building, let’s take a look at the libraries we’ll be using.
For examples of LiveView Native applications, the LiveView Native organization on Github has a collection of Livebook notebooks that will help you get started.
Token Classification
defmodule TokenClassifier do
def run(text) do
Nx.Serving.run(serving(), text)
end
def serving do
{:ok, model_info} = Bumblebee.load_model({:hf, "dslim/bert-base-NER"})
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "google-bert/bert-base-cased"})
Bumblebee.Text.token_classification(model_info, tokenizer,
aggregation: :same,
compile: [batch_size: 1, sequence_length: 100],
defn_options: [compiler: EXLA]
)
end
end
TokenClassifier.run("Green Bay Packers will defeat the Chicago Bears at Soldier Field on Sunday.")
LiveView & LiveView Native
require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns) do
~LVN"""
Enter your text for classification...
Submit
Processing statement...
<%= for token <- tokens do %>
<%= "#{token[:phrase]} (#{token[:label]})" %>
<% end %>
"""
end
end
defmodule ServerWeb.ExampleLive do
use ServerWeb, :live_view
use ServerNative, :live_view
@impl true
def mount(_, _, socket) do
socket = assign_async(socket, :tokens, fn -> {:ok, %{tokens: []}} end)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
Processing statement...
<%= for token <- tokens do %>
- <%= "#{token[:phrase]} (#{token[:label]})" %>
<% end %>
"""
end
@impl true
def handle_event("submit", %{"text" => text}, socket) do
socket =
assign_async(socket, :tokens, fn ->
%{entities: tokens} = TokenClassifier.run(text)
{:ok, %{tokens: tokens}}
end)
{:noreply, socket}
end
end
|> Server.SmartCells.LiveViewNative.register("/")
import Server.Livebook, only: []
import Kernel
:ok
Sample Templates
require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns) do
~LVN"""
Text Classification
Statement
Submit
Statement: <%= @statement %>
Processing statement...
<%= for term <- terms do %>
<%= "#{term[:phrase]} (#{term[:label]})" %>
<% end %>
"""
end
end
defmodule ServerWeb.ExampleLive do
use ServerWeb, :live_view
use ServerNative, :live_view
@impl true
def mount(_params, _session, socket) do
socket =
socket
|> assign_async(:terms, fn -> {:ok, %{terms: []}} end)
|> assign(:statement, "")
{:ok, socket}
end
@impl true
def handle_event("validate", %{"statement" => statement}, socket) do
socket = assign(socket, :statement, statement)
{:noreply, socket}
end
@impl true
def handle_event("save", %{"statement" => statement}, socket) do
# [
# %{label: "PER", start: 0, end: 11, score: 0.9997237622737885, phrase: "Jordan Love"},
# %{label: "ORG", start: 38, end: 47, score: 0.8440460562705994, phrase: "Green Bay"}
# ]
socket = assign_async(socket, :terms, fn ->
%{entities: terms} = Nx.Serving.run(TokenClassifier.serving(), statement)
{:ok, %{terms: terms}}
end)
{:noreply, socket}
end
@impl true
def render(assigns), do: ~H"Hello, from LiveView Web
"
end
|> Server.SmartCells.LiveViewNative.register("/")
import Server.Livebook, only: []
import Kernel
:ok