├── test ├── test_helper.exs ├── kino_copilot_test.exs └── kino_copilot │ └── code_writer_cell_test.exs ├── .formatter.exs ├── lib ├── kino_copilot.ex ├── kino_copilot │ ├── application.ex │ └── code_writer_cell.ex └── assets │ └── code_writer_cell │ ├── main.js │ └── main.css ├── .gitignore ├── mix.exs ├── README.md └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/kino_copilot_test.exs: -------------------------------------------------------------------------------- 1 | defmodule KinoCopilotTest do 2 | use ExUnit.Case 3 | doctest KinoCopilot 4 | end 5 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /lib/kino_copilot.ex: -------------------------------------------------------------------------------- 1 | defmodule KinoCopilot do 2 | @moduledoc """ 3 | Documentation for `KinoCopilot`. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> KinoCopilot.hello() 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/kino_copilot/application.ex: -------------------------------------------------------------------------------- 1 | defmodule KinoCopilot.Application do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | @impl true 7 | def start(_type, _args) do 8 | Kino.SmartCell.register(KinoCopilot.CodeWriterCell) 9 | 10 | children = [] 11 | opts = [strategy: :one_for_one, name: KinoDB.Supervisor] 12 | Supervisor.start_link(children, opts) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | kino_copilot-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule KinoCopilot.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :kino_copilot, 7 | version: "0.1.2", 8 | description: "Bringing ChatGPT to you livebook", 9 | elixir: "~> 1.15", 10 | start_permanent: Mix.env() == :prod, 11 | deps: deps(), 12 | package: package() 13 | ] 14 | end 15 | 16 | # Run "mix help compile.app" to learn about applications. 17 | def application do 18 | [ 19 | mod: {KinoCopilot.Application, []} 20 | ] 21 | end 22 | 23 | # Run "mix help deps" to learn about dependencies. 24 | defp deps do 25 | [ 26 | {:kino, "~> 0.7"}, 27 | {:openai, "~> 0.5.4"}, 28 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} 29 | ] 30 | end 31 | 32 | def package do 33 | [ 34 | maintainers: ["Thomas Millar"], 35 | licenses: ["Apache-2.0"], 36 | links: %{ 37 | "GitHub" => "https://github.com/thmsmlr/kino_copilot" 38 | } 39 | ] 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/kino_copilot/code_writer_cell_test.exs: -------------------------------------------------------------------------------- 1 | defmodule KinoCopilot.CodeWriterCellTest do 2 | use ExUnit.Case 3 | 4 | alias KinoCopilot.CodeWriterCell 5 | 6 | test "parse_function_call handles triple quote" do 7 | response = "{\n \"code\": \"\"\"\n IO.puts(\"Hello world\") \"\"\"\n}" 8 | 9 | assert "IO.puts(\"Hello world\")" = 10 | CodeWriterCell.parse_function_call(response) |> String.trim() 11 | end 12 | 13 | test "parse_function_call handles extra escaped quotes" do 14 | response = "{\n \"code\": \"\n IO.puts(\\\"Hello world\\\") \"\n}" 15 | 16 | assert "IO.puts(\"Hello world\")" = 17 | CodeWriterCell.parse_function_call(response) |> String.trim() 18 | end 19 | 20 | test "parse_function_call handles extra unescaped quotes" do 21 | response = "{\n \"code\": \"\n IO.puts(\"Hello world\") \"\n}" 22 | 23 | assert "IO.puts(\"Hello world\")" = 24 | CodeWriterCell.parse_function_call(response) |> String.trim() 25 | end 26 | 27 | test "parse_function_call handles proper JSON response" do 28 | response = "{\n \"code\": \"IO.puts(\\\"Hello world\\\")\"\n}" 29 | 30 | assert "IO.puts(\"Hello world\")" = 31 | CodeWriterCell.parse_function_call(response) |> String.trim() 32 | 33 | assert "IO.puts(\"Hello world\")" = 34 | Jason.decode!(response)["code"] |> String.trim() 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kino Copilot 2 | 3 | [](https://hex.pm/packages/kino_copilot) 4 | [](https://hexdocs.pm/kino_copilot/) 5 | 6 | Bringing the power of ChatGPT into [Livebook](https://livebook.dev)! 7 | KinoCopilot is a series of Kino SmartCells which allow you to have an AI Copilot help you write code. 8 | 9 |  10 | 11 | ## Installation 12 | 13 | To bring KinoCopilot to Livebook all you need to do is Mix.install/2: 14 | 15 | ```elixir 16 | Mix.install([ 17 | {:kino_copilot, "~> 0.1.2"} 18 | ]) 19 | ``` 20 | 21 | By default we'll use the `LB_OPENAI_API_KEY` for the API key. 22 | Optionally, however, you can explicitly pass in your API key and specify which model to use. 23 | 24 | ```elixir 25 | Mix.install( 26 | [ 27 | {:kino_copilot, "~> 0.1.2"} 28 | ], 29 | config: [ 30 | kino_copilot: [ 31 | api_key: System.fetch_env!("LB_OPENAI_API_KEY"), 32 | model: "gpt-3.5-turbo" 33 | ] 34 | ] 35 | ) 36 | ``` 37 | 38 | ## Development 39 | 40 | KinoCopilot is still an active development. 41 | If you want to contribute, here are some instructions that will help get you up and running. 42 | 43 | First, you're going to want to install the package from source. 44 | 45 | ```elixir 46 | Mix.install([ 47 | {:kino_copilot, path: "/Users/thomas/code/kino_copilot"}, 48 | ]) 49 | ``` 50 | 51 | Then, if you're modifying any of the front-end bits, you'll want to make sure you have tailwind running in the background, recompiling the CSS. 52 | 53 | ```bash 54 | $ npx tailwindcss -o lib/assets/code_writer_cell/main.css --content lib/assets/code_writer_cell/main.js --watch 55 | ``` 56 | 57 | In the future when we have specialized code writer cells for various languages you'll want to run this command for the specific smart cell you are working on. 58 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.36", "487ea8ef9bdc659f085e6e654f3c3feea1d36ac3943edf9d2ef6c98de9174c13", [:mix], [], "hexpm", "a524e395634bdcf60a616efe77fd79561bec2e930d8b82745df06ab4e844400a"}, 4 | "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, 5 | "hackney": {:hex, :hackney, "1.19.1", "59de4716e985dd2b5cbd4954fa1ae187e2b610a9c4520ffcb0b1653c3d6e5559", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "8aa08234bdefc269995c63c2282cf3cd0e36febe3a6bfab11b610572fdd1cad0"}, 6 | "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, 7 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 8 | "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, 9 | "kino": {:hex, :kino, "0.10.0", "ae598b5ddabc4834585c895a1ee36dcad9d771d86188637c3e28a3f589f17fa1", [:mix], [{:nx, "~> 0.1", [hex: :nx, repo: "hexpm", optional: true]}, {:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "2239ec384fe527f173ceab3d290b45272f095250164f9794c4a65a714683d228"}, 10 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, 11 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, 12 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, 13 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 14 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 15 | "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, 16 | "openai": {:hex, :openai, "0.5.4", "2abc7bc6a72ad1732c16d3f0914aa54f4de14b174a4c70c1b2d7934f0fe2646f", [:mix], [{:httpoison, "~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "72add1d3dcbf3ed7d24ce3acf51e8b2f374b23305b0fc1d5f6acff35c567b267"}, 17 | "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, 18 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, 19 | "table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"}, 20 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 21 | } 22 | -------------------------------------------------------------------------------- /lib/assets/code_writer_cell/main.js: -------------------------------------------------------------------------------- 1 | import * as Vue from "https://cdn.jsdelivr.net/npm/vue@3.2.26/dist/vue.esm-browser.prod.js"; 2 | 3 | export function init(ctx, payload) { 4 | ctx.importCSS("main.css"); 5 | ctx.importCSS("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap"); 6 | 7 | const app = Vue.createApp({ 8 | components: {}, 9 | 10 | template: ` 11 |
{{ payload.errors[0] }}
23 | #{code}\n\n When answering the question, do not change existing modules and functions unless explicitly instructed, favor adding code and changing implementations."
238 | }
239 | ),
240 | %{role: "user", content: user_query}
241 | ]
242 | |> Enum.filter(& &1),
243 | function_call: %{name: func},
244 | functions: [
245 | %{
246 | name: func,
247 | description: "Report back valid elixir code to the user to be run.",
248 | parameters: %{
249 | type: "object",
250 | properties: %{
251 | code: %{
252 | type: "string",
253 | description: "Valid elixir code with newlines properly delimited"
254 | }
255 | }
256 | }
257 | }
258 | ]
259 | ],
260 | config_override
261 | )
262 |
263 | %{
264 | choices: [
265 | %{
266 | "finish_reason" => "stop",
267 | "message" => %{
268 | "function_call" => %{
269 | "arguments" => arguments,
270 | "name" => ^func
271 | }
272 | }
273 | }
274 | ]
275 | } = resp
276 |
277 | IO.inspect(arguments)
278 |
279 | # You would think JSON decoding is the right solution here, but you'd be wrong.
280 | code = parse_function_call(arguments)
281 |
282 | code
283 | |> Code.format_string!()
284 | |> IO.iodata_to_binary()
285 | end
286 |
287 | def parse_function_call(response) do
288 | response
289 | |> String.replace(~r"{\s*\"code\"\s*:\s*\"+", "")
290 | |> String.replace_suffix("}", "")
291 | |> String.replace_trailing("\n", "")
292 | |> String.replace_trailing(" ", "")
293 | |> String.replace_suffix("\"\"\"", "")
294 | |> String.replace_suffix("\"", "")
295 | |> String.replace("\\\"", "\"")
296 | |> String.replace("\\n", "\n")
297 | end
298 | end
299 |
--------------------------------------------------------------------------------
/lib/assets/code_writer_cell/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | */
36 |
37 | html {
38 | line-height: 1.5;
39 | /* 1 */
40 | -webkit-text-size-adjust: 100%;
41 | /* 2 */
42 | -moz-tab-size: 4;
43 | /* 3 */
44 | -o-tab-size: 4;
45 | tab-size: 4;
46 | /* 3 */
47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
48 | /* 4 */
49 | font-feature-settings: normal;
50 | /* 5 */
51 | font-variation-settings: normal;
52 | /* 6 */
53 | }
54 |
55 | /*
56 | 1. Remove the margin in all browsers.
57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
58 | */
59 |
60 | body {
61 | margin: 0;
62 | /* 1 */
63 | line-height: inherit;
64 | /* 2 */
65 | }
66 |
67 | /*
68 | 1. Add the correct height in Firefox.
69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
70 | 3. Ensure horizontal rules are visible by default.
71 | */
72 |
73 | hr {
74 | height: 0;
75 | /* 1 */
76 | color: inherit;
77 | /* 2 */
78 | border-top-width: 1px;
79 | /* 3 */
80 | }
81 |
82 | /*
83 | Add the correct text decoration in Chrome, Edge, and Safari.
84 | */
85 |
86 | abbr:where([title]) {
87 | -webkit-text-decoration: underline dotted;
88 | text-decoration: underline dotted;
89 | }
90 |
91 | /*
92 | Remove the default font size and weight for headings.
93 | */
94 |
95 | h1,
96 | h2,
97 | h3,
98 | h4,
99 | h5,
100 | h6 {
101 | font-size: inherit;
102 | font-weight: inherit;
103 | }
104 |
105 | /*
106 | Reset links to optimize for opt-in styling instead of opt-out.
107 | */
108 |
109 | a {
110 | color: inherit;
111 | text-decoration: inherit;
112 | }
113 |
114 | /*
115 | Add the correct font weight in Edge and Safari.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bolder;
121 | }
122 |
123 | /*
124 | 1. Use the user's configured `mono` font family by default.
125 | 2. Correct the odd `em` font sizing in all browsers.
126 | */
127 |
128 | code,
129 | kbd,
130 | samp,
131 | pre {
132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
133 | /* 1 */
134 | font-size: 1em;
135 | /* 2 */
136 | }
137 |
138 | /*
139 | Add the correct font size in all browsers.
140 | */
141 |
142 | small {
143 | font-size: 80%;
144 | }
145 |
146 | /*
147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
148 | */
149 |
150 | sub,
151 | sup {
152 | font-size: 75%;
153 | line-height: 0;
154 | position: relative;
155 | vertical-align: baseline;
156 | }
157 |
158 | sub {
159 | bottom: -0.25em;
160 | }
161 |
162 | sup {
163 | top: -0.5em;
164 | }
165 |
166 | /*
167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
169 | 3. Remove gaps between table borders by default.
170 | */
171 |
172 | table {
173 | text-indent: 0;
174 | /* 1 */
175 | border-color: inherit;
176 | /* 2 */
177 | border-collapse: collapse;
178 | /* 3 */
179 | }
180 |
181 | /*
182 | 1. Change the font styles in all browsers.
183 | 2. Remove the margin in Firefox and Safari.
184 | 3. Remove default padding in all browsers.
185 | */
186 |
187 | button,
188 | input,
189 | optgroup,
190 | select,
191 | textarea {
192 | font-family: inherit;
193 | /* 1 */
194 | font-feature-settings: inherit;
195 | /* 1 */
196 | font-variation-settings: inherit;
197 | /* 1 */
198 | font-size: 100%;
199 | /* 1 */
200 | font-weight: inherit;
201 | /* 1 */
202 | line-height: inherit;
203 | /* 1 */
204 | color: inherit;
205 | /* 1 */
206 | margin: 0;
207 | /* 2 */
208 | padding: 0;
209 | /* 3 */
210 | }
211 |
212 | /*
213 | Remove the inheritance of text transform in Edge and Firefox.
214 | */
215 |
216 | button,
217 | select {
218 | text-transform: none;
219 | }
220 |
221 | /*
222 | 1. Correct the inability to style clickable types in iOS and Safari.
223 | 2. Remove default button styles.
224 | */
225 |
226 | button,
227 | [type='button'],
228 | [type='reset'],
229 | [type='submit'] {
230 | -webkit-appearance: button;
231 | /* 1 */
232 | background-color: transparent;
233 | /* 2 */
234 | background-image: none;
235 | /* 2 */
236 | }
237 |
238 | /*
239 | Use the modern Firefox focus style for all focusable elements.
240 | */
241 |
242 | :-moz-focusring {
243 | outline: auto;
244 | }
245 |
246 | /*
247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
248 | */
249 |
250 | :-moz-ui-invalid {
251 | box-shadow: none;
252 | }
253 |
254 | /*
255 | Add the correct vertical alignment in Chrome and Firefox.
256 | */
257 |
258 | progress {
259 | vertical-align: baseline;
260 | }
261 |
262 | /*
263 | Correct the cursor style of increment and decrement buttons in Safari.
264 | */
265 |
266 | ::-webkit-inner-spin-button,
267 | ::-webkit-outer-spin-button {
268 | height: auto;
269 | }
270 |
271 | /*
272 | 1. Correct the odd appearance in Chrome and Safari.
273 | 2. Correct the outline style in Safari.
274 | */
275 |
276 | [type='search'] {
277 | -webkit-appearance: textfield;
278 | /* 1 */
279 | outline-offset: -2px;
280 | /* 2 */
281 | }
282 |
283 | /*
284 | Remove the inner padding in Chrome and Safari on macOS.
285 | */
286 |
287 | ::-webkit-search-decoration {
288 | -webkit-appearance: none;
289 | }
290 |
291 | /*
292 | 1. Correct the inability to style clickable types in iOS and Safari.
293 | 2. Change font properties to `inherit` in Safari.
294 | */
295 |
296 | ::-webkit-file-upload-button {
297 | -webkit-appearance: button;
298 | /* 1 */
299 | font: inherit;
300 | /* 2 */
301 | }
302 |
303 | /*
304 | Add the correct display in Chrome and Safari.
305 | */
306 |
307 | summary {
308 | display: list-item;
309 | }
310 |
311 | /*
312 | Removes the default spacing and border for appropriate elements.
313 | */
314 |
315 | blockquote,
316 | dl,
317 | dd,
318 | h1,
319 | h2,
320 | h3,
321 | h4,
322 | h5,
323 | h6,
324 | hr,
325 | figure,
326 | p,
327 | pre {
328 | margin: 0;
329 | }
330 |
331 | fieldset {
332 | margin: 0;
333 | padding: 0;
334 | }
335 |
336 | legend {
337 | padding: 0;
338 | }
339 |
340 | ol,
341 | ul,
342 | menu {
343 | list-style: none;
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | /*
349 | Reset default styling for dialogs.
350 | */
351 |
352 | dialog {
353 | padding: 0;
354 | }
355 |
356 | /*
357 | Prevent resizing textareas horizontally by default.
358 | */
359 |
360 | textarea {
361 | resize: vertical;
362 | }
363 |
364 | /*
365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
366 | 2. Set the default placeholder color to the user's configured gray 400 color.
367 | */
368 |
369 | input::-moz-placeholder, textarea::-moz-placeholder {
370 | opacity: 1;
371 | /* 1 */
372 | color: #9ca3af;
373 | /* 2 */
374 | }
375 |
376 | input::placeholder,
377 | textarea::placeholder {
378 | opacity: 1;
379 | /* 1 */
380 | color: #9ca3af;
381 | /* 2 */
382 | }
383 |
384 | /*
385 | Set the default cursor for buttons.
386 | */
387 |
388 | button,
389 | [role="button"] {
390 | cursor: pointer;
391 | }
392 |
393 | /*
394 | Make sure disabled buttons don't get the pointer cursor.
395 | */
396 |
397 | :disabled {
398 | cursor: default;
399 | }
400 |
401 | /*
402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
404 | This can trigger a poorly considered lint error in some tools but is included by design.
405 | */
406 |
407 | img,
408 | svg,
409 | video,
410 | canvas,
411 | audio,
412 | iframe,
413 | embed,
414 | object {
415 | display: block;
416 | /* 1 */
417 | vertical-align: middle;
418 | /* 2 */
419 | }
420 |
421 | /*
422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
423 | */
424 |
425 | img,
426 | video {
427 | max-width: 100%;
428 | height: auto;
429 | }
430 |
431 | /* Make elements with the HTML hidden attribute stay hidden by default */
432 |
433 | [hidden] {
434 | display: none;
435 | }
436 |
437 | *, ::before, ::after {
438 | --tw-border-spacing-x: 0;
439 | --tw-border-spacing-y: 0;
440 | --tw-translate-x: 0;
441 | --tw-translate-y: 0;
442 | --tw-rotate: 0;
443 | --tw-skew-x: 0;
444 | --tw-skew-y: 0;
445 | --tw-scale-x: 1;
446 | --tw-scale-y: 1;
447 | --tw-pan-x: ;
448 | --tw-pan-y: ;
449 | --tw-pinch-zoom: ;
450 | --tw-scroll-snap-strictness: proximity;
451 | --tw-gradient-from-position: ;
452 | --tw-gradient-via-position: ;
453 | --tw-gradient-to-position: ;
454 | --tw-ordinal: ;
455 | --tw-slashed-zero: ;
456 | --tw-numeric-figure: ;
457 | --tw-numeric-spacing: ;
458 | --tw-numeric-fraction: ;
459 | --tw-ring-inset: ;
460 | --tw-ring-offset-width: 0px;
461 | --tw-ring-offset-color: #fff;
462 | --tw-ring-color: rgb(59 130 246 / 0.5);
463 | --tw-ring-offset-shadow: 0 0 #0000;
464 | --tw-ring-shadow: 0 0 #0000;
465 | --tw-shadow: 0 0 #0000;
466 | --tw-shadow-colored: 0 0 #0000;
467 | --tw-blur: ;
468 | --tw-brightness: ;
469 | --tw-contrast: ;
470 | --tw-grayscale: ;
471 | --tw-hue-rotate: ;
472 | --tw-invert: ;
473 | --tw-saturate: ;
474 | --tw-sepia: ;
475 | --tw-drop-shadow: ;
476 | --tw-backdrop-blur: ;
477 | --tw-backdrop-brightness: ;
478 | --tw-backdrop-contrast: ;
479 | --tw-backdrop-grayscale: ;
480 | --tw-backdrop-hue-rotate: ;
481 | --tw-backdrop-invert: ;
482 | --tw-backdrop-opacity: ;
483 | --tw-backdrop-saturate: ;
484 | --tw-backdrop-sepia: ;
485 | }
486 |
487 | ::backdrop {
488 | --tw-border-spacing-x: 0;
489 | --tw-border-spacing-y: 0;
490 | --tw-translate-x: 0;
491 | --tw-translate-y: 0;
492 | --tw-rotate: 0;
493 | --tw-skew-x: 0;
494 | --tw-skew-y: 0;
495 | --tw-scale-x: 1;
496 | --tw-scale-y: 1;
497 | --tw-pan-x: ;
498 | --tw-pan-y: ;
499 | --tw-pinch-zoom: ;
500 | --tw-scroll-snap-strictness: proximity;
501 | --tw-gradient-from-position: ;
502 | --tw-gradient-via-position: ;
503 | --tw-gradient-to-position: ;
504 | --tw-ordinal: ;
505 | --tw-slashed-zero: ;
506 | --tw-numeric-figure: ;
507 | --tw-numeric-spacing: ;
508 | --tw-numeric-fraction: ;
509 | --tw-ring-inset: ;
510 | --tw-ring-offset-width: 0px;
511 | --tw-ring-offset-color: #fff;
512 | --tw-ring-color: rgb(59 130 246 / 0.5);
513 | --tw-ring-offset-shadow: 0 0 #0000;
514 | --tw-ring-shadow: 0 0 #0000;
515 | --tw-shadow: 0 0 #0000;
516 | --tw-shadow-colored: 0 0 #0000;
517 | --tw-blur: ;
518 | --tw-brightness: ;
519 | --tw-contrast: ;
520 | --tw-grayscale: ;
521 | --tw-hue-rotate: ;
522 | --tw-invert: ;
523 | --tw-saturate: ;
524 | --tw-sepia: ;
525 | --tw-drop-shadow: ;
526 | --tw-backdrop-blur: ;
527 | --tw-backdrop-brightness: ;
528 | --tw-backdrop-contrast: ;
529 | --tw-backdrop-grayscale: ;
530 | --tw-backdrop-hue-rotate: ;
531 | --tw-backdrop-invert: ;
532 | --tw-backdrop-opacity: ;
533 | --tw-backdrop-saturate: ;
534 | --tw-backdrop-sepia: ;
535 | }
536 |
537 | .absolute {
538 | position: absolute;
539 | }
540 |
541 | .relative {
542 | position: relative;
543 | }
544 |
545 | .bottom-0 {
546 | bottom: 0px;
547 | }
548 |
549 | .right-0 {
550 | right: 0px;
551 | }
552 |
553 | .mx-2 {
554 | margin-left: 0.5rem;
555 | margin-right: 0.5rem;
556 | }
557 |
558 | .my-\[3px\] {
559 | margin-top: 3px;
560 | margin-bottom: 3px;
561 | }
562 |
563 | .ml-1 {
564 | margin-left: 0.25rem;
565 | }
566 |
567 | .ml-3 {
568 | margin-left: 0.75rem;
569 | }
570 |
571 | .mt-2 {
572 | margin-top: 0.5rem;
573 | }
574 |
575 | .block {
576 | display: block;
577 | }
578 |
579 | .flex {
580 | display: flex;
581 | }
582 |
583 | .h-4 {
584 | height: 1rem;
585 | }
586 |
587 | .h-5 {
588 | height: 1.25rem;
589 | }
590 |
591 | .w-4 {
592 | width: 1rem;
593 | }
594 |
595 | .w-5 {
596 | width: 1.25rem;
597 | }
598 |
599 | .w-full {
600 | width: 100%;
601 | }
602 |
603 | .flex-1 {
604 | flex: 1 1 0%;
605 | }
606 |
607 | .flex-shrink-0 {
608 | flex-shrink: 0;
609 | }
610 |
611 | @keyframes spin {
612 | to {
613 | transform: rotate(360deg);
614 | }
615 | }
616 |
617 | .animate-spin {
618 | animation: spin 1s linear infinite;
619 | }
620 |
621 | .resize-none {
622 | resize: none;
623 | }
624 |
625 | .list-disc {
626 | list-style-type: disc;
627 | }
628 |
629 | .items-center {
630 | align-items: center;
631 | }
632 |
633 | .justify-center {
634 | justify-content: center;
635 | }
636 |
637 | .gap-3 {
638 | gap: 0.75rem;
639 | }
640 |
641 | .space-y-1 > :not([hidden]) ~ :not([hidden]) {
642 | --tw-space-y-reverse: 0;
643 | margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
644 | margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
645 | }
646 |
647 | .rounded {
648 | border-radius: 0.25rem;
649 | }
650 |
651 | .rounded-md {
652 | border-radius: 0.375rem;
653 | }
654 |
655 | .rounded-bl {
656 | border-bottom-left-radius: 0.25rem;
657 | }
658 |
659 | .rounded-br {
660 | border-bottom-right-radius: 0.25rem;
661 | }
662 |
663 | .border {
664 | border-width: 1px;
665 | }
666 |
667 | .border-gray-200 {
668 | --tw-border-opacity: 1;
669 | border-color: rgb(229 231 235 / var(--tw-border-opacity));
670 | }
671 |
672 | .border-red-400 {
673 | --tw-border-opacity: 1;
674 | border-color: rgb(248 113 113 / var(--tw-border-opacity));
675 | }
676 |
677 | .border-red-300 {
678 | --tw-border-opacity: 1;
679 | border-color: rgb(252 165 165 / var(--tw-border-opacity));
680 | }
681 |
682 | .bg-blue-50 {
683 | --tw-bg-opacity: 1;
684 | background-color: rgb(239 246 255 / var(--tw-bg-opacity));
685 | }
686 |
687 | .bg-gray-50 {
688 | --tw-bg-opacity: 1;
689 | background-color: rgb(249 250 251 / var(--tw-bg-opacity));
690 | }
691 |
692 | .bg-red-50 {
693 | --tw-bg-opacity: 1;
694 | background-color: rgb(254 242 242 / var(--tw-bg-opacity));
695 | }
696 |
697 | .bg-indigo-50 {
698 | --tw-bg-opacity: 1;
699 | background-color: rgb(238 242 255 / var(--tw-bg-opacity));
700 | }
701 |
702 | .p-4 {
703 | padding: 1rem;
704 | }
705 |
706 | .px-2 {
707 | padding-left: 0.5rem;
708 | padding-right: 0.5rem;
709 | }
710 |
711 | .px-3 {
712 | padding-left: 0.75rem;
713 | padding-right: 0.75rem;
714 | }
715 |
716 | .py-2 {
717 | padding-top: 0.5rem;
718 | padding-bottom: 0.5rem;
719 | }
720 |
721 | .py-3 {
722 | padding-top: 0.75rem;
723 | padding-bottom: 0.75rem;
724 | }
725 |
726 | .px-3\.5 {
727 | padding-left: 0.875rem;
728 | padding-right: 0.875rem;
729 | }
730 |
731 | .py-2\.5 {
732 | padding-top: 0.625rem;
733 | padding-bottom: 0.625rem;
734 | }
735 |
736 | .pr-\[50px\] {
737 | padding-right: 50px;
738 | }
739 |
740 | .pl-5 {
741 | padding-left: 1.25rem;
742 | }
743 |
744 | .text-sm {
745 | font-size: 0.875rem;
746 | line-height: 1.25rem;
747 | }
748 |
749 | .font-medium {
750 | font-weight: 500;
751 | }
752 |
753 | .font-semibold {
754 | font-weight: 600;
755 | }
756 |
757 | .text-gray-600 {
758 | --tw-text-opacity: 1;
759 | color: rgb(75 85 99 / var(--tw-text-opacity));
760 | }
761 |
762 | .text-red-400 {
763 | --tw-text-opacity: 1;
764 | color: rgb(248 113 113 / var(--tw-text-opacity));
765 | }
766 |
767 | .text-red-700 {
768 | --tw-text-opacity: 1;
769 | color: rgb(185 28 28 / var(--tw-text-opacity));
770 | }
771 |
772 | .text-red-800 {
773 | --tw-text-opacity: 1;
774 | color: rgb(153 27 27 / var(--tw-text-opacity));
775 | }
776 |
777 | .text-indigo-600 {
778 | --tw-text-opacity: 1;
779 | color: rgb(79 70 229 / var(--tw-text-opacity));
780 | }
781 |
782 | .opacity-25 {
783 | opacity: 0.25;
784 | }
785 |
786 | .opacity-75 {
787 | opacity: 0.75;
788 | }
789 |
790 | .shadow-sm {
791 | --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
792 | --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
793 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
794 | }
795 |
796 | .blur {
797 | --tw-blur: blur(8px);
798 | filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
799 | }
800 |
801 | .hover\:bg-indigo-100:hover {
802 | --tw-bg-opacity: 1;
803 | background-color: rgb(224 231 255 / var(--tw-bg-opacity));
804 | }
805 |
806 | .hover\:enabled\:bg-gray-200:enabled:hover {
807 | --tw-bg-opacity: 1;
808 | background-color: rgb(229 231 235 / var(--tw-bg-opacity));
809 | }
810 |
811 | .disabled\:bg-gray-100:disabled {
812 | --tw-bg-opacity: 1;
813 | background-color: rgb(243 244 246 / var(--tw-bg-opacity));
814 | }
815 |
816 | .disabled\:text-gray-700:disabled {
817 | --tw-text-opacity: 1;
818 | color: rgb(55 65 81 / var(--tw-text-opacity));
819 | }
820 |
821 | .disabled\:ring-gray-200:disabled {
822 | --tw-ring-opacity: 1;
823 | --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity));
824 | }
--------------------------------------------------------------------------------