Skip to content

Instantly share code, notes, and snippets.

@joelpaulkoch
Last active June 17, 2025 09:05
Show Gist options
  • Save joelpaulkoch/9192abd23bd2e6ff76be314c24173974 to your computer and use it in GitHub Desktop.
Save joelpaulkoch/9192abd23bd2e6ff76be314c24173974 to your computer and use it in GitHub Desktop.
A RAG for Elixir in Elixir
Mix.install(
[
{:phoenix_playground, "~> 0.1.6"},
{:phoenix, "~> 1.7.14"},
{:phoenix_live_view, "~> 1.0.0-rc.1"},
{:chroma, "~> 0.1.3"},
{:text_chunker, "~> 0.3.1"},
{:nx, "~> 0.9.0"},
{:exla, "~> 0.9.1"},
{:axon, "~> 0.7.0"},
{:bumblebee, github: "joelpaulkoch/bumblebee", branch: "jina-embeddings-v2-base-code"}
],
config: [
chroma: [host: "http://localhost:8000", api_base: "api", api_version: "v1"],
nx: [default_backend: EXLA.Backend]
]
)
defmodule RagTime.Serving do
def build_embedding_serving() do
repo = {:hf, "jinaai/jina-embeddings-v2-base-code"}
{:ok, model_info} =
Bumblebee.load_model(repo,
spec_overrides: [architecture: :base],
params_filename: "model.safetensors"
)
{:ok, tokenizer} = Bumblebee.load_tokenizer(repo)
Bumblebee.Text.TextEmbedding.text_embedding(model_info, tokenizer,
compile: [batch_size: 64, sequence_length: 512],
defn_options: [compiler: EXLA],
output_attribute: :hidden_state,
output_pool: :mean_pooling
)
end
def build_llm_serving() do
repo = {:hf, "microsoft/phi-3.5-mini-instruct"}
{:ok, model_info} = Bumblebee.load_model(repo)
{:ok, tokenizer} = Bumblebee.load_tokenizer(repo)
{:ok, generation_config} = Bumblebee.load_generation_config(repo)
generation_config = Bumblebee.configure(generation_config, max_new_tokens: 512)
Bumblebee.Text.generation(model_info, tokenizer, generation_config,
compile: [batch_size: 1, sequence_length: 6000],
defn_options: [compiler: EXLA],
stream: false
)
end
end
defmodule RagTime.Ingestion do
def ingest(collection, input_path) do
files =
Path.wildcard(input_path <> "/**/*.{ex, exs}")
|> Enum.reject(&String.contains?(&1, ["/_build/", "/deps/"]))
files_content = for file <- files, do: File.read!(file)
documents =
Enum.zip_with(files, files_content, fn file, content ->
%{content: content, source: file}
end)
chunks = chunk_with_metadata(documents, :elixir)
embeddings = generate_embeddings(chunks)
store_embeddings_and_chunks(collection, embeddings, chunks)
end
def chunk_with_metadata(documents, format) do
chunks = Enum.map(documents, &TextChunker.split(&1.content, format: format))
sources = Enum.map(documents, & &1.source)
Enum.zip(sources, chunks)
|> Enum.flat_map(fn {source, source_chunks} ->
for chunk <- source_chunks do
%{
source: source,
start_byte: chunk.start_byte,
end_byte: chunk.end_byte,
text: chunk.text
}
end
end)
end
def generate_embeddings(chunks) do
chunk_text_list = Enum.map(chunks, & &1.text)
Nx.Serving.batched_run(RagTime.EmbeddingServing, chunk_text_list)
|> Enum.map(fn %{embedding: embedding} -> Nx.to_list(embedding) end)
end
def store_embeddings_and_chunks(collection, embeddings, chunks) do
documents = Enum.map(chunks, & &1.text)
ids = Enum.map(chunks, &chunk_to_id(&1))
Chroma.Collection.add(collection, %{documents: documents, ids: ids, embeddings: embeddings})
end
defp chunk_to_id(%{source: path, start_byte: start_byte, end_byte: end_byte}) do
file_content = File.read!(path)
start_line =
file_content
|> String.byte_slice(0, start_byte)
|> String.split("\n")
|> Enum.count()
end_line =
file_content
|> String.byte_slice(0, end_byte)
|> String.split("\n")
|> Enum.count()
"#{path}:#{start_line}-#{end_line}"
end
end
defmodule RagTime.Retrieval do
def retrieve(collection, query) do
%{embedding: query_embedding} = Nx.Serving.batched_run(RagTime.EmbeddingServing, query)
query_embedding = Nx.to_list(query_embedding)
{:ok, results} =
Chroma.Collection.query(collection,
results: 10,
query_embeddings: [query_embedding]
)
[code_chunks] = results["documents"]
[sources] = results["ids"]
{code_chunks, sources}
end
end
defmodule RagTime.Generation do
def generate_response(query, context_documents, context_sources) do
context =
Enum.map(context_documents, fn code_chunk ->
"""
[...]
#{code_chunk}
[...]
"""
end)
|> Enum.join("\n\n")
prompt = """
<|system|>
You are a helpful assistant.</s>
<|user|>
Context information is below.
---------------------
#{context}
---------------------
Given the context information and no prior knowledge, answer the query.
Query: #{query}
Answer: </s>
<|assistant|>
"""
%{results: [result]} = Nx.Serving.batched_run(RagTime.LLMServing, prompt)
%{
query: query,
context: context,
context_sources: context_sources,
response: result.text
}
end
end
defmodule RagTime do
def ingest(collection, path), do: RagTime.Ingestion.ingest(collection, path)
def query(collection, query) do
{context, sources} = RagTime.Retrieval.retrieve(collection, query)
RagTime.Generation.generate_response(query, context, sources)
end
end
defmodule RagLive.Style do
def styles do
"""
:root {
--bs-transition-default-duration: 0.075s;
--bs-transition-default-easing: ease-in-out;
--bs-transition-long-duration: 0.5s;
--bs-transition-long-easing: ease-in-out;
}
:root {
--bs-color-brand-1-light-4: rgb(243.95, 242.25, 255);
--bs-color-brand-1-light-4-rgb:
244,
242,
255;
--bs-color-brand-1-light-3: rgb(221.85, 216.75, 255);
--bs-color-brand-1-light-3-rgb:
222,
217,
255;
--bs-color-brand-1-light-2: rgb(199.75, 191.25, 255);
--bs-color-brand-1-light-2-rgb:
200,
191,
255;
--bs-color-brand-1-light-1: rgb(177.65, 165.75, 255);
--bs-color-brand-1-light-1-rgb:
178,
166,
255;
--bs-color-brand-1: #9485ff;
--bs-color-brand-1-rgb:
148,
133,
255;
--bs-color-brand-1-dark-1: rgb(100.3, 76.5, 255);
--bs-color-brand-1-dark-1-rgb:
100,
77,
255;
--bs-color-brand-1-dark-2: rgb(30.6, 0, 229.5);
--bs-color-brand-1-dark-2-rgb:
31,
0,
230;
--bs-color-brand-1-dark-3: rgb(13.6, 0, 102);
--bs-color-brand-1-dark-3-rgb:
14,
0,
102;
--bs-color-brand-1-dark-4: rgb(6.8, 0, 51);
--bs-color-brand-1-dark-4-rgb:
7,
0,
51;
--bs-color-brand-2-light-4: rgb(229.5, 255, 253.7);
--bs-color-brand-2-light-4-rgb:
229,
255,
254;
--bs-color-brand-2-light-3: rgb(178.5, 255, 251.1);
--bs-color-brand-2-light-3-rgb:
179,
255,
251;
--bs-color-brand-2-light-2: rgb(140.25, 255, 249.15);
--bs-color-brand-2-light-2-rgb:
140,
255,
249;
--bs-color-brand-2-light-1: rgb(51, 255, 244.6);
--bs-color-brand-2-light-1-rgb:
51,
255,
245;
--bs-color-brand-2: #00e5da;
--bs-color-brand-2-rgb:
0,
229,
218;
--bs-color-brand-2-dark-1: rgb(0, 153, 145.2);
--bs-color-brand-2-dark-1-rgb:
0,
153,
145;
--bs-color-brand-2-dark-2: rgb(0, 127.5, 121);
--bs-color-brand-2-dark-2-rgb:
0,
128,
121;
--bs-color-brand-2-dark-3: #005752;
--bs-color-brand-2-dark-3-rgb:
0,
87,
82;
--bs-color-brand-2-dark-4: rgb(0, 38.25, 36.3);
--bs-color-brand-2-dark-4-rgb:
0,
38,
36;
--bs-color-grayscale-white: white;
--bs-color-grayscale-white-rgb:
255,
255,
255;
--bs-color-grayscale-light-4: rgb(248.631375, 248.401875, 250.123125);
--bs-color-grayscale-light-4-rgb:
249,
248,
250;
--bs-color-grayscale-light-3: rgb(235.894125, 235.205625, 240.369375);
--bs-color-grayscale-light-3-rgb:
236,
235,
240;
--bs-color-grayscale-light-2: rgb(216.78825, 215.41125, 225.73875);
--bs-color-grayscale-light-2-rgb:
217,
215,
226;
--bs-color-grayscale-light-1: rgb(191.31375, 189.01875, 206.23125);
--bs-color-grayscale-light-1-rgb:
191,
189,
206;
--bs-color-grayscale: rgb(140.36475, 136.23375, 167.21625);
--bs-color-grayscale-rgb:
140,
136,
167;
--bs-color-grayscale-dark-1: rgb(99.858, 95.37, 129.03);
--bs-color-grayscale-dark-1-rgb:
100,
95,
129;
--bs-color-grayscale-dark-2: rgb(81.134625, 77.488125, 104.836875);
--bs-color-grayscale-dark-2-rgb:
81,
77,
105;
--bs-color-grayscale-dark-3: rgb(49.929, 47.685, 64.515);
--bs-color-grayscale-dark-3-rgb:
50,
48,
65;
--bs-color-grayscale-dark-4: rgb(37.44675, 35.76375, 48.38625);
--bs-color-grayscale-dark-4-rgb:
37,
36,
48;
--bs-color-grayscale-black: black;
--bs-color-grayscale-black-rgb:
0,
0,
0;
--bs-color-danger-light-4: rgb(255, 242.25, 243.55);
--bs-color-danger-light-4-rgb:
255,
242,
244;
--bs-color-danger-light-3: rgb(255, 216.75, 220.65);
--bs-color-danger-light-3-rgb:
255,
217,
221;
--bs-color-danger-light-2: rgb(255, 191.25, 197.75);
--bs-color-danger-light-2-rgb:
255,
191,
198;
--bs-color-danger-light-1: rgb(255, 153, 163.4);
--bs-color-danger-light-1-rgb:
255,
153,
163;
--bs-color-danger: rgb(255, 102, 117.6);
--bs-color-danger-rgb:
255,
102,
118;
--bs-color-danger-dark-1: rgb(216.75, 0, 22.1);
--bs-color-danger-dark-1-rgb:
217,
0,
22;
--bs-color-danger-dark-2: rgb(153, 0, 15.6);
--bs-color-danger-dark-2-rgb:
153,
0,
16;
--bs-color-danger-dark-3: rgb(89.25, 0, 9.1);
--bs-color-danger-dark-3-rgb:
89,
0,
9;
--bs-color-danger-dark-4: rgb(63.75, 0, 6.5);
--bs-color-danger-dark-4-rgb:
64,
0,
7;
--bs-color-warning-light-4: rgb(255, 243.5, 229.5);
--bs-color-warning-light-4-rgb:
255,
243,
229;
--bs-color-warning-light-3: #ffe8cc;
--bs-color-warning-light-3-rgb:
255,
232,
204;
--bs-color-warning-light-2: rgb(255, 214.75, 165.75);
--bs-color-warning-light-2-rgb:
255,
215,
166;
--bs-color-warning-light-1: rgb(255, 203.25, 140.25);
--bs-color-warning-light-1-rgb:
255,
203,
140;
--bs-color-warning: rgb(255, 168.75, 63.75);
--bs-color-warning-rgb:
255,
169,
64;
--bs-color-warning-dark-1: darkorange;
--bs-color-warning-dark-1-rgb:
255,
140,
0;
--bs-color-warning-dark-2: rgb(165.75, 91, 0);
--bs-color-warning-dark-2-rgb:
166,
91,
0;
--bs-color-warning-dark-3: #613500;
--bs-color-warning-dark-3-rgb:
97,
53,
0;
--bs-color-warning-dark-4: #331c00;
--bs-color-warning-dark-4-rgb:
51,
28,
0;
--bs-color-positive-light-4: rgb(216.75, 255, 240.3);
--bs-color-positive-light-4-rgb:
217,
255,
240;
--bs-color-positive-light-3: rgb(178.5, 255, 225.6);
--bs-color-positive-light-3-rgb:
179,
255,
226;
--bs-color-positive-light-2: rgb(114.75, 255, 201.1);
--bs-color-positive-light-2-rgb:
115,
255,
201;
--bs-color-positive-light-1: rgb(25.5, 255, 166.8);
--bs-color-positive-light-1-rgb:
26,
255,
167;
--bs-color-positive: rgb(0, 216.75, 133.45);
--bs-color-positive-rgb:
0,
217,
133;
--bs-color-positive-dark-1: rgb(0, 153, 94.2);
--bs-color-positive-dark-1-rgb:
0,
153,
94;
--bs-color-positive-dark-2: rgb(0, 102, 62.8);
--bs-color-positive-dark-2-rgb:
0,
102,
63;
--bs-color-positive-dark-3: rgb(0, 63.75, 39.25);
--bs-color-positive-dark-3-rgb:
0,
64,
39;
--bs-color-positive-dark-4: rgb(0, 38.25, 23.55);
--bs-color-positive-dark-4-rgb:
0,
38,
24;
}
:root {
--bs-size-0: 0;
--bs-size-s7: 0.125rem;
--bs-size-s6: 0.25rem;
--bs-size-s5: 0.375rem;
--bs-size-s4: 0.5rem;
--bs-size-s3: 0.625rem;
--bs-size-s2: 0.75rem;
--bs-size-s1: 0.875rem;
--bs-size-m: 1rem;
--bs-size-l1: 1.25rem;
--bs-size-l2: 1.5rem;
--bs-size-l3: 2rem;
--bs-size-l4: 2.5rem;
--bs-size-l5: 3rem;
--bs-size-l6: 4rem;
--bs-size-l7: 5rem;
--bs-z-index-base: 0;
--bs-z-index-overlay: 10;
--bs-z-index-top: 20;
}
:root {
--bs-font-size-1: 0.75rem;
--bs-font-size-2: 0.875rem;
--bs-font-size-3: 1rem;
--bs-font-size-4: 1.125rem;
--bs-font-size-5: 1.25rem;
--bs-font-size-6: 1.5rem;
--bs-font-size-7: 2rem;
--bs-font-size-8: 3rem;
--bs-line-height-1: 1.1667;
--bs-line-height-2: 1.25;
--bs-line-height-3: 1.4;
--bs-line-height-4: 1.5555;
--bs-line-height-5: 1.67;
--bs-font-weight-thin: 100;
--bs-font-weight-extralight: 200;
--bs-font-weight-light: 300;
--bs-font-weight-normal: 400;
--bs-font-weight-medium: 500;
--bs-font-weight-semibold: 600;
--bs-font-weight-bold: 700;
--bs-font-weight-extrabold: 800;
--bs-font-weight-black: 900;
}
:root {
--bs-content-padding-base: var(--bs-size-s1);
--bs-content-padding-l1: var(--bs-size-m);
}
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
html,
body,
#root {
height: 100%;
min-height: stretch;
}
button,
input,
textarea {
font-family: inherit;
font-size: 100%;
line-height: inherit;
margin: 0;
}
button,
input {
overflow: visible;
}
button {
text-transform: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type=button]:-moz-focusring,
[type=reset]:-moz-focusring,
[type=submit]:-moz-focusring {
outline: 1px dotted ButtonText;
}
textarea {
overflow: auto;
}
html,
body,
p,
textarea,
button,
input {
margin: 0;
}
html {
font-family:
-apple-system,
BlinkMacSystemFont,
Roboto,
helvetica,
arial,
sans-serif;
line-height: var(--bs-line-height-4);
-webkit-text-size-adjust: 100%;
}
textarea,
input {
transition:
color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
background-color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
border var(--bs-transition-default-duration) var(--bs-transition-default-easing);
resize: vertical;
}
textarea::placeholder,
input::placeholder {
opacity: 1;
color: var(--bs-color-grayscale-light-1);
font-style: italic;
font-weight: var(--bs-font-weight-normal);
}
[type=text],
textarea {
display: block;
width: 100%;
padding: var(--bs-size-s5) var(--bs-size-s5);
border: var(--bs-size-s7) solid var(--bs-color-grayscale);
border-radius: var(--bs-size-s5);
background-color: var(--bs-color-grayscale-white);
box-shadow:
0 0 calc(var(--bs-size-s5) / 4) rgba(var(--bs-color-grayscale-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s5) / 3) rgba(var(--bs-color-grayscale-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s5) / 2) rgba(var(--bs-color-grayscale-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s5) / 1) rgba(var(--bs-color-grayscale-light-1-rgb), 0.025);
color: var(--bs-color-grayscale);
font: inherit;
}
[type=text]:hover,
textarea:hover {
border-color: var(--bs-color-grayscale-dark-1);
background-color: var(--bs-color-grayscale);
box-shadow:
0 0 calc(var(--bs-size-s2) / 4) rgba(var(--bs-color-brand-1-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s2) / 3) rgba(var(--bs-color-brand-1-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s2) / 2) rgba(var(--bs-color-brand-1-light-1-rgb), 0.025),
0 0 calc(var(--bs-size-s2) / 1) rgba(var(--bs-color-brand-1-light-1-rgb), 0.025);
color: var(--bs-color-grayscale-dark-1);
}
[type=text]:active,
[type=text]:focus,
[type=url]:active,
[type=url]:focus,
textarea:active,
textarea:focus {
border-color: var(--bs-color-grayscale-dark-1);
outline: none;
background-color: var(--bs-color-grayscale-white);
box-shadow:
0 0 calc(var(--bs-size-s6) / 4) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s6) / 3) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s6) / 2) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s6) / 1) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075);
color: var(--bs-color-grayscale-dark-1);
}
[type=text]:invalid,
[type=text][aria-invalid=true],
textarea:invalid,
textarea[aria-invalid=true] {
border-color: var(--bs-color-warning);
background-color: var(--bs-color-grayscale-white);
box-shadow:
0 0 calc(var(--bs-size-s4) / 4) rgba(var(--bs-color-warning-rgb), 0.05),
0 0 calc(var(--bs-size-s4) / 3) rgba(var(--bs-color-warning-rgb), 0.05),
0 0 calc(var(--bs-size-s4) / 2) rgba(var(--bs-color-warning-rgb), 0.05),
0 0 calc(var(--bs-size-s4) / 1) rgba(var(--bs-color-warning-rgb), 0.05);
color: var(--bs-color-warning);
}
[type=text]:disabled,
textarea:disabled {
border-color: var(--bs-color-grayscale-light-2);
background: var(--bs-color-grayscale-light-2);
box-shadow: none;
color: var(--bs-color-grayscale);
cursor: default;
}
:root,
[data-theme] {
background-color: var(--bs-theme-background);
color: var(--bs-theme-text);
}
:root,
[data-theme=default] {
--bs-theme-text: var(--bs-color-grayscale-dark-3);
--bs-theme-background: var(--bs-color-grayscale-white);
}
[data-theme=dark] {
--bs-theme-text: var(--bs-color-grayscale-light-3);
--bs-theme-background: var(--bs-color-grayscale-dark-3);
}
[data-theme=brand-1] {
--bs-theme-text: var(--bs-color-brand-1-dark-2);
--bs-theme-text-dark: var(--bs-color-brand-1-dark-2);
--bs-theme-background: var(--bs-color-brand-1-light-4);
--bs-theme-background-dark: var(--bs-color-brand-1-light-2);
}
[data-theme=brand-2] {
--bs-theme-text: var(--bs-color-brand-2-dark-2);
--bs-theme-background: var(--bs-color-brand-2-light-4);
}
[data-theme=positive] {
--bs-theme-text: var(--bs-color-positive-dark-2);
--bs-theme-text-dark: var(--bs-color-positive-dark-2);
--bs-theme-background: var(--bs-color-positive-light-4);
--bs-theme-background-dark: var(--bs-color-positive-light-2);
}
[data-theme=warning] {
--bs-theme-text: var(--bs-color-warning-dark-2);
--bs-theme-text-dark: var(--bs-color-grayscale-dark-4);
--bs-theme-background: var(--bs-color-warning-light-4);
--bs-theme-background-dark: var(--bs-color-warning-light-1);
}
[data-theme=danger] {
--bs-theme-text: var(--bs-color-danger-dark-1);
--bs-theme-text-dark: var(--bs-color-danger-dark-2);
--bs-theme-background: var(--bs-color-danger-light-4);
--bs-theme-background-dark: var(--bs-color-danger-light-2);
}
[data-theme=grayscale] {
--bs-theme-text: var(--bs-color-grayscale-dark-2);
--bs-theme-background: var(--bs-color-grayscale-light-4);
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
font-family:
-apple-system,
BlinkMacSystemFont,
Roboto,
helvetica,
arial,
sans-serif;
font-weight: var(--bs-font-weight-bold);
text-rendering: optimizeLegibility;
overflow-wrap: break-word;
text-wrap: balance;
hyphens: auto;
}
h1 {
font-size: var(--bs-font-size-7);
line-height: var(--bs-line-height-2);
}
h2 {
font-size: var(--bs-font-size-4);
line-height: var(--bs-line-height-4);
}
body {
font-size: var(--bs-font-size-3);
line-height: var(--bs-line-height-4);
}
.u-fg-brand-1 {
color: var(--bs-color-brand-1);
}
.u-margin-l3-y {
margin-top: var(--bs-size-l3);
margin-bottom: var(--bs-size-l3);
}
.a-button {
--bs-button-border-top-right-radius: var(--bs-button-border-radius);
--bs-button-border-bottom-right-radius: var(--bs-button-border-radius);
--bs-button-border-bottom-left-radius: var(--bs-button-border-radius);
--bs-button-border-top-left-radius: var(--bs-button-border-radius);
--bs-button-transition:
color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
background-color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
border var(--bs-transition-default-duration) var(--bs-transition-default-easing),
box-shadow var(--bs-transition-default-duration) var(--bs-transition-default-easing),
outline-offset var(--bs-transition-default-duration) var(--bs-transition-default-easing);
--bs-button-justify-content: center;
--bs-button-transition:
color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
background-color var(--bs-transition-default-duration) var(--bs-transition-default-easing),
border var(--bs-transition-default-duration) var(--bs-transition-default-easing),
box-shadow var(--bs-transition-default-duration) var(--bs-transition-default-easing),
outline-offset var(--bs-transition-default-duration) var(--bs-transition-default-easing);
--bs-button-justify-content: center;
display: flex;
position: relative;
flex-shrink: 0;
align-items: center;
justify-content: var(--bs-button-justify-content);
min-width: var(--bs-button-min-width);
max-width: 100%;
min-height: var(--bs-button-min-height);
margin: 0;
padding: calc(var(--bs-button-padding-vertical) - var(--bs-button-border-width, 0rem)) calc(var(--bs-button-padding-horizontal) - var(--bs-button-border-width, 0rem));
overflow: visible;
transition: var(--bs-button-transition);
border-width: var(--bs-button-border-width);
border-style: solid;
border-radius: var(--bs-button-border-top-left-radius) var(--bs-button-border-top-right-radius) var(--bs-button-border-bottom-right-radius) var(--bs-button-border-bottom-left-radius);
outline-offset: 0;
font-family: inherit;
font-size: var(--bs-button-font-size);
font-weight: var(--bs-button-font-weight);
text-align: center;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
user-select: none;
-webkit-appearance: none;
touch-action: manipulation;
}
.a-button:hover,
.a-button:focus {
z-index: 1;
outline-width: 0;
text-decoration: none;
}
.a-button:focus-visible {
z-index: 3;
outline: var(--bs-color-brand-2) solid calc(var(--bs-size-s7) + var(--bs-size-s7) / 2);
outline-offset: var(--bs-size-s7);
}
.a-button[aria-expanded=true],
.a-button[aria-pressed=true],
.a-button[aria-selected=true],
.a-button[aria-current] {
z-index: 2;
}
.a-button:disabled,
.a-button[aria-disabled=true],
.a-button:disabled:hover,
.a-button[aria-disabled=true]:hover,
.a-button:disabled:focus,
.a-button[aria-disabled=true]:focus {
cursor: not-allowed;
}
.a-button__avatar,
.a-button__icon {
flex-shrink: 0;
margin-right: var(--bs-size-s2);
margin-left: calc(var(--bs-size-s2) * -1 / 4);
}
.a-button__label + .a-button__icon {
margin-right: calc(var(--bs-size-s2) * -1 / 4);
margin-left: var(--bs-size-s2);
}
.a-button,
.a-button:visited {
color: var(--bs-button-color);
background-color: var(--bs-button-background-color);
border-color: var(--bs-button-border-color);
box-shadow: var(--bs-button-box-shadow);
}
.a-button:hover,
.a-button:focus {
color: var(--bs-button-hover-color);
background-color: var(--bs-button-hover-background-color);
border-color: var(--bs-button-hover-border-color);
box-shadow: var(--bs-button-hover-box-shadow);
}
.a-button:active {
color: var(--bs-button-active-color);
background-color: var(--bs-button-active-background-color);
border-color: var(--bs-button-active-border-color);
box-shadow: var(--bs-button-active-box-shadow);
}
.a-button[aria-expanded=true],
.a-button[aria-pressed=true],
.a-button[aria-selected=true],
.a-button[aria-current] {
color: var(--bs-button-pressed-color);
background-color: var(--bs-button-pressed-background-color);
border-color: var(--bs-button-pressed-border-color);
box-shadow: var(--bs-button-pressed-box-shadow);
}
.a-button:disabled,
.a-button[aria-disabled=true],
.a-button:disabled:hover,
.a-button[aria-disabled=true]:hover,
.a-button:disabled:focus,
.a-button[aria-disabled=true]:focus {
color: var(--bs-button-disabled-color);
background-color: var(--bs-button-disabled-background-color);
border-color: var(--bs-button-disabled-border-color);
box-shadow: var(--bs-button-disabled-box-shadow);
}
:root,
[data-theme=default] {
--bs-button-color: var(--bs-color-grayscale-white);
--bs-button-background-color: var(--bs-color-brand-1-dark-1);
--bs-button-box-shadow: none;
--bs-button-hover-color: var(--bs-color-brand-1-dark-2);
--bs-button-hover-background-color: var(--bs-color-brand-1-light-2);
--bs-button-hover-box-shadow: none;
--bs-button-active-color: var(--bs-color-grayscale-white);
--bs-button-active-background-color: var(--bs-color-brand-1-dark-1);
--bs-button-active-box-shadow: none;
--bs-button-pressed-color: var(--bs-color-grayscale-white);
--bs-button-pressed-background-color: var(--bs-color-brand-1-dark-2);
--bs-button-pressed-box-shadow: none;
--bs-button-disabled-color: var(--bs-color-grayscale);
--bs-button-disabled-background-color: var(--bs-color-grayscale-light-3);
--bs-button-disabled-box-shadow: none;
}
.a-button--secondary,
.a-button--secondary:visited {
color: var(--bs-button--secondary-color);
background-color: var(--bs-button--secondary-background-color);
border-color: var(--bs-button--secondary-border-color);
box-shadow: var(--bs-button--secondary-box-shadow);
}
.a-button--secondary:hover,
.a-button--secondary:focus {
color: var(--bs-button--secondary-hover-color);
background-color: var(--bs-button--secondary-hover-background-color);
border-color: var(--bs-button--secondary-hover-border-color);
box-shadow: var(--bs-button--secondary-hover-box-shadow);
}
.a-button--secondary:active {
color: var(--bs-button--secondary-active-color);
background-color: var(--bs-button--secondary-active-background-color);
border-color: var(--bs-button--secondary-active-border-color);
box-shadow: var(--bs-button--secondary-active-box-shadow);
}
.a-button--secondary[aria-expanded=true],
.a-button--secondary[aria-pressed=true],
.a-button--secondary[aria-selected=true],
.a-button--secondary[aria-current] {
color: var(--bs-button--secondary-pressed-color);
background-color: var(--bs-button--secondary-pressed-background-color);
border-color: var(--bs-button--secondary-pressed-border-color);
box-shadow: var(--bs-button--secondary-pressed-box-shadow);
}
.a-button--secondary:disabled,
.a-button--secondary[aria-disabled=true],
.a-button--secondary:disabled:hover,
.a-button--secondary[aria-disabled=true]:hover,
.a-button--secondary:disabled:focus,
.a-button--secondary[aria-disabled=true]:focus {
color: var(--bs-button--secondary-disabled-color);
background-color: var(--bs-button--secondary-disabled-background-color);
border-color: var(--bs-button--secondary-disabled-border-color);
box-shadow: var(--bs-button--secondary-disabled-box-shadow);
}
:root,
[data-theme=default] {
--bs-button--secondary-color: var(--bs-color-grayscale-dark-2);
--bs-button--secondary-background-color: var(--bs-color-grayscale-light-3);
--bs-button--secondary-box-shadow: none;
--bs-button--secondary-hover-color: var(--bs-color-grayscale-dark-2);
--bs-button--secondary-hover-background-color: var(--bs-color-brand-1-light-2);
--bs-button--secondary-hover-box-shadow: none;
--bs-button--secondary-active-color: var(--bs-color-brand-1-dark-2);
--bs-button--secondary-active-background-color: var(--bs-color-brand-1-light-2);
--bs-button--secondary-active-box-shadow: none;
--bs-button--secondary-pressed-color: var(--bs-color-brand-1-dark-1);
--bs-button--secondary-pressed-background-color: var(--bs-color-brand-1-light-2);
--bs-button--secondary-pressed-box-shadow:
0 0 calc(var(--bs-size-s4) / 4) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 3) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 2) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 1) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075);
--bs-button--secondary-disabled-color: var(--bs-color-grayscale);
--bs-button--secondary-disabled-background-color: var(--bs-color-grayscale-light-3);
--bs-button--secondary-disabled-box-shadow: none;
}
[data-theme=dark] {
--bs-button--secondary-color: var(--bs-color-grayscale-light-2);
--bs-button--secondary-background-color: var(--bs-color-grayscale-dark-1);
--bs-button--secondary-box-shadow: none;
--bs-button--secondary-hover-color: var(--bs-color-grayscale-dark-2);
--bs-button--secondary-hover-background-color: var(--bs-color-brand-1-light-1);
--bs-button--secondary-hover-box-shadow: none;
--bs-button--secondary-active-color: var(--bs-color-brand-1-dark-1);
--bs-button--secondary-active-background-color: var(--bs-color-brand-1-light-1);
--bs-button--secondary-active-box-shadow: none;
--bs-button--secondary-pressed-color: var(--bs-color-brand-1);
--bs-button--secondary-pressed-background-color: var(--bs-color-brand-1-light-1);
--bs-button--secondary-pressed-box-shadow:
0 0 calc(var(--bs-size-s4) / 4) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 3) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 2) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075),
0 0 calc(var(--bs-size-s4) / 1) rgba(var(--bs-color-brand-1-light-1-rgb), 0.075);
--bs-button--secondary-disabled-color: var(--bs-color-grayscale-light-1);
--bs-button--secondary-disabled-background-color: var(--bs-color-grayscale-dark-2);
--bs-button--secondary-disabled-box-shadow: none;
}
.a-button--outline,
.a-button--outline:visited {
color: var(--bs-button--outline-color);
background-color: var(--bs-button--outline-background-color);
border-color: var(--bs-button--outline-border-color);
box-shadow: var(--bs-button--outline-box-shadow);
}
.a-button--outline:hover,
.a-button--outline:focus {
color: var(--bs-button--outline-hover-color);
background-color: var(--bs-button--outline-hover-background-color);
border-color: var(--bs-button--outline-hover-border-color);
box-shadow: var(--bs-button--outline-hover-box-shadow);
}
.a-button--outline:active {
color: var(--bs-button--outline-active-color);
background-color: var(--bs-button--outline-active-background-color);
border-color: var(--bs-button--outline-active-border-color);
box-shadow: var(--bs-button--outline-active-box-shadow);
}
.a-button--outline[aria-expanded=true],
.a-button--outline[aria-pressed=true],
.a-button--outline[aria-selected=true],
.a-button--outline[aria-current] {
color: var(--bs-button--outline-pressed-color);
background-color: var(--bs-button--outline-pressed-background-color);
border-color: var(--bs-button--outline-pressed-border-color);
box-shadow: var(--bs-button--outline-pressed-box-shadow);
}
.a-button--outline:disabled,
.a-button--outline[aria-disabled=true],
.a-button--outline:disabled:hover,
.a-button--outline[aria-disabled=true]:hover,
.a-button--outline:disabled:focus,
.a-button--outline[aria-disabled=true]:focus {
color: var(--bs-button--outline-disabled-color);
background-color: var(--bs-button--outline-disabled-background-color);
border-color: var(--bs-button--outline-disabled-border-color);
box-shadow: var(--bs-button--outline-disabled-box-shadow);
}
:root,
[data-theme=default] {
--bs-button--outline-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-background-color: var(--bs-color-grayscale-white);
--bs-button--outline-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-box-shadow: none;
--bs-button--outline-hover-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-hover-background-color: var(--bs-color-brand-1-light-4);
--bs-button--outline-hover-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-hover-box-shadow: none;
--bs-button--outline-active-color: var(--bs-color-brand-1-dark-1);
--bs-button--outline-active-background-color: var(--bs-color-brand-1-light-4);
--bs-button--outline-active-border-color: var(--bs-color-brand-1-dark-1);
--bs-button--outline-active-box-shadow: none;
--bs-button--outline-pressed-color: var(--bs-color-brand-1-dark-1);
--bs-button--outline-pressed-background-color: var(--bs-color-grayscale-white);
--bs-button--outline-pressed-border-color: var(--bs-color-brand-1-dark-1);
--bs-button--outline-pressed-box-shadow:
0 0 calc(var(--bs-size-m) / 4) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 3) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 2) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 1) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125);
--bs-button--outline-disabled-color: var(--bs-color-grayscale);
--bs-button--outline-disabled-background-color: transparent;
--bs-button--outline-disabled-border-color: var(--bs-color-grayscale);
--bs-button--outline-disabled-box-shadow: none;
}
[data-theme=dark] {
--bs-button--outline-color: var(--bs-color-grayscale-light-2);
--bs-button--outline-background-color: var(--bs-color-grayscale-dark-1);
--bs-button--outline-border-color: var(--bs-color-grayscale-dark-1);
--bs-button--outline-box-shadow: none;
--bs-button--outline-hover-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-hover-background-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-hover-border-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-hover-box-shadow: none;
--bs-button--outline-active-color: var(--bs-color-brand-1-dark-1);
--bs-button--outline-active-background-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-active-border-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-active-box-shadow: none;
--bs-button--outline-pressed-color: var(--bs-color-brand-1);
--bs-button--outline-pressed-background-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-pressed-border-color: var(--bs-color-brand-1-light-1);
--bs-button--outline-pressed-box-shadow:
0 0 calc(var(--bs-size-m) / 4) rgba(var(--bs-color-brand-1-light-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 3) rgba(var(--bs-color-brand-1-light-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 2) rgba(var(--bs-color-brand-1-light-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 1) rgba(var(--bs-color-brand-1-light-1-rgb), 0.125);
--bs-button--outline-disabled-color: var(--bs-color-grayscale-light-1);
--bs-button--outline-disabled-background-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-disabled-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--outline-disabled-box-shadow: none;
}
.a-button--transparent,
.a-button--transparent:visited {
color: var(--bs-button--transparent-color);
background-color: var(--bs-button--transparent-background-color);
border-color: var(--bs-button--transparent-border-color);
box-shadow: var(--bs-button--transparent-box-shadow);
}
.a-button--transparent:hover,
.a-button--transparent:focus {
color: var(--bs-button--transparent-hover-color);
background-color: var(--bs-button--transparent-hover-background-color);
border-color: var(--bs-button--transparent-hover-border-color);
box-shadow: var(--bs-button--transparent-hover-box-shadow);
}
.a-button--transparent:active {
color: var(--bs-button--transparent-active-color);
background-color: var(--bs-button--transparent-active-background-color);
border-color: var(--bs-button--transparent-active-border-color);
box-shadow: var(--bs-button--transparent-active-box-shadow);
}
.a-button--transparent[aria-expanded=true],
.a-button--transparent[aria-pressed=true],
.a-button--transparent[aria-selected=true],
.a-button--transparent[aria-current] {
color: var(--bs-button--transparent-pressed-color);
background-color: var(--bs-button--transparent-pressed-background-color);
border-color: var(--bs-button--transparent-pressed-border-color);
box-shadow: var(--bs-button--transparent-pressed-box-shadow);
}
.a-button--transparent:disabled,
.a-button--transparent[aria-disabled=true],
.a-button--transparent:disabled:hover,
.a-button--transparent[aria-disabled=true]:hover,
.a-button--transparent:disabled:focus,
.a-button--transparent[aria-disabled=true]:focus {
color: var(--bs-button--transparent-disabled-color);
background-color: var(--bs-button--transparent-disabled-background-color);
border-color: var(--bs-button--transparent-disabled-border-color);
box-shadow: var(--bs-button--transparent-disabled-box-shadow);
}
:root,
[data-theme=default] {
--bs-button--transparent-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-background-color: transparent;
--bs-button--transparent-border-color: transparent;
--bs-button--transparent-box-shadow: none;
--bs-button--transparent-hover-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-hover-background-color: var(--bs-color-brand-1-light-4);
--bs-button--transparent-hover-border-color: transparent;
--bs-button--transparent-hover-box-shadow: none;
--bs-button--transparent-active-color: var(--bs-color-brand-1-dark-1);
--bs-button--transparent-active-background-color: var(--bs-color-brand-1-light-4);
--bs-button--transparent-active-border-color: var(--bs-color-brand-1-dark-1);
--bs-button--transparent-active-box-shadow: none;
--bs-button--transparent-pressed-color: var(--bs-color-brand-1-dark-1);
--bs-button--transparent-pressed-background-color: var(--bs-color-grayscale-white);
--bs-button--transparent-pressed-border-color: var(--bs-color-brand-1-dark-1);
--bs-button--transparent-pressed-box-shadow:
0 0 calc(var(--bs-size-m) / 4) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 3) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 2) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 1) rgba(var(--bs-color-brand-1-dark-1-rgb), 0.125);
--bs-button--transparent-disabled-color: var(--bs-color-grayscale);
--bs-button--transparent-disabled-background-color: var(--bs-color-grayscale-light-3);
--bs-button--transparent-disabled-border-color: var(--bs-color-grayscale-light-3);
--bs-button--transparent-disabled-box-shadow: none;
}
[data-theme=dark] {
--bs-button--transparent-color: var(--bs-color-grayscale-light-1);
--bs-button--transparent-background-color: transparent;
--bs-button--transparent-border-color: transparent;
--bs-button--transparent-box-shadow: none;
--bs-button--transparent-hover-color: var(--bs-color-brand-1-light-1);
--bs-button--transparent-hover-background-color: transparent;
--bs-button--transparent-hover-border-color: transparent;
--bs-button--transparent-hover-box-shadow: none;
--bs-button--transparent-active-color: var(--bs-color-brand-1-light-1);
--bs-button--transparent-active-background-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-active-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-active-box-shadow: none;
--bs-button--transparent-pressed-color: var(--bs-color-grayscale-white);
--bs-button--transparent-pressed-background-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-pressed-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-pressed-box-shadow:
0 0 calc(var(--bs-size-m) / 4) rgba(var(--bs-color-grayscale-dark-2-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 3) rgba(var(--bs-color-grayscale-dark-2-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 2) rgba(var(--bs-color-grayscale-dark-2-rgb), 0.125),
0 0 calc(var(--bs-size-m) / 1) rgba(var(--bs-color-grayscale-dark-2-rgb), 0.125);
--bs-button--transparent-disabled-color: var(--bs-color-grayscale-light-1);
--bs-button--transparent-disabled-background-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-disabled-border-color: var(--bs-color-grayscale-dark-2);
--bs-button--transparent-disabled-box-shadow: none;
}
.a-button--tab,
.a-button--tab:visited {
color: var(--bs-button--tab-color);
background-color: var(--bs-button--tab-background-color);
border-color: var(--bs-button--tab-border-color);
box-shadow: var(--bs-button--tab-box-shadow);
}
.a-button--tab:hover,
.a-button--tab:focus {
color: var(--bs-button--tab-hover-color);
background-color: var(--bs-button--tab-hover-background-color);
border-color: var(--bs-button--tab-hover-border-color);
box-shadow: var(--bs-button--tab-hover-box-shadow);
}
.a-button--tab:active {
color: var(--bs-button--tab-active-color);
background-color: var(--bs-button--tab-active-background-color);
border-color: var(--bs-button--tab-active-border-color);
box-shadow: var(--bs-button--tab-active-box-shadow);
}
.a-button--tab[aria-expanded=true],
.a-button--tab[aria-pressed=true],
.a-button--tab[aria-selected=true],
.a-button--tab[aria-current] {
color: var(--bs-button--tab-pressed-color);
background-color: var(--bs-button--tab-pressed-background-color);
border-color: var(--bs-button--tab-pressed-border-color);
box-shadow: var(--bs-button--tab-pressed-box-shadow);
}
.a-button--tab:disabled,
.a-button--tab[aria-disabled=true],
.a-button--tab:disabled:hover,
.a-button--tab[aria-disabled=true]:hover,
.a-button--tab:disabled:focus,
.a-button--tab[aria-disabled=true]:focus {
color: var(--bs-button--tab-disabled-color);
background-color: var(--bs-button--tab-disabled-background-color);
border-color: var(--bs-button--tab-disabled-border-color);
box-shadow: var(--bs-button--tab-disabled-box-shadow);
}
:root,
[data-theme=default] {
--bs-button--tab-color: var(--bs-color-grayscale-dark-1);
--bs-button--tab-background-color: transparent;
--bs-button--tab-box-shadow: none;
--bs-button--tab-icon-color: var(--bs-color-grayscale-dark-2);
--bs-button--tab-hover-color: var(--bs-color-brand-1-dark-1);
--bs-button--tab-hover-background-color: transparent;
--bs-button--tab-hover-box-shadow: none;
--bs-button--tab-active-color: var(--bs-color-brand-1-dark-1);
--bs-button--tab-active-background-color: transparent;
--bs-button--tab-active-box-shadow: none;
--bs-button--tab-pressed-color: var(--bs-color-brand-1-dark-2);
--bs-button--tab-pressed-background-color: transparent;
--bs-button--tab-pressed-box-shadow: none;
--bs-button--tab-disabled-color: var(--bs-color-grayscale);
--bs-button--tab-disabled-background-color: transparent;
--bs-button--tab-disabled-box-shadow: none;
}
[data-theme=dark] {
--bs-button--tab-color: var(--bs-color-grayscale-light-2);
--bs-button--tab-background-color: transparent;
--bs-button--tab-box-shadow: none;
--bs-button--tab-hover-color: var(--bs-color-brand-1-light-2);
--bs-button--tab-hover-background-color: transparent;
--bs-button--tab-hover-box-shadow: none;
--bs-button--tab-active-color: var(--bs-color-brand-1-light-3);
--bs-button--tab-active-background-color: transparent;
--bs-button--tab-active-box-shadow: none;
--bs-button--tab-pressed-color: var(--bs-color-grayscale-light-1);
--bs-button--tab-pressed-background-color: transparent;
--bs-button--tab-pressed-box-shadow: none;
--bs-button--tab-disabled-color: var(--bs-color-grayscale-dark-2);
--bs-button--tab-disabled-background-color: transparent;
--bs-button--tab-disabled-box-shadow: none;
}
.a-button--danger:hover,
.a-button--danger:focus {
color: var(--bs-button--danger-hover-color);
background-color: var(--bs-button--danger-hover-background-color);
border-color: var(--bs-button--danger-hover-border-color);
box-shadow: var(--bs-button--danger-hover-box-shadow);
}
.a-button--danger:active {
color: var(--bs-button--danger-active-color);
background-color: var(--bs-button--danger-active-background-color);
border-color: var(--bs-button--danger-active-border-color);
box-shadow: var(--bs-button--danger-active-box-shadow);
}
.a-button--danger[aria-expanded=true],
.a-button--danger[aria-pressed=true],
.a-button--danger[aria-selected=true],
.a-button--danger[aria-current] {
color: var(--bs-button--danger-pressed-color);
background-color: var(--bs-button--danger-pressed-background-color);
border-color: var(--bs-button--danger-pressed-border-color);
box-shadow: var(--bs-button--danger-pressed-box-shadow);
}
:root,
[data-theme=default] {
--bs-button--danger-hover-color: var(--bs-color-grayscale-white);
--bs-button--danger-hover-background-color: var(--bs-color-danger-dark-1);
--bs-button--danger-hover-border-color: var(--bs-color-danger-dark-1);
--bs-button--danger-hover-box-shadow: none;
--bs-button--danger-active-color: var(--bs-color-danger-dark-1);
--bs-button--danger-active-background-color: var(--bs-color-danger-light-2);
--bs-button--danger-active-border-color: var(--bs-color-danger-light-2);
--bs-button--danger-active-box-shadow: none;
--bs-button--danger-pressed-color: var(--bs-color-danger-dark-2);
--bs-button--danger-pressed-background-color: var(--bs-color-danger-light-2);
--bs-button--danger-pressed-border-color: var(--bs-color-danger-light-2);
--bs-button--danger-pressed-box-shadow: none;
}
.a-button {
--bs-button-padding-vertical: var(--bs-size-s2);
--bs-button-padding-horizontal: var(--bs-size-l1);
--bs-button-border-width: 0rem;
--bs-button-border-radius: var(--bs-size-s4);
--bs-button-min-height: 1em;
--bs-button-min-width: 0;
--bs-button-font-size: var(--bs-font-size-2);
--bs-button-font-weight: var(--bs-font-weight-semibold);
--bs-button-padding-vertical: var(--bs-size-s2);
--bs-button-padding-horizontal: var(--bs-size-l1);
--bs-button-border-width: 0rem;
--bs-button-border-radius: var(--bs-size-s4);
--bs-button-min-height: 1em;
--bs-button-min-width: 0;
--bs-button-font-size: var(--bs-font-size-2);
--bs-button-font-weight: var(--bs-font-weight-semibold);
}
.a-button.a-button--square {
--bs-button-min-width: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s2));
--bs-button-min-height: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s2));
--bs-button-padding-horizontal: var(--bs-size-s2);
--bs-button-padding-vertical: var(--bs-size-s2);
}
.a-button--small {
--bs-button-padding-vertical: var(--bs-size-s4);
--bs-button-padding-horizontal: var(--bs-size-m);
--bs-button-min-height: var(--bs-size-l1);
--bs-button-min-width: var(--bs-size-l2);
--bs-button-font-weight: var(--bs-font-weight-medium);
--bs-button-padding-vertical: var(--bs-size-s4);
--bs-button-padding-horizontal: var(--bs-size-m);
--bs-button-min-height: var(--bs-size-l1);
--bs-button-min-width: var(--bs-size-l2);
--bs-button-font-weight: var(--bs-font-weight-medium);
}
.a-button--small.a-button--square {
--bs-button-min-width: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s4));
--bs-button-min-height: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s4));
--bs-button-padding-horizontal: var(--bs-size-s4);
--bs-button-padding-vertical: var(--bs-size-s4);
}
.a-button--x-small {
--bs-button-padding-vertical: var(--bs-size-s6);
--bs-button-padding-horizontal: var(--bs-size-s2);
--bs-button-font-weight: var(--bs-font-weight-medium);
--bs-button-padding-vertical: var(--bs-size-s6);
--bs-button-padding-horizontal: var(--bs-size-s2);
--bs-button-font-weight: var(--bs-font-weight-medium);
}
.a-button--x-small.a-button--square {
--bs-button-min-width: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s6));
--bs-button-min-height: calc(1em * var(--bs-line-height-5) + 2 * var(--bs-size-s6));
--bs-button-padding-horizontal: var(--bs-size-s6);
--bs-button-padding-vertical: var(--bs-size-s6);
}
.a-button--transparent {
--bs-button-border-width: 0.1875rem;
--bs-button-border-width: 0.1875rem;
}
.a-button--outline {
--bs-button-border-width: 0.1875rem;
--bs-button-border-width: 0.1875rem;
}
.a-button--menu {
--bs-button-border-radius: 0;
--bs-button-justify-content: flex-start;
--bs-button-border-radius: 0;
--bs-button-justify-content: flex-start;
}
.a-button--tab {
--bs-button-border-radius: 0;
--bs-button-border-radius: 0;
}
.a-button--square {
--bs-button-padding-vertical: calc(var(--bs-size-s2) + var(--bs-size-s7));
--bs-button-padding-horizontal: calc(var(--bs-size-s2) + var(--bs-size-s7));
--bs-button-padding-vertical: calc(var(--bs-size-s2) + var(--bs-size-s7));
--bs-button-padding-horizontal: calc(var(--bs-size-s2) + var(--bs-size-s7));
}
.a-button--round {
--bs-button-border-radius: 9999rem;
--bs-button-border-radius: 9999rem;
}
.a-button--tab-container > *:first-child {
margin-left: calc(var(--bs-button-padding-horizontal) * -1);
}
.a-button--tab {
position: relative;
}
.a-button--tab::after,
.a-button--tab:visited::after {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: var(--bs-size-s6);
transition: background-color var(--bs-transition-default-duration) var(--bs-transition-default-easing);
border-radius: 0;
background-color: var(--bs-color-brand-1-light-4);
}
.a-button--tab:hover::after,
.a-button--tab:focus::after {
background-color: var(--bs-color-brand-1-light-4);
}
.a-button--tab:active::after {
background-color: var(--bs-color-brand-1-light-2);
}
.a-button--tab[aria-selected=true]::after,
.a-button--tab[aria-current]::after {
background-color: var(--bs-color-brand-1-dark-1);
}
.a-button--tab:disabled::after,
.a-button--tab[aria-disabled=true]::after,
.a-button--tab:disabled:hover::after,
.a-button--tab[aria-disabled=true]:hover::after,
.a-button--tab:disabled:focus::after,
.a-button--tab[aria-disabled=true]:focus::after {
background-color: var(--bs-color-grayscale);
}
.a-button--tab .a-icon {
color: var(--bs-button--tab-icon-color, currentcolor);
}
.min-h-screen {
min-height: 100vh;
}
.center {
max-width: 100ch;
margin-inline: auto;
}
.flex-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
align-items: flex-end;
}
.flex-grow {
flex-grow: 1;
}
"""
end
end
defmodule RagLive do
use Phoenix.LiveView
@chroma_collection_name "rag-time"
def mount(_params, _session, socket) do
{:ok, collection} =
Chroma.Collection.get_or_create(@chroma_collection_name, %{"hnsw:space" => "l2"})
socket =
socket
|> assign(:ingest_form, to_form(%{"path" => ""}))
|> assign_async(
:chunks,
fn ->
{:ok, %{chunks: Chroma.Collection.count(collection)}}
end,
reset: true
)
|> assign(:query_form, to_form(%{"query" => ""}))
|> assign_async(:response, fn -> {:ok, %{response: %{}}} end)
{:ok, socket}
end
def render(assigns) do
~H"""
<style>
<%= RagLive.Style.styles() %>
</style>
<div class="flex-column center min-h-screen">
<h1 class="u-fg-brand-1 u-margin-l3-y">A RAG for Elixir</h1>
<div class="flex-row">
<div class="flex-grow">
<.async_result :let={chunks} assign={@chunks}>
<:loading>Ingesting...</:loading>
<:failed>Something went wrong...</:failed>
Code chunks in database: <%= chunks %>
</.async_result>
</div>
<button phx-click="reset" class="a-button a-button--secondary a-button--danger">Reset</button>
</div>
<.form for={@ingest_form} phx-submit="ingest" class="flex-row" >
<div class="flex-grow">
<.input type="text" field={@ingest_form[:path]} label="Ingestion Path" />
</div>
<button class="a-button a-button--secondary">Ingest</button>
</.form>
<div class="u-bg-white flex-grow" >
<.async_result :let={response} assign={@response}>
<:loading>Waiting for response...</:loading>
<:failed>Something went wrong...</:failed>
<div class="flow">
<h2 :if={response[:query]}>Query</h2>
<p :if={response[:query]}> <%= response.query %></p>
<h2 :if={response[:response]}>Response</h2>
<p :if={response[:response]}> <%= response.response %></p>
<div :if={response[:context_sources]}>
<h2>Sources</h2>
<ol>
<li :for={source <- response[:context_sources] || []}><%= source %></li>
</ol>
</div>
</div>
</.async_result>
</div>
<.form for={@query_form} phx-submit="query" class="flex-row u-margin-l3-y" >
<div class="flex-grow">
<.input type="textarea" field={@query_form[:query]} label="Query" />
</div>
<button class="a-button a-button--primary">Send</button>
</.form>
</div>
"""
end
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil, id: assigns[:id] || field.id)
|> assign_new(:name, fn -> field.name end)
|> assign_new(:value, fn -> field.value end)
|> input()
end
def input(%{type: "textarea"} = assigns) do
~H"""
<div>
<label for={@id}><%= @label %></label>
<textarea id={@id} name={@name}><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
</div>
"""
end
def input(assigns) do
~H"""
<div>
<label for={@id}><%= @label %></label>
<input
type={@type}
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
/>
</div>
"""
end
def handle_event("ingest", %{"path" => path}, socket) do
{:ok, collection} =
Chroma.Collection.get_or_create(@chroma_collection_name, %{"hnsw:space" => "l2"})
{:noreply,
assign_async(
socket,
:chunks,
fn ->
RagTime.ingest(collection, path)
{:ok, %{chunks: Chroma.Collection.count(collection)}}
end,
reset: true
)}
end
def handle_event("reset", _params, socket) do
{:noreply,
assign_async(
socket,
:chunks,
fn ->
{:ok, collection} =
Chroma.Collection.get_or_create(@chroma_collection_name, %{"hnsw:space" => "l2"})
Chroma.Collection.delete(collection)
{:ok, %{chunks: 0}}
end,
reset: true
)}
end
def handle_event("query", %{"query" => query}, socket) do
{:ok, collection} =
Chroma.Collection.get_or_create(@chroma_collection_name, %{"hnsw:space" => "l2"})
{:noreply,
assign_async(
socket,
:response,
fn -> {:ok, %{response: RagTime.query(collection, query)}} end,
reset: true
)}
end
end
PhoenixPlayground.start(
live: RagLive,
child_specs: [
{Nx.Serving,
serving: RagTime.Serving.build_embedding_serving(),
name: RagTime.EmbeddingServing,
batch_timeout: 100},
{Nx.Serving,
serving: RagTime.Serving.build_llm_serving(), name: RagTime.LLMServing, batch_timeout: 100}
]
)
@joelpaulkoch
Copy link
Author

Here is the accompanying blog post: https://bitcrowd.dev/a-rag-for-elixir-in-elixir

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment