Packages
fnord
0.8.27
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ui.ex
defmodule UI do
require Logger
# ----------------------------------------------------------------------------
# Messaging
# ----------------------------------------------------------------------------
def say(msg) do
UI.flush()
msg
|> UI.Formatter.format_output()
|> IO.puts()
end
@spec confirm(binary) :: boolean
def confirm(msg), do: confirm(msg, false)
@spec confirm(binary, boolean) :: boolean
def confirm(msg, default) do
has_default = is_boolean(default)
cond do
is_tty?() ->
yes = if default == true, do: "Y", else: "y"
no = if default == false, do: "N", else: "n"
flush()
IO.write(:stderr, UI.Formatter.format_output("#{msg} (#{yes}/#{no}) "))
case IO.gets("") do
"y\n" -> true
"Y\n" -> true
_ -> default
end
has_default ->
default
true ->
Logger.warning(
"Confirmation requested without default, but session is not connected to a TTY."
)
false
end
end
def flush, do: Logger.flush()
def report_step(msg), do: info(msg)
def report_step(msg, detail), do: info(msg, detail)
def begin_step(msg, detail \\ nil) do
if is_nil(detail) do
Logger.info(IO.ANSI.format([:green, msg, :reset], colorize?()))
else
Logger.info(
IO.ANSI.format(
[:green, msg, :reset, ": ", :cyan, clean_detail(detail), :reset],
colorize?()
)
)
end
end
def end_step(msg, detail \\ nil) do
if is_nil(detail) do
Logger.info(IO.ANSI.format([:yellow, msg, :reset], colorize?()))
else
Logger.info(
IO.ANSI.format(
[:yellow, msg, :reset, ": ", :cyan, clean_detail(detail), :reset],
colorize?()
)
)
end
end
def printf_debug(item) do
Logger.debug(inspect(item, pretty: true))
Logger.flush()
item
end
def debug(msg) do
Logger.debug(IO.ANSI.format([:green, msg, :reset], colorize?()))
end
def debug(msg, detail) do
Logger.debug(
IO.ANSI.format(
[:green, msg, :reset, ": ", :cyan, clean_detail(detail), :reset],
colorize?()
)
)
end
def info(msg) do
Logger.info(IO.ANSI.format([:green, msg, :reset], colorize?()))
end
def info(msg, detail) do
msg = msg || ""
Logger.info(
IO.ANSI.format(
[:green, msg, :reset, ": ", :cyan, clean_detail(detail), :reset],
colorize?()
)
)
end
def warn(msg) do
Logger.warning(IO.ANSI.format([:yellow, msg, :reset], colorize?()))
end
def warn(msg, detail) do
Logger.warning(
IO.ANSI.format(
[:yellow, msg, :reset, ": ", :cyan, clean_detail(detail), :reset],
colorize?()
)
)
end
def error(msg) do
Logger.error(IO.ANSI.format([:red, msg, :reset], colorize?()))
end
def error(msg, detail) do
Logger.error(
IO.ANSI.format([:red, msg, :reset, ": ", :cyan, clean_detail(detail), :reset], colorize?())
)
end
def warning_banner(msg) do
IO.puts(
:stderr,
IO.ANSI.format(
[
:red_background,
:black,
" <<< WARNING >>> #{msg} <<< WARNING >>> ",
:reset
],
colorize?()
)
)
end
@spec fatal(binary) :: no_return()
def fatal(msg) do
Logger.error(IO.ANSI.format([:red, msg, :reset], colorize?()))
Logger.flush()
System.halt(1)
end
@spec fatal(binary, binary) :: no_return()
def fatal(msg, detail) do
Logger.error(
IO.ANSI.format([:red, msg, :reset, ": ", :cyan, clean_detail(detail), :reset], colorize?())
)
Logger.flush()
System.halt(1)
end
@spec log_usage(AI.Model.t(), non_neg_integer) :: :ok
def log_usage(model, usage) when is_integer(usage) do
percentage = Float.round(usage / model.context * 100, 2)
str_usage = Util.format_number(usage)
str_context = Util.format_number(model.context)
info("Context window usage", "#{percentage}% (#{str_usage} / #{str_context} tokens)")
end
def is_tty? do
:prim_tty.isatty(:stderr)
|> case do
true -> true
_ -> false
end
end
def colorize?, do: is_tty?()
def italicize(text) do
IO.ANSI.format([:italic, text, :reset], colorize?())
end
# ----------------------------------------------------------------------------
# Interactive elements
# ----------------------------------------------------------------------------
def quiet?() do
Application.get_env(:fnord, :quiet)
end
def spin(processing, func) do
if quiet?() do
begin_step(processing)
{msg, result} = func.()
end_step(msg)
result
else
Spinner.run(func, processing)
end
end
def progress_bar_start(name, label, total) do
if !quiet?() do
Owl.ProgressBar.start(
id: name,
label: label,
total: total,
timer: true,
absolute_values: true
)
end
end
def progress_bar_update(name) do
if !quiet?() do
Owl.ProgressBar.inc(id: name)
Owl.LiveScreen.await_render()
end
end
def async_stream(enumerable, fun, label \\ "Working", options \\ []) do
progress_bar_start(:async_stream, label, Enum.count(enumerable))
enumerable
|> Util.async_stream(
fn item ->
result = fun.(item)
progress_bar_update(:async_stream)
result
end,
options
)
end
def choose(prompt, options, owl_opts \\ []) do
prompt |> UI.Formatter.format_output() |> IO.puts()
Owl.IO.select(options, owl_opts)
end
def prompt(prompt, owl_opts \\ []) do
prompt |> UI.Formatter.format_output() |> IO.puts()
Owl.IO.input(owl_opts)
end
def clean_detail(nil), do: ""
def clean_detail(detail) do
if iodata?(detail) do
detail
else
inspect(detail, pretty: true, limit: :infinity)
end
|> IO.ANSI.format(colorize?())
|> IO.iodata_to_binary()
|> String.trim()
|> then(fn str ->
# If there are multiple lines, prefix with an empty line
# to ensure the string is displayed correctly.
if String.contains?(str, "\n") do
"\n" <> str
else
str
end
end)
end
def iodata?(term) when is_binary(term), do: true
def iodata?(term) when is_integer(term) and term in 0..255, do: true
def iodata?([]), do: true
def iodata?([head | tail]), do: iodata?(head) and iodata_tail?(tail)
def iodata?(_), do: false
defp iodata_tail?(tail) when is_list(tail), do: iodata?(tail)
defp iodata_tail?(tail) when is_binary(tail), do: true
defp iodata_tail?(_), do: false
end