Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Building an AI-Powered iOS App with LiveView Native

building-an-ai-powered-ios-app-with-lvn.livemd

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

Startup Wisconsin Week 2024

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.

Elixir Ecosystem

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