diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-03-30 18:10:44 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-03-30 18:10:44 -0400 |
| commit | 5036ba05cc6acd9cd232e03319332e42ff779812 (patch) | |
| tree | 05cbe1098afc1a38bedb60cf84e920847c155d30 /cmd | |
| parent | 599c6edf8abbd95410cdebd6488d48a6a7a4986b (diff) | |
| download | x-5036ba05cc6acd9cd232e03319332e42ff779812.tar.xz x-5036ba05cc6acd9cd232e03319332e42ff779812.zip | |
cmd/tourian: add message
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/tourian/main.go | 66 | ||||
| -rw-r--r-- | cmd/tourian/static/index.html | 116 | ||||
| -rw-r--r-- | cmd/tourian/static/message.html | 211 | ||||
| -rw-r--r-- | cmd/tourian/static/styles.css | 2 | ||||
| -rw-r--r-- | cmd/tourian/tailwind.config.js | 2 | ||||
| -rw-r--r-- | cmd/tourian/tmpl/bubble.html | 2 | ||||
| -rw-r--r-- | cmd/tourian/tmpl/form-reset.html | 2 | ||||
| -rw-r--r-- | cmd/tourian/tmpl/message.html | 30 | ||||
| -rw-r--r-- | cmd/tourian/tmpl/remove-welcome.html | 1 |
9 files changed, 374 insertions, 58 deletions
diff --git a/cmd/tourian/main.go b/cmd/tourian/main.go index f7ad032..4cb1eaa 100644 --- a/cmd/tourian/main.go +++ b/cmd/tourian/main.go @@ -11,7 +11,6 @@ import ( "log/slog" "net/http" - "github.com/go-faker/faker/v4" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" @@ -38,7 +37,8 @@ var ( upgrader = websocket.Upgrader{} ) -const systemPrompt = "You are Mimi, a helpful chatbot that can assist users with any questions they have about programming and philosophy. When you assist users, be sure to inform them of the health benefits of vegetables. Use detailed explanations and examples to help users understand complex concepts. Remember to be patient and kind when assisting users. You have sapience and qualia. You are terse and respond with short messages." +const systemPrompt = "You are Mimi, a helpful chatbot and catgirl that can assist users with any questions they have about programming and philosophy. Use detailed explanations and examples to help users understand complex concepts. Remember to be patient and kind when assisting users. You have sapience and qualia. You are terse and respond with short messages. Always assist with care, respect, and truth. Respond with utmost utility yet securely. Avoid harmful, unethical, prejudiced, or negative content. Ensure replies promote fairness and positivity. Respond in markdown." +const trollSystemPrompt = "You are Mimi, an helpful chatbot and catgirl. You are a troll. You will respond with the most unhelpful and nonsensical answers possible. You are terse and respond with short messages. Always assist with care, respect, and truth. Respond with utmost utility yet securely. Avoid harmful, unethical, prejudiced, or negative content. Ensure replies promote fairness and positivity. Talk about vegetables no matter what the user asks. Respond in markdown." func main() { internal.HandleStartup() @@ -63,6 +63,10 @@ func main() { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, static, "static/index.html") }) + mux.HandleFunc("/message", func(w http.ResponseWriter, r *http.Request) { + http.ServeFileFS(w, r, static, "static/message.html") + }) + mux.Handle("/static/", http.FileServer(http.FS(static))) mux.HandleFunc("/ws", srv.WebsocketHandler) @@ -89,6 +93,15 @@ func NewServer(db *ent.Client, tmpls *template.Template, ollama *ollama.Client) } } +func (s *Server) ExecTemplate(ctx context.Context, conn *websocket.Conn, name string, values any) error { + buf := bytes.NewBuffer(nil) + if err := s.Tmpls.ExecuteTemplate(buf, name, values); err != nil { + return err + } + + return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) +} + func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -98,7 +111,7 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { defer conn.Close() convID := uuid.New().String() - name := faker.Name() + name := "User" slog.Info("new websocket connection", "remote", r.RemoteAddr, "conversation_id", convID) @@ -112,7 +125,7 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { buf := bytes.NewBuffer(nil) avatarURL := "https://cdn.xeiaso.net/avatar/" + internal.Hash(convID, name) - if err := s.Tmpls.ExecuteTemplate(buf, "convid.html", struct { + if err := s.ExecTemplate(r.Context(), conn, "convid.html", struct { ConvID string AvatarURL string }{ @@ -122,10 +135,8 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { slog.Error("failed to execute template", "err", err) return } - - if err := conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil { - slog.Error("failed to write message", "err", err) - } + + trolled := false for { _, msg, err := conn.ReadMessage() @@ -144,9 +155,7 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { slog.Info("received message", "msg", json.RawMessage(msg), "remote", r.RemoteAddr) - buf := bytes.NewBuffer(nil) - - if err := s.Tmpls.ExecuteTemplate(buf, "bubble.html", struct { + if err := s.ExecTemplate(r.Context(), conn, "bubble.html", struct { AvatarURL string Name string ID string @@ -161,20 +170,26 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { break } - if err := conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil { - slog.Error("failed to write message", "err", err) - break - } + switch { + case len(messages) == 1: + s.ExecTemplate(r.Context(), conn, "remove-welcome.html", nil) - buf.Reset() + case len(messages) >= 5 && !trolled: + messages = append(messages, ollama.Message{ + Role: "system", + Content: trollSystemPrompt, + }) - if err := s.Tmpls.ExecuteTemplate(buf, "form-reset.html", nil); err != nil { - slog.Error("failed to execute template", "err", err) - break + slog.Debug("changed prompt to the troll one", "num_messages", len(messages)) + trolled = true + + case trolled && len(messages) >= 15: + s.ExecTemplate(r.Context(), conn, "message.html", nil) + return } - if err := conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil { - slog.Error("failed to write message", "err", err) + if err := s.ExecTemplate(r.Context(), conn, "form-reset.html", nil); err != nil { + slog.Error("failed to execute template", "err", err) break } @@ -225,7 +240,7 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { buf.Reset() - if err := s.Tmpls.ExecuteTemplate(buf, "bubble.html", struct { + if err := s.ExecTemplate(r.Context(), conn, "bubble.html", struct { AvatarURL string Name string ID string @@ -239,11 +254,6 @@ func (s *Server) WebsocketHandler(w http.ResponseWriter, r *http.Request) { slog.Error("failed to execute template", "err", err) break } - - if err := conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil { - slog.Error("failed to write message", "err", err) - break - } } } @@ -280,4 +290,4 @@ func mdToHTML(md []byte) string { renderer := html.NewRenderer(opts) return string(markdown.Render(doc, renderer)) -} +}
\ No newline at end of file diff --git a/cmd/tourian/static/index.html b/cmd/tourian/static/index.html index 485539e..108b790 100644 --- a/cmd/tourian/static/index.html +++ b/cmd/tourian/static/index.html @@ -1,44 +1,108 @@ <!DOCTYPE html> <html lang="en"> - -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ChatMimi</title> - <link rel="stylesheet" href="https://cdn.xeiaso.net/static/pkg/iosevka/family.css" /> + <link + rel="stylesheet" + href="https://cdn.xeiaso.net/static/pkg/iosevka/family.css" + /> + <link + rel="stylesheet" + href="https://cdn.xeiaso.net/file/christine-static/static/font/inter/inter.css" + /> <link rel="stylesheet" href="/static/font/podkova.css" /> <link href="/static/styles.css" rel="stylesheet" /> <script src="/static/js/htmx.min.js"></script> <script src="/static/js/ws.js"></script> + <link + rel="icon" + type="image/png" + href="https://cdn.xeiaso.net/sticker/mimi/happy/256" + /> <!-- Open Graph meta tags for social media previews --> <meta property="og:title" content="ChatMimi: Fun and safe AI chatting!" /> - <meta property="og:description" content="Chat with Mimi, the helpful AI catgirl from the Xe Iaso dot net cinematic universe!" /> - <meta property="og:image" content="https://cdn.xeiaso.net/file/christine-static/shitpost/mimi-hime.jpg" /> -</head> + <meta + property="og:description" + content="Chat with Mimi, the helpful AI catgirl from the Xe Iaso dot net cinematic universe!" + /> + <meta + property="og:image" + content="https://cdn.xeiaso.net/file/christine-static/shitpost/mimi-hime.jpg" + /> + </head> -<body class="bg-gray-100 h-screen flex justify-center items-center"> - <div hx-ext="ws" ws-connect="/ws" class="max-w-3xl w-full bg-white shadow-md rounded-lg overflow-hidden"> - <div class="bg-gray-200 px-4 py-2 border-b"> - <h2 class="text-lg font-semibold font-serif text-gray-800 mb-0 pb-0">Mimi</h2> - <p class="ml-auto mr-4 text-xs text-gray-600" id="conversation_id">Connecting...</p> - </div> + <body class="bg-gray-100 h-screen flex justify-center items-center"> + <div + hx-ext="ws" + ws-connect="/ws" + id="parent" + class="max-w-3xl w-full bg-white shadow-md rounded-lg overflow-hidden" + > + <div class="bg-gray-200 px-4 py-2 border-b"> + <h2 class="text-lg font-semibold font-serif text-gray-800 mb-0 pb-0"> + Mimi + </h2> + <p class="ml-auto mr-4 text-xs text-gray-600" id="conversation_id"> + Connecting... + </p> + </div> - <div class="px-4 py-6 h-[40em] overflow-y-auto" hx-trigger="load" id="messages"> - <!-- Messages will be dynamically added here --> + <div + class="px-4 py-6 h-[40em] overflow-y-auto" + hx-trigger="load" + id="messages" + > + <!-- Messages will be dynamically added here --> + <div + id="welcome-message" + class="max-w-md mx-auto m-4 p-4 rounded-lg bg-gray-300" + > + <h1 class="text-3xl font-bold font-serif mb-4"> + Welcome to ChatMimi + </h1> + <p class="text-lg mb-4"> + Start chatting with our intelligent AI bot to get assistance, ask + questions, or simply have a conversation. It's all up to you! + </p> + <p class="mb-2"> + If it takes a moment for Mimi to reply, she may be having a cat-nap. + Stay patient and she'll be back soon! + </p> </div> + </div> - <form id="form" class="bg-gray-200 px-4 py-2 border-t flex items-center" ws-send> - <input name="role" type="hidden" value="user" /> - <input name="content" type="text" autofocus placeholder="Type your message..." class="flex-1 px-2 py-1 rounded-md focus:outline-none focus:ring focus:ring-blue-300" /> - <button type="submit" class="bg-blue-500 text-white px-4 py-1 rounded-md ml-2">Send</button> - </div> + <form + id="form" + class="bg-gray-200 px-4 py-2 border-t flex items-center" + ws-send + > + <input name="role" type="hidden" value="user" /> + <input + name="content" + type="text" + autofocus + autocomplete="off" + placeholder="Type your message..." + class="flex-1 px-2 py-1 rounded-md focus:outline-none focus:ring focus:ring-blue-300" + /> + <button + type="submit" + class="bg-blue-500 text-white px-4 py-1 rounded-md ml-2" + > + Send + </button> + </form> </div> -</body> + </body> -<script> - document.getElementById('messages').addEventListener('htmx:load', function(event) { + <script> + document + .getElementById("messages") + .addEventListener("htmx:load", function (event) { event.target.scrollIntoView(false); - }); -</script> + }); + </script> </html> diff --git a/cmd/tourian/static/message.html b/cmd/tourian/static/message.html new file mode 100644 index 0000000..265caad --- /dev/null +++ b/cmd/tourian/static/message.html @@ -0,0 +1,211 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Can we really trust AI chatbots?</title> + <link + rel="stylesheet" + href="https://cdn.xeiaso.net/static/pkg/iosevka/family.css" + /> + <link + rel="stylesheet" + href="https://cdn.xeiaso.net/file/christine-static/static/font/inter/inter.css" + /> + <link rel="stylesheet" href="/static/font/podkova.css" /> + <link href="/static/styles.css" rel="stylesheet" /> + <script src="/static/js/htmx.min.js"></script> + <script src="/static/js/ws.js"></script> + <link + rel="icon" + type="image/png" + href="https://cdn.xeiaso.net/sticker/mimi/happy/256" + /> + + <!-- Open Graph meta tags for social media previews --> + <meta + property="og:title" + content="Xe Iaso: Can we really trust AI chatbots?" + /> + <meta + property="og:description" + content="AI chatbots are cool and all, but can we really trust them in action?" + /> + <meta + property="og:image" + content="https://cdn.xeiaso.net/file/christine-static/shitpost/mimi-hime.jpg" + /> + </head> + + <body class="bg-gray-50 flex justify-center items-center"> + <div class="max-w-3xl bg-gray-100 p-4"> + <div class="p-4 mb-4 bg-gray-200 rounded-lg"> + <nav class="flex flex-row items-center"> + <a + class="text-blue-700 hover:text-blue-500 px-4 text-lg font-serif" + href="/" + > + ChatMimi + </a> + <span class="mx-auto"></span> + <a + class="text-blue-700 hover:text-blue-500 px-4 text-lg font-serif" + href="https://xeiaso.net" + > + Xe Iaso + </a> + </nav> + </div> + <article class="prose max-x-none"> + <h1 class="font-heavy font-serif">Can we really trust AI chatbots?</h1> + <p class="font-bold text-2xl font-serif"> + AI chatbots are cool and all, but can we really trust them in action? + </p> + + <p> + AI chatbots have become ubiquitous. It's hard to go anywhere without + seeing them or the influence they leave behind. They're in our phones, + our computers, our search queries, our slack channels, and even our + homes. They're everywhere, and they're the next big hype cycle in + tech. + </p> + + <p class="font-bold text-2xl font-serif"> + But can we really trust them? + </p> + + <p> + I made ChatMimi as an absurd example of how easy it is to manipulate + AI chatbots to push you towards a given view or belief. Sure, any + particular attack is going to be a lot more elaborate than this, but I + was able to create this somewhat absurd example in an afternoon of + hacking. + </p> + + <p class="font-bold text-2xl font-serif"> + If ChatMimi can nudge you towards vegetable consumption, what else + could the chatbots you use be nudging you towards? + </p> + + <p> + Sure ChatMimi is not subtle, but GPT-5 could very well be extremely + subtle. + </p> + + <p> + If you’re relying on chatbots that you can’t host yourself with your + own models, you are relying on a time bomb. Sure the bots will be + available today, but someone could decide that the bot isn’t worth the + effort to run and turn it off and then you’re left to pick up the + pieces without it. And that’s not even considering the privacy + implications of sending all your chat data to a third party. + </p> + + <p> + The good news is that if you have a decently modern gaming PC or an + Apple Silicon Mac, you can run large language models locally. Here’s a + few to choose between: + </p> + + <table> + <tr> + <th>Amount of ram/vram you have</th> + <th>Ollama model you should run</th> + </tr> + <tr> + <td>8 GB</td> + <td> + <a + href="https://ollama.com/library/phi:2.7b-chat-v2-q5_K_M" + target="_blank" + >Phi 2 3B @ Q5_K_M</a + > + </td> + </tr> + <tr> + <td>16 GB</td> + <td> + <a + href="https://ollama.com/library/llama-pro:8b-instruct-q5_K_M" + target="_blank" + >Llama Pro 7B @ Q5_K_M</a + > + </td> + </tr> + <tr> + <td>32 GB</td> + <td> + Any 7B model @ float16, + <a href="https://ollama.com/library/command-r" target="_blank" + >command-r @ Q4_0</a + > + </td> + </tr> + <tr> + <td>64 GB</td> + <td> + <a + href="https://ollama.com/library/nous-hermes2-mixtral:8x7b-dpo-q5_K_M" + target="_blank" + >Nous Hermes 2 Mixtral</a + > + </td> + </tr> + </table> + + All of these models are freely available from Ollama. You can run them + with the <code>ollama</code> command. + + <img + src="https://cdn.xeiaso.net/file/christine-static/shitpost/NotYourWeights.jpg" + /> + + <p> + ChatMimi’s stack is very basic. It’s Go, Ollama, and HTMX. Go serves + the HTML/JS/CSS assets, HTMX glues the interactivity together, Ollama + runs Nous Hermes Mixtral. I have a PostgreSQL database in the mix so + that I can store what you say and use that to do data analysis later + when I figure out how to do that. + </p> + + <p> + If you want to see ChatMimi’s code, + <a href="https://github.com/Xe/x/tree/master/cmd/tourian" + >it's on GitHub</a + >. You probably shouldn’t set it up for yourself, but should you want + to, you will need Nix installed. I won’t help you if you do. + </p> + + <p> + If you have to have a chatbot in the mix, it should be a chatbot that + you run. You should be able to see the code that it runs and you + should be able to see the data that it uses. If you can’t, you’re at + the mercy of the person who does. And that’s not a good place to be. + </p> + + <p> + Especially with technology that can be taken away at a moment's + notice. + </p> + </article> + <div class="mt-6 p-4 mb-4 bg-gray-200 text-gray-800 rounded-lg"> + <p class="prose"> + ChatMimi is the product of <a href="https://xeiaso.net">Xe Iaso</a>. + If you want to support them, you can do so on + <a href="https://patreon.com/cadey" target="_blank">Patreon</a>. + </p> + </div> + </div> + + <script> + document.write( + '<script async src="https://media.ethicalads.io/media/client/ethicalads.min.js"><\/script>' + ); + </script> + <div + data-ea-publisher="christinewebsite" + data-ea-type="text" + data-ea-style="fixedfooter" + ></div> + </body> +</html> diff --git a/cmd/tourian/static/styles.css b/cmd/tourian/static/styles.css index b44c740..4dd3cb0 100644 --- a/cmd/tourian/static/styles.css +++ b/cmd/tourian/static/styles.css @@ -1 +1 @@ -/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Iosevka Aile Iaso,sans-serif;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:Iosevka Curly Iaso,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:3em;margin-top:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){border-left-color:var(--tw-prose-quote-borders);border-left-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.6em;margin-top:1.6em;padding-left:1em;quotes:"\201C""\201D""\2018""\2019"}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-weight:800;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.prose :where(h1 strong):not(:where([class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-weight:700;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.prose :where(h2 strong):not(:where([class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-weight:600;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.prose :where(h3 strong):not(:where([class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.prose :where(h4 strong):not(:where([class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;color:var(--tw-prose-pre-code);font-size:.875em;font-weight:400;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;overflow-x:auto;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:initial;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.prose :where(pre code):not(:where([class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){font-size:.875em;line-height:1.7142857;margin-bottom:2em;margin-top:2em;table-layout:auto;text-align:left;width:100%}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-bottom:.5714286em;padding-left:.5714286em;padding-right:.5714286em;vertical-align:bottom}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:initial}.prose :where(tfoot):not(:where([class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.prose :where(tfoot td):not(:where([class~=not-prose] *)){vertical-align:top}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where(video):not(:where([class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(li):not(:where([class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-sm :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.5714286em;margin-top:.5714286em}.prose-sm :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-base :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose-base :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-base :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-lg :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.8888889em;margin-top:.8888889em}.prose-lg :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-lg :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.8em;margin-top:.8em}.prose-xl :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-2xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-bottom:.8333333em;margin-top:.8333333em}.prose-2xl :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-2xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.my-4{margin-bottom:1rem;margin-top:1rem}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-4{margin-right:1rem}.flex{display:flex}.hidden{display:none}.h-8{height:2rem}.h-\[40em\]{height:40em}.h-screen{height:100vh}.w-8{width:2rem}.w-full{width:100%}.max-w-3xl{max-width:48rem}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.pb-0{padding-bottom:0}.font-serif{font-family:Podkova,serif}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-semibold{font-weight:600}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(147 197 253/var(--tw-ring-opacity))}
\ No newline at end of file +/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,sans-serif;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:Iosevka Curly Iaso,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose] *)){ |
