Kino Documentation

repository·main·Indexed 19 days ago

https://github.com/livebook-dev/kino

Kino is a library used by Livebook to render rich, interactive, and visual output directly from Elixir code within a notebook environment. It includes support for specialized component packages such as kino_vega_lite for charting, kino_maplibre for maps, kino_explorer, kino_bumblebee, kino_db, kino_slack, and kino_benchee. The library provides various interactive UI components including data tables with sorting and pagination, pipeline debugging interfaces, Mermaid diagram rendering, and remote execution cells.

Tokens
10.7K
Snippets
37
Records
46
Agent score
66%

What's inside Kino

  1. Install Kino in Livebook

    main

    To use Kino within a Livebook session, use Mix.install/2 to include the dependency. This enables the rendering of rich and interactive output directly from your Elixir code.

    If you have specific use cases, you can also install specialized component packages such as kino_vega_lite for charting or kino_maplibre for maps.

    Mix.install([
      {:kino, "~> 0.19.0"}
    ])
  2. Monitor Kino objects and their lifecycles

    main

    Kino allows you to associate a process (pid) with a UI object using reference_object/2. Once a reference is established, you can use monitor_object/4 to trigger an action (sending a payload to a destination) when the associated processes terminate or the cells are re-evaluated.

    Options for monitor_object/4

    • :ack? (boolean, default false): If true, the monitoring process must acknowledge the message by sending back {payload, reply_to, reply_as}. This is useful for ensuring state is cleaned up before Livebook starts a new evaluation.
    # 1. Associate an object with a PID
    Kino.Bridge.reference_object(my_widget, self())
    
    # 2. Set up monitoring
    Kino.Bridge.monitor_object(my_widget, target_pid, :cleanup_payload, ack?: true)
  3. Implement the Kino.Table behaviour

    main

    To create an interactive data table in Kino, you must implement the Kino.Table behaviour in a module. This module acts as the data provider, handling state initialization, data fetching, and optional export/update logic. The Kino.Table module handles the UI and orchestration, while your module manages the actual data lifecycle.

    Required Callbacks

    • init(init_arg): Initializes the server state. Returns {:ok, info(), state()}.
    • get_data(rows_spec(), state()): Fetches the data for the current view. Returns {:ok, %{columns: list(column()), data: {:columns | :rows, list(list(String.t()))}, total_rows: non_neg_integer() | nil}, state()}.

    Optional Callbacks

    • export_data(rows_spec(), state(), format): Handles data export (e.g., to Markdown or CSV). Returns {:ok, %{data: binary(), extension: String.t(), type: String.t()}}.
    • on_update(update_arg, state): Responds to external updates via Kino.Table.update/2. Returns {:ok, state()}.

    Data Structures

    info()

    Used to configure the table's capabilities and appearance:

    %{ 
      :name => "My Table", 
      :features => [:pagination, :sorting, :export], 
      :num_rows => 50 
    }
    • :features: A list of supported features: :export, :refetch, :pagination, :sorting, :relocate, :actions.
    • :export: If provided, a map containing :formats (list of strings).
    • :num_rows: Sets the default number of rows per page.

    column()

    Defines the structure of a table column:

    %{ 
      :key => :id, 
      :label => "ID", 
      :type => "number", 
      :summary => %{"short" => "long"} 
    }
    • :type: Supported front-end types: "date", "list", "number", "struct", "text", "uri".

    rows_spec()

    Passed to get_data/2 and export_data/3 to describe the current view:

    %{ 
      :offset => 0, 
      :limit => 10, 
      :order => %{direction: :asc, key: :id}, 
      :relocates => [%{from_index: 0, to_index: 1}] 
    }
    defmodule MyTableProvider do
      use Kino.Table
    
      @impl true
      def init(_init_arg) do
        {:ok, %{name: "Users", features: [:pagination, :sorting]}, %{data: []}}
      end
    
      @impl true
      def get_data(rows_spec, state) do
        # Fetch data based on rows_spec (offset, limit, order)
        {:ok, %{columns: columns, data: {:rows, rows}, total_rows: 100}, state}
      end
    end
    
    # Usage
    kino_table = Kino.Table.new(MyTableProvider, %{})
  4. How Kino.Frame works for dynamic updates

    main

    A Kino.Frame acts as a stable UI container. The typical workflow involves:

    1. Initialization: Create the frame with Kino.Frame.new/1.
    2. Placement: Render the frame once using Kino.render/1 to reserve space in the Livebook notebook.
    3. Updates: Use Kino.Frame.render/3 (to replace) or Kino.Frame.append/3 (to add) to push new content to that specific location without moving other notebook elements.

    Example: Simple Loop Update

    frame = Kino.Frame.new() |> Kino.render()
    
    for i <- 1..100 do
      Kino.Frame.render(frame, i)
      Process.sleep(50)
    end
  5. Manage state in `Kino.JS.Live` server callbacks with `Kino.JS.Live.Context`

    main

    The Kino.JS.Live.Context struct represents the state available within Kino.JS.Live server callbacks. It allows you to manage custom server-side state across callback calls and identify the client that triggered an action.

    Properties

    • :assigns - A map containing custom server state kept across callback calls.
    • :origin - An opaque identifier of the client that triggered the given action. This is set in c:Kino.JS.Live.handle_connect/1 and c:Kino.JS.Live.handle_event/3.
  6. Implement a Smart Cell module

    main

    To create an interactive smart cell in Kino, you must implement a module that follows the smart cell protocol. The server uses these functions to manage state, handle editor changes, and render content.

    Required/Optional functions for your module:

    • to_attrs(ctx): (Required) Converts the current context into an attribute map.
    • to_source(attrs): (Required) Converts the attributes into a source string (or a list of strings) to be displayed in the cell.
    • handle_editor_change(source, ctx): (Required if using the editor) Called when the user modifies the code in the smart cell editor. It must return {:ok, new_ctx}.
    • scan_binding(ctx, binding, opts): (Optional) Used to scan for bindings.
    • scan_eval_result(ctx, result, opts): (Optional) Used to scan evaluation results.
  7. Supported Data Table features

    main

    The Data Table component dynamically enables UI elements based on the features array provided in the data object. Supported features include:

    • sorting: Enables column-based sorting. Triggers order_by event.
    • pagination: Enables page navigation. Triggers show_page event.
    • actions: Adds an 'actions' column with custom buttons for each row. Triggers action event.
    • relocate: Allows reordering columns. Triggers relocate event.
    • refetch: Adds a refresh button. Triggers refetch event.
    • export: Enables data downloading in supported formats. Triggers download event.
    • limit: Allows changing the number of rows displayed.
  8. Data structure for tree nodes

    main

    The tree component expects a recursive node structure. Based on the implementation, a node object should contain the following fields:

    • kind: (string) The type of node (e.g., "tuple").
    • content: (Array<TextItem>) An array of items to display when the node is collapsed.
    • expanded_before: (Array<TextItem>, optional) Items to display at the top of the node when it is expanded.
    • expanded_after: (Array<TextItem>, optional) Items to display at the bottom of the node when it is expanded.
    • children: (Array<Node>, optional) An array of child nodes. If present, the node is interactive (expandable/collapsible).

    Each TextItem in the arrays above should have:

    • text: (string) The text content to display.
    • color: (string, optional) A CSS color value to apply to the text.
  9. Implement a JS Live component module

    main

    To create a custom interactive component, you must define a module that implements the expected callbacks used by Kino.JS.Live.Server.

    Required/Supported Callbacks

    CallbackSignatureExpected ReturnDescription
    initinit(init_arg, ctx){:ok, ctx} or {:ok, ctx, opts}Initializes the component state.
    handle_connecthandle_connect(ctx){:ok, data, ctx}Called when a client connects. data is sent to the client.
    handle_eventhandle_event(event, payload, ctx){:noreply, ctx}Handles events sent from the JS client.
    handle_infohandle_info(msg, ctx){:noreply, ctx}Handles Elixir-side messages.
    terminateterminate(reason, ctx)(none)Cleanup logic when the server stops.

    Example Module Structure

    defmodule MyComponent do
      def init(init_arg, ctx) do
        # Initialize your state here
        {:ok, ctx}
      end
    
      def handle_connect(ctx) do
        # Data sent to the JS client upon connection
        {:ok, %{initial_state: "hello"}, ctx}
      end
    
      def handle_event("click", payload, ctx) do
        # Handle a 'click' event from JS
        {:noreply, ctx}
      end
    
      def terminate(reason, ctx) do
        # Cleanup
      end
    end
    defmodule MyComponent do
      def init(init_arg, ctx) do
        {:ok, ctx}
      end
    
      def handle_connect(ctx) do
        {:ok, %{initial_state: "hello"}, ctx}
      end
    
      def handle_event("click", payload, ctx) do
        {:noreply, ctx}
      end
    
      def terminate(reason, ctx) do
      end
    end
  10. Configure the Smart Cell editor

    main

    When initializing a smart cell, you can enable an interactive editor by providing an :editor option.

    Editor Options:

    • :source: (Required) The initial source code for the editor.
    • :language: (Optional) The programming language for syntax highlighting.
    • :placement: (Optional) Where the editor appears. Must be :top or :bottom. Defaults to :bottom.
    • :intellisense_node: (Optional) Configuration for intellisense.
    • :visible: (Optional) Whether the editor is visible. Defaults to true.

    Note on Deprecation: The :attribute and :default_source keys are deprecated and will be removed in v1.0. Use :source instead.

    # Example editor configuration
    # Note: :attribute and :default_source are deprecated
    # Use :source instead
    opts = [
      editor: %{
        source: "print('hello')",
        language: "python",
        placement: :top,
        visible: true
      },
      reevaluate_on_change: true
    ]
  11. Explore Kino component packages

    main

    Kino provides several officially supported packages designed for specific integrations and use cases:

    • kino_bumblebee: For Bumblebee integration.
    • kino_db: For database integrations.
    • kino_explorer: For Explorer integration.
    • kino_maplibre: For map plotting.
    • kino_slack: For Slack integration.
    • kino_vega_lite: For data charting.
    • kino_benchee: For rendering Benchee test results.
  12. Dispatch events using `broadcast_event/3`, `send_event/4`, and `emit_event/2`

    main

    The context provides three ways to dispatch events depending on the target audience:

    1. broadcast_event(ctx, event, payload \ nil): Sends an event to the registered JavaScript callback on all connected clients.
    2. send_event(ctx, client_id, event, payload \ nil): Sends an event to the registered JavaScript callback on a specific client (identified by client_id).
    3. emit_event(ctx, event): Emits an event to Elixir processes subscribed to this kino instance (e.g., via Kino.Control.stream/1).
    # To all clients
    broadcast_event(ctx, "new_point", %{x: 10, y: 10})
    
    # To a specific client
    send_event(ctx, client_id, "new_point", %{x: 10, y: 10})
    
    # To Elixir subscribers
    emit_event(%{event: :click, counter: 1})