├── .gitignore
├── LICENSE
├── README.md
├── chat-ollama.png
├── config.edn
├── dist
├── assets
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── manifest.json
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── favicon.ico
├── index.html
├── js
│ └── main.js
├── license.txt
└── style.css
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── manifest.json
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── favicon.ico
├── index.html
└── license.txt
├── shadow-cljs.edn
├── src
├── cljs
│ └── chat_ollama
│ │ ├── core.cljs
│ │ ├── db.cljs
│ │ ├── db
│ │ ├── dialog.cljs
│ │ └── model.cljs
│ │ ├── events.cljs
│ │ ├── fx.cljs
│ │ ├── hooks.cljs
│ │ ├── lib.cljc
│ │ ├── subs.cljs
│ │ ├── utils.cljs
│ │ └── views.cljs
└── css
│ └── style.css
├── tailwind.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist-ssr
12 | *.local
13 |
14 | dist/js/manifest.edn
15 | public/js
16 | public/style.css
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .lsp
22 | .idea
23 | .calva
24 | .clj-kondo/*
25 | .shadow-cljs
26 | .DS_Store
27 | *.suo
28 | *.ntvs*
29 | *.njsproj
30 | *.sln
31 | *.sw?
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jonathan Dale
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chat-Ollama
2 |
3 | > Chat with your [Ollama](https://ollama.ai) models, locally
4 |
5 | Chat-Ollama is a local chat app for your Ollama models in a web browser. With multiple dialogs and message formatting support, you can easily communicate with your ollama models without using the CLI.
6 |
7 | 
8 |
9 | ## Usage
10 |
11 | To use chat-ollama _without_ building from source:
12 |
13 | ```shell
14 | # First, clone and move into the repo.
15 | $ git clone https://github.com/jonathandale/chat-ollama
16 | $ cd chat-ollama
17 | ```
18 |
19 | ```shell
20 | # Then, serve bundled app from /dist
21 | $ yarn serve
22 | # Visit http://localhost:1420
23 | ```
24 | _NB_: If you don't want to install [serve](https://github.com/vercel/serve), consider an [alternative](https://gist.github.com/willurd/5720255).
25 |
26 | ## Development
27 |
28 | Chat-Ollama is built with [ClojureScript](https://clojurescript.org/), using [Shadow CLJS](https://github.com/thheller/shadow-cljs) for building and [Helix](https://github.com/lilactown/helix) for rendering views. Global state management is handled by [Refx](https://github.com/ferdinand-beyer/refx) (a [re-frame](https://github.com/day8/re-frame) for Helix). Tailwind is used for css.
29 |
30 | ### Requirements
31 |
32 | - node.js (v6.0.0+, most recent version preferred)
33 | - npm (comes bundled with node.js) or yarn
34 | - Java SDK (Version 11+, Latest LTS Version recommended)
35 |
36 | ### Installation
37 |
38 | With [yarn](https://yarnpkg.com/en/):
39 |
40 | ```shell
41 | $ yarn install
42 | ```
43 |
44 | ### Development
45 |
46 | Running in development watches source files (including css changes), and uses fast-refresh resulting in near-instant feedback.
47 |
48 | ```shell
49 | $ yarn dev
50 | # Visit http://localhost:1420
51 | ```
52 | ### Release
53 |
54 | Running build compiles javascript and css to the `dist` folder.
55 |
56 | ```shell
57 | $ yarn build
58 | ```
59 |
60 | Serve the built app.
61 |
62 | ```shell
63 | # Serve bundled app from /dist
64 | $ yarn serve
65 | # Visit http://localhost:1420
66 | ```
67 |
68 | ## License
69 |
70 | Distributed under the [MIT License](LICENSE).
71 |
72 | Copyright © 2023 Jonathan Dale
73 |
--------------------------------------------------------------------------------
/chat-ollama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/chat-ollama.png
--------------------------------------------------------------------------------
/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {chat-ollama.lib/defnc clojure.core/defn}}
2 |
--------------------------------------------------------------------------------
/dist/assets/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-144x144.png
--------------------------------------------------------------------------------
/dist/assets/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-192x192.png
--------------------------------------------------------------------------------
/dist/assets/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-36x36.png
--------------------------------------------------------------------------------
/dist/assets/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-48x48.png
--------------------------------------------------------------------------------
/dist/assets/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-72x72.png
--------------------------------------------------------------------------------
/dist/assets/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/android-icon-96x96.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-114x114.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-120x120.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-144x144.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-152x152.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-180x180.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-57x57.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-60x60.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-72x72.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-76x76.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/dist/assets/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/apple-icon.png
--------------------------------------------------------------------------------
/dist/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/dist/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/dist/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/dist/assets/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/favicon-96x96.png
--------------------------------------------------------------------------------
/dist/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "App",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/dist/assets/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/ms-icon-144x144.png
--------------------------------------------------------------------------------
/dist/assets/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/ms-icon-150x150.png
--------------------------------------------------------------------------------
/dist/assets/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/ms-icon-310x310.png
--------------------------------------------------------------------------------
/dist/assets/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/assets/ms-icon-70x70.png
--------------------------------------------------------------------------------
/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/dist/favicon.ico
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Chat Ollama
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/dist/license.txt:
--------------------------------------------------------------------------------
1 | # Licenses
2 |
3 | ## @react-spring/web
4 |
5 | MIT License
6 |
7 | Copyright (c) 2018-present Paul Henschel, react-spring, all contributors
8 |
9 | https://opensource.org/license/mit/
10 |
11 | ## date-fns
12 |
13 | MIT License
14 |
15 | Copyright (c) 2021 Sasha Koss and Lesha Koss https://kossnocorp.mit-license.org
16 |
17 | https://opensource.org/license/mit/
18 |
19 | ## lucide-react
20 |
21 | ISC License
22 |
23 | Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
24 |
25 | https://opensource.org/license/isc-license-txt/
26 |
27 | ## react
28 |
29 | MIT License
30 |
31 | Copyright (c) Facebook, Inc. and its affiliates.
32 |
33 | https://opensource.org/license/mit/
34 |
35 | ## react-dom
36 |
37 | MIT License
38 |
39 | Copyright (c) Facebook, Inc. and its affiliates.
40 |
41 | https://opensource.org/license/mit/
42 |
43 | ## react-hotkeys-hook
44 |
45 | MIT License
46 |
47 | Copyright (c) 2018 Johannes Klauss
48 |
49 | https://opensource.org/license/mit/
50 |
51 | ## react-markdown
52 |
53 | The MIT License (MIT)
54 |
55 | Copyright (c) 2015 Espen Hovlandsdal
56 |
57 | https://opensource.org/license/mit/
58 |
59 | ## react-refresh
60 |
61 | MIT License
62 |
63 | Copyright (c) Facebook, Inc. and its affiliates.
64 |
65 | https://opensource.org/license/mit/
66 |
67 | ## react-syntax-highlighter
68 |
69 | MIT License
70 |
71 | Copyright (c) 2019 Conor Hastings
72 |
73 | https://opensource.org/license/mit/
74 |
75 | ## use-sync-external-store
76 |
77 | MIT License
78 |
79 | Copyright (c) Facebook, Inc. and its affiliates.
80 |
81 | https://opensource.org/license/mit/
82 |
83 | ## lilactown/helix "0.1.10"
84 |
85 | Eclipse Public License - v 2.0
86 |
87 | https://opensource.org/license/epl-2-0/
88 |
89 | ## com.fbeyer/refx "0.0.49"
90 |
91 | MIT License
92 |
93 | Copyright (c) 2022 Ferdinand Beyer
94 |
95 | https://opensource.org/license/mit/
96 |
97 | ## applied-science/js-interop "0.4.2"
98 |
99 | Eclipse Public License - v 2.0
100 |
101 | https://opensource.org/license/epl-2-0/
102 |
103 | ## cljs-bean "1.9.0"
104 |
105 | Eclipse Public License - v 1.0
106 |
107 | https://opensource.org/license/epl-1-0/
108 |
109 | ## funcool/promesa "11.0.678"
110 |
111 | Mozilla Public License Version 2.0
112 |
113 | https://opensource.org/license/mpl-2-0/
114 |
115 | ## com.cognitect/transit-cljs "0.8.280"
116 |
117 | Apache License
118 | Version 2.0, January 2004
119 | http://www.apache.org/licenses/
120 |
--------------------------------------------------------------------------------
/dist/style.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: #CED3DE;
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: #919CB6;
373 | /* 2 */
374 | }
375 |
376 | input::placeholder,
377 | textarea::placeholder {
378 | opacity: 1;
379 | /* 1 */
380 | color: #919CB6;
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 | .pointer-events-none {
538 | pointer-events: none;
539 | }
540 |
541 | .absolute {
542 | position: absolute;
543 | }
544 |
545 | .relative {
546 | position: relative;
547 | }
548 |
549 | .inset-0 {
550 | inset: 0px;
551 | }
552 |
553 | .inset-x-0 {
554 | left: 0px;
555 | right: 0px;
556 | }
557 |
558 | .inset-x-16 {
559 | left: 4rem;
560 | right: 4rem;
561 | }
562 |
563 | .bottom-0 {
564 | bottom: 0px;
565 | }
566 |
567 | .bottom-20 {
568 | bottom: 5rem;
569 | }
570 |
571 | .bottom-9 {
572 | bottom: 2.25rem;
573 | }
574 |
575 | .left-0 {
576 | left: 0px;
577 | }
578 |
579 | .right-0 {
580 | right: 0px;
581 | }
582 |
583 | .right-1 {
584 | right: 0.25rem;
585 | }
586 |
587 | .right-1\.5 {
588 | right: 0.375rem;
589 | }
590 |
591 | .right-3 {
592 | right: 0.75rem;
593 | }
594 |
595 | .right-3\.5 {
596 | right: 0.875rem;
597 | }
598 |
599 | .right-4 {
600 | right: 1rem;
601 | }
602 |
603 | .top-0 {
604 | top: 0px;
605 | }
606 |
607 | .top-1 {
608 | top: 0.25rem;
609 | }
610 |
611 | .top-1\.5 {
612 | top: 0.375rem;
613 | }
614 |
615 | .top-4 {
616 | top: 1rem;
617 | }
618 |
619 | .z-0 {
620 | z-index: 0;
621 | }
622 |
623 | .z-10 {
624 | z-index: 10;
625 | }
626 |
627 | .z-20 {
628 | z-index: 20;
629 | }
630 |
631 | .z-30 {
632 | z-index: 30;
633 | }
634 |
635 | .z-50 {
636 | z-index: 50;
637 | }
638 |
639 | .-m-2 {
640 | margin: -0.5rem;
641 | }
642 |
643 | .mx-auto {
644 | margin-left: auto;
645 | margin-right: auto;
646 | }
647 |
648 | .mb-1 {
649 | margin-bottom: 0.25rem;
650 | }
651 |
652 | .mb-1\.5 {
653 | margin-bottom: 0.375rem;
654 | }
655 |
656 | .mb-10 {
657 | margin-bottom: 2.5rem;
658 | }
659 |
660 | .mb-3 {
661 | margin-bottom: 0.75rem;
662 | }
663 |
664 | .mb-3\.5 {
665 | margin-bottom: 0.875rem;
666 | }
667 |
668 | .mb-4 {
669 | margin-bottom: 1rem;
670 | }
671 |
672 | .ml-2 {
673 | margin-left: 0.5rem;
674 | }
675 |
676 | .ml-6 {
677 | margin-left: 1.5rem;
678 | }
679 |
680 | .mr-3 {
681 | margin-right: 0.75rem;
682 | }
683 |
684 | .mt-12 {
685 | margin-top: 3rem;
686 | }
687 |
688 | .mt-2 {
689 | margin-top: 0.5rem;
690 | }
691 |
692 | .mt-6 {
693 | margin-top: 1.5rem;
694 | }
695 |
696 | .inline {
697 | display: inline;
698 | }
699 |
700 | .flex {
701 | display: flex;
702 | }
703 |
704 | .hidden {
705 | display: none;
706 | }
707 |
708 | .h-10 {
709 | height: 2.5rem;
710 | }
711 |
712 | .h-12 {
713 | height: 3rem;
714 | }
715 |
716 | .h-2 {
717 | height: 0.5rem;
718 | }
719 |
720 | .h-9 {
721 | height: 2.25rem;
722 | }
723 |
724 | .h-\[101px\] {
725 | height: 101px;
726 | }
727 |
728 | .h-\[27px\] {
729 | height: 27px;
730 | }
731 |
732 | .h-fit {
733 | height: -moz-fit-content;
734 | height: fit-content;
735 | }
736 |
737 | .h-full {
738 | height: 100%;
739 | }
740 |
741 | .h-screen {
742 | height: 100vh;
743 | }
744 |
745 | .w-10 {
746 | width: 2.5rem;
747 | }
748 |
749 | .w-\[21px\] {
750 | width: 21px;
751 | }
752 |
753 | .w-\[75\%\] {
754 | width: 75%;
755 | }
756 |
757 | .w-\[77px\] {
758 | width: 77px;
759 | }
760 |
761 | .w-full {
762 | width: 100%;
763 | }
764 |
765 | .w-screen {
766 | width: 100vw;
767 | }
768 |
769 | .min-w-\[250px\] {
770 | min-width: 250px;
771 | }
772 |
773 | .max-w-2xl {
774 | max-width: 42rem;
775 | }
776 |
777 | .max-w-5xl {
778 | max-width: 64rem;
779 | }
780 |
781 | .max-w-6xl {
782 | max-width: 72rem;
783 | }
784 |
785 | .max-w-full {
786 | max-width: 100%;
787 | }
788 |
789 | .shrink-0 {
790 | flex-shrink: 0;
791 | }
792 |
793 | .grow {
794 | flex-grow: 1;
795 | }
796 |
797 | .-translate-y-full {
798 | --tw-translate-y: -100%;
799 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
800 | }
801 |
802 | .scale-125 {
803 | --tw-scale-x: 1.25;
804 | --tw-scale-y: 1.25;
805 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
806 | }
807 |
808 | @keyframes pulse {
809 | 50% {
810 | opacity: .5;
811 | }
812 | }
813 |
814 | .animate-pulse {
815 | animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
816 | }
817 |
818 | .cursor-default {
819 | cursor: default;
820 | }
821 |
822 | .select-none {
823 | -webkit-user-select: none;
824 | -moz-user-select: none;
825 | user-select: none;
826 | }
827 |
828 | .resize-none {
829 | resize: none;
830 | }
831 |
832 | .flex-row-reverse {
833 | flex-direction: row-reverse;
834 | }
835 |
836 | .flex-col {
837 | flex-direction: column;
838 | }
839 |
840 | .items-end {
841 | align-items: flex-end;
842 | }
843 |
844 | .items-center {
845 | align-items: center;
846 | }
847 |
848 | .justify-end {
849 | justify-content: flex-end;
850 | }
851 |
852 | .justify-center {
853 | justify-content: center;
854 | }
855 |
856 | .justify-between {
857 | justify-content: space-between;
858 | }
859 |
860 | .gap-2 {
861 | gap: 0.5rem;
862 | }
863 |
864 | .gap-2\.5 {
865 | gap: 0.625rem;
866 | }
867 |
868 | .gap-3 {
869 | gap: 0.75rem;
870 | }
871 |
872 | .gap-6 {
873 | gap: 1.5rem;
874 | }
875 |
876 | .gap-y-1 {
877 | row-gap: 0.25rem;
878 | }
879 |
880 | .divide-y > :not([hidden]) ~ :not([hidden]) {
881 | --tw-divide-y-reverse: 0;
882 | border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
883 | border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
884 | }
885 |
886 | .divide-gray-400\/50 > :not([hidden]) ~ :not([hidden]) {
887 | border-color: rgb(145 156 182 / 0.5);
888 | }
889 |
890 | .place-self-start {
891 | place-self: start;
892 | }
893 |
894 | .place-self-end {
895 | place-self: end;
896 | }
897 |
898 | .overflow-hidden {
899 | overflow: hidden;
900 | }
901 |
902 | .overflow-scroll {
903 | overflow: scroll;
904 | }
905 |
906 | .truncate {
907 | overflow: hidden;
908 | text-overflow: ellipsis;
909 | white-space: nowrap;
910 | }
911 |
912 | .rounded {
913 | border-radius: 0.25rem;
914 | }
915 |
916 | .rounded-md {
917 | border-radius: 0.375rem;
918 | }
919 |
920 | .rounded-sm {
921 | border-radius: 0.125rem;
922 | }
923 |
924 | .border {
925 | border-width: 1px;
926 | }
927 |
928 | .border-l-4 {
929 | border-left-width: 4px;
930 | }
931 |
932 | .border-cyan-600 {
933 | --tw-border-opacity: 1;
934 | border-color: rgb(8 145 178 / var(--tw-border-opacity));
935 | }
936 |
937 | .border-gray-300\/50 {
938 | border-color: rgb(175 183 202 / 0.5);
939 | }
940 |
941 | .border-gray-300\/60 {
942 | border-color: rgb(175 183 202 / 0.6);
943 | }
944 |
945 | .border-gray-400\/50 {
946 | border-color: rgb(145 156 182 / 0.5);
947 | }
948 |
949 | .border-transparent {
950 | border-color: transparent;
951 | }
952 |
953 | .bg-cyan-600 {
954 | --tw-bg-opacity: 1;
955 | background-color: rgb(8 145 178 / var(--tw-bg-opacity));
956 | }
957 |
958 | .bg-gray-100 {
959 | --tw-bg-opacity: 1;
960 | background-color: rgb(240 241 245 / var(--tw-bg-opacity));
961 | }
962 |
963 | .bg-gray-200 {
964 | --tw-bg-opacity: 1;
965 | background-color: rgb(206 211 222 / var(--tw-bg-opacity));
966 | }
967 |
968 | .bg-gray-200\/75 {
969 | background-color: rgb(206 211 222 / 0.75);
970 | }
971 |
972 | .bg-gray-300\/20 {
973 | background-color: rgb(175 183 202 / 0.2);
974 | }
975 |
976 | .bg-gray-300\/40 {
977 | background-color: rgb(175 183 202 / 0.4);
978 | }
979 |
980 | .bg-gray-50 {
981 | --tw-bg-opacity: 1;
982 | background-color: rgb(243 245 247 / var(--tw-bg-opacity));
983 | }
984 |
985 | .bg-gray-800 {
986 | --tw-bg-opacity: 1;
987 | background-color: rgb(47 54 70 / var(--tw-bg-opacity));
988 | }
989 |
990 | .bg-white {
991 | --tw-bg-opacity: 1;
992 | background-color: rgb(255 255 255 / var(--tw-bg-opacity));
993 | }
994 |
995 | .bg-white\/30 {
996 | background-color: rgb(255 255 255 / 0.3);
997 | }
998 |
999 | .bg-white\/75 {
1000 | background-color: rgb(255 255 255 / 0.75);
1001 | }
1002 |
1003 | .bg-gradient-to-t {
1004 | background-image: linear-gradient(to top, var(--tw-gradient-stops));
1005 | }
1006 |
1007 | .from-transparent {
1008 | --tw-gradient-from: transparent var(--tw-gradient-from-position);
1009 | --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
1010 | --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1011 | }
1012 |
1013 | .from-white {
1014 | --tw-gradient-from: #fff var(--tw-gradient-from-position);
1015 | --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
1016 | --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1017 | }
1018 |
1019 | .to-transparent {
1020 | --tw-gradient-to: transparent var(--tw-gradient-to-position);
1021 | }
1022 |
1023 | .to-white {
1024 | --tw-gradient-to: #fff var(--tw-gradient-to-position);
1025 | }
1026 |
1027 | .fill-gray-900 {
1028 | fill: #1A1E27;
1029 | }
1030 |
1031 | .fill-white {
1032 | fill: #fff;
1033 | }
1034 |
1035 | .p-1 {
1036 | padding: 0.25rem;
1037 | }
1038 |
1039 | .p-1\.5 {
1040 | padding: 0.375rem;
1041 | }
1042 |
1043 | .p-2 {
1044 | padding: 0.5rem;
1045 | }
1046 |
1047 | .p-2\.5 {
1048 | padding: 0.625rem;
1049 | }
1050 |
1051 | .p-4 {
1052 | padding: 1rem;
1053 | }
1054 |
1055 | .p-6 {
1056 | padding: 1.5rem;
1057 | }
1058 |
1059 | .px-20 {
1060 | padding-left: 5rem;
1061 | padding-right: 5rem;
1062 | }
1063 |
1064 | .px-3 {
1065 | padding-left: 0.75rem;
1066 | padding-right: 0.75rem;
1067 | }
1068 |
1069 | .px-4 {
1070 | padding-left: 1rem;
1071 | padding-right: 1rem;
1072 | }
1073 |
1074 | .px-\[4\.5rem\] {
1075 | padding-left: 4.5rem;
1076 | padding-right: 4.5rem;
1077 | }
1078 |
1079 | .py-1 {
1080 | padding-top: 0.25rem;
1081 | padding-bottom: 0.25rem;
1082 | }
1083 |
1084 | .py-1\.5 {
1085 | padding-top: 0.375rem;
1086 | padding-bottom: 0.375rem;
1087 | }
1088 |
1089 | .py-12 {
1090 | padding-top: 3rem;
1091 | padding-bottom: 3rem;
1092 | }
1093 |
1094 | .py-16 {
1095 | padding-top: 4rem;
1096 | padding-bottom: 4rem;
1097 | }
1098 |
1099 | .py-2 {
1100 | padding-top: 0.5rem;
1101 | padding-bottom: 0.5rem;
1102 | }
1103 |
1104 | .py-2\.5 {
1105 | padding-top: 0.625rem;
1106 | padding-bottom: 0.625rem;
1107 | }
1108 |
1109 | .pb-28 {
1110 | padding-bottom: 7rem;
1111 | }
1112 |
1113 | .pb-6 {
1114 | padding-bottom: 1.5rem;
1115 | }
1116 |
1117 | .pl-3 {
1118 | padding-left: 0.75rem;
1119 | }
1120 |
1121 | .pl-3\.5 {
1122 | padding-left: 0.875rem;
1123 | }
1124 |
1125 | .pl-4 {
1126 | padding-left: 1rem;
1127 | }
1128 |
1129 | .pr-10 {
1130 | padding-right: 2.5rem;
1131 | }
1132 |
1133 | .pr-2 {
1134 | padding-right: 0.5rem;
1135 | }
1136 |
1137 | .pr-3 {
1138 | padding-right: 0.75rem;
1139 | }
1140 |
1141 | .pr-3\.5 {
1142 | padding-right: 0.875rem;
1143 | }
1144 |
1145 | .pt-6 {
1146 | padding-top: 1.5rem;
1147 | }
1148 |
1149 | .text-left {
1150 | text-align: left;
1151 | }
1152 |
1153 | .text-center {
1154 | text-align: center;
1155 | }
1156 |
1157 | .font-mono {
1158 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1159 | }
1160 |
1161 | .text-2xl {
1162 | font-size: 1.5rem;
1163 | line-height: 2rem;
1164 | }
1165 |
1166 | .text-3xl {
1167 | font-size: 1.875rem;
1168 | line-height: 2.25rem;
1169 | }
1170 |
1171 | .text-base {
1172 | font-size: 1rem;
1173 | line-height: 1.5rem;
1174 | }
1175 |
1176 | .text-lg {
1177 | font-size: 1.125rem;
1178 | line-height: 1.75rem;
1179 | }
1180 |
1181 | .text-sm {
1182 | font-size: 0.875rem;
1183 | line-height: 1.25rem;
1184 | }
1185 |
1186 | .text-xs {
1187 | font-size: 0.75rem;
1188 | line-height: 1rem;
1189 | }
1190 |
1191 | .font-normal {
1192 | font-weight: 400;
1193 | }
1194 |
1195 | .italic {
1196 | font-style: italic;
1197 | }
1198 |
1199 | .text-gray-300 {
1200 | --tw-text-opacity: 1;
1201 | color: rgb(175 183 202 / var(--tw-text-opacity));
1202 | }
1203 |
1204 | .text-gray-400 {
1205 | --tw-text-opacity: 1;
1206 | color: rgb(145 156 182 / var(--tw-text-opacity));
1207 | }
1208 |
1209 | .text-gray-600 {
1210 | --tw-text-opacity: 1;
1211 | color: rgb(88 101 132 / var(--tw-text-opacity));
1212 | }
1213 |
1214 | .text-gray-600\/80 {
1215 | color: rgb(88 101 132 / 0.8);
1216 | }
1217 |
1218 | .text-gray-700 {
1219 | --tw-text-opacity: 1;
1220 | color: rgb(67 77 101 / var(--tw-text-opacity));
1221 | }
1222 |
1223 | .text-gray-800\/60 {
1224 | color: rgb(47 54 70 / 0.6);
1225 | }
1226 |
1227 | .text-gray-900 {
1228 | --tw-text-opacity: 1;
1229 | color: rgb(26 30 39 / var(--tw-text-opacity));
1230 | }
1231 |
1232 | .text-white {
1233 | --tw-text-opacity: 1;
1234 | color: rgb(255 255 255 / var(--tw-text-opacity));
1235 | }
1236 |
1237 | .placeholder-gray-400\/75::-moz-placeholder {
1238 | color: rgb(145 156 182 / 0.75);
1239 | }
1240 |
1241 | .placeholder-gray-400\/75::placeholder {
1242 | color: rgb(145 156 182 / 0.75);
1243 | }
1244 |
1245 | .opacity-20 {
1246 | opacity: 0.2;
1247 | }
1248 |
1249 | .opacity-25 {
1250 | opacity: 0.25;
1251 | }
1252 |
1253 | .opacity-40 {
1254 | opacity: 0.4;
1255 | }
1256 |
1257 | .opacity-50 {
1258 | opacity: 0.5;
1259 | }
1260 |
1261 | .opacity-60 {
1262 | opacity: 0.6;
1263 | }
1264 |
1265 | .opacity-75 {
1266 | opacity: 0.75;
1267 | }
1268 |
1269 | .shadow {
1270 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
1271 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
1272 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1273 | }
1274 |
1275 | .backdrop-blur {
1276 | --tw-backdrop-blur: blur(8px);
1277 | -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1278 | backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1279 | }
1280 |
1281 | .backdrop-blur-md {
1282 | --tw-backdrop-blur: blur(12px);
1283 | -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1284 | backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1285 | }
1286 |
1287 | @media (prefers-color-scheme: dark) {
1288 | .markdown-body {
1289 | color-scheme: dark;
1290 | --color-prettylights-syntax-comment: #8b949e;
1291 | --color-prettylights-syntax-constant: #79c0ff;
1292 | --color-prettylights-syntax-entity: #d2a8ff;
1293 | --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
1294 | --color-prettylights-syntax-entity-tag: #7ee787;
1295 | --color-prettylights-syntax-keyword: #ff7b72;
1296 | --color-prettylights-syntax-string: #a5d6ff;
1297 | --color-prettylights-syntax-variable: #ffa657;
1298 | --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
1299 | --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
1300 | --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
1301 | --color-prettylights-syntax-carriage-return-text: #f0f6fc;
1302 | --color-prettylights-syntax-carriage-return-bg: #b62324;
1303 | --color-prettylights-syntax-string-regexp: #7ee787;
1304 | --color-prettylights-syntax-markup-list: #f2cc60;
1305 | --color-prettylights-syntax-markup-heading: #1f6feb;
1306 | --color-prettylights-syntax-markup-italic: #c9d1d9;
1307 | --color-prettylights-syntax-markup-bold: #c9d1d9;
1308 | --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
1309 | --color-prettylights-syntax-markup-deleted-bg: #67060c;
1310 | --color-prettylights-syntax-markup-inserted-text: #aff5b4;
1311 | --color-prettylights-syntax-markup-inserted-bg: #033a16;
1312 | --color-prettylights-syntax-markup-changed-text: #ffdfb6;
1313 | --color-prettylights-syntax-markup-changed-bg: #5a1e02;
1314 | --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
1315 | --color-prettylights-syntax-markup-ignored-bg: #1158c7;
1316 | --color-prettylights-syntax-meta-diff-range: #d2a8ff;
1317 | --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
1318 | --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
1319 | --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
1320 | --color-fg-default: #c9d1d9;
1321 | --color-fg-muted: #8b949e;
1322 | --color-fg-subtle: #6e7681;
1323 | --color-canvas-default: #0d1117;
1324 | --color-canvas-subtle: #161b22;
1325 | --color-border-default: #30363d;
1326 | --color-border-muted: #21262d;
1327 | --color-neutral-muted: rgba(110, 118, 129, 0.4);
1328 | --color-accent-fg: #58a6ff;
1329 | --color-accent-emphasis: #1f6feb;
1330 | --color-attention-subtle: rgba(187, 128, 9, 0.15);
1331 | --color-danger-fg: #f85149;
1332 | }
1333 | }
1334 |
1335 | @media (prefers-color-scheme: light) {
1336 | .markdown-body {
1337 | color-scheme: light;
1338 | --color-prettylights-syntax-comment: #6e7781;
1339 | --color-prettylights-syntax-constant: #0550ae;
1340 | --color-prettylights-syntax-entity: #8250df;
1341 | --color-prettylights-syntax-storage-modifier-import: #24292f;
1342 | --color-prettylights-syntax-entity-tag: #116329;
1343 | --color-prettylights-syntax-keyword: #cf222e;
1344 | --color-prettylights-syntax-string: #0a3069;
1345 | --color-prettylights-syntax-variable: #953800;
1346 | --color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
1347 | --color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
1348 | --color-prettylights-syntax-invalid-illegal-bg: #82071e;
1349 | --color-prettylights-syntax-carriage-return-text: #f6f8fa;
1350 | --color-prettylights-syntax-carriage-return-bg: #cf222e;
1351 | --color-prettylights-syntax-string-regexp: #116329;
1352 | --color-prettylights-syntax-markup-list: #3b2300;
1353 | --color-prettylights-syntax-markup-heading: #0550ae;
1354 | --color-prettylights-syntax-markup-italic: #24292f;
1355 | --color-prettylights-syntax-markup-bold: #24292f;
1356 | --color-prettylights-syntax-markup-deleted-text: #82071e;
1357 | --color-prettylights-syntax-markup-deleted-bg: #ffebe9;
1358 | --color-prettylights-syntax-markup-inserted-text: #116329;
1359 | --color-prettylights-syntax-markup-inserted-bg: #dafbe1;
1360 | --color-prettylights-syntax-markup-changed-text: #953800;
1361 | --color-prettylights-syntax-markup-changed-bg: #ffd8b5;
1362 | --color-prettylights-syntax-markup-ignored-text: #eaeef2;
1363 | --color-prettylights-syntax-markup-ignored-bg: #0550ae;
1364 | --color-prettylights-syntax-meta-diff-range: #8250df;
1365 | --color-prettylights-syntax-brackethighlighter-angle: #57606a;
1366 | --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
1367 | --color-prettylights-syntax-constant-other-reference-link: #0a3069;
1368 | --color-fg-default: #24292f;
1369 | --color-fg-muted: #57606a;
1370 | --color-fg-subtle: #6e7781;
1371 | --color-canvas-default: #ffffff;
1372 | --color-canvas-subtle: #f6f8fa;
1373 | --color-border-default: #d0d7de;
1374 | --color-border-muted: hsla(210, 18%, 87%, 1);
1375 | --color-neutral-muted: rgba(175, 184, 193, 0.2);
1376 | --color-accent-fg: #0969da;
1377 | --color-accent-emphasis: #0969da;
1378 | --color-attention-subtle: #fff8c5;
1379 | --color-danger-fg: #cf222e;
1380 | }
1381 | }
1382 |
1383 | .markdown-body {
1384 | -ms-text-size-adjust: 100%;
1385 | -webkit-text-size-adjust: 100%;
1386 | margin: 0;
1387 | color: var(--color-fg-default);
1388 | /* background-color: var(--color-canvas-default); */
1389 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1390 | font-size: 16px;
1391 | line-height: 1.5;
1392 | word-wrap: break-word;
1393 | }
1394 |
1395 | .markdown-body .octicon {
1396 | display: inline-block;
1397 | fill: currentColor;
1398 | vertical-align: text-bottom;
1399 | }
1400 |
1401 | .markdown-body h1:hover .anchor .octicon-link:before,
1402 | .markdown-body h2:hover .anchor .octicon-link:before,
1403 | .markdown-body h3:hover .anchor .octicon-link:before,
1404 | .markdown-body h4:hover .anchor .octicon-link:before,
1405 | .markdown-body h5:hover .anchor .octicon-link:before,
1406 | .markdown-body h6:hover .anchor .octicon-link:before {
1407 | width: 16px;
1408 | height: 16px;
1409 | content: ' ';
1410 | display: inline-block;
1411 | background-color: currentColor;
1412 | -webkit-mask-image: url("data:image/svg+xml,");
1413 | mask-image: url("data:image/svg+xml,");
1414 | }
1415 |
1416 | .markdown-body details,
1417 | .markdown-body figcaption,
1418 | .markdown-body figure {
1419 | display: block;
1420 | }
1421 |
1422 | .markdown-body summary {
1423 | display: list-item;
1424 | }
1425 |
1426 | .markdown-body [hidden] {
1427 | display: none !important;
1428 | }
1429 |
1430 | .markdown-body a {
1431 | background-color: transparent;
1432 | color: var(--color-accent-fg);
1433 | text-decoration: none;
1434 | }
1435 |
1436 | .markdown-body abbr[title] {
1437 | border-bottom: none;
1438 | -webkit-text-decoration: underline dotted;
1439 | text-decoration: underline dotted;
1440 | }
1441 |
1442 | .markdown-body b,
1443 | .markdown-body strong {
1444 | font-weight: var(--base-text-weight-semibold, 600);
1445 | }
1446 |
1447 | .markdown-body dfn {
1448 | font-style: italic;
1449 | }
1450 |
1451 | .markdown-body h1 {
1452 | margin: .67em 0;
1453 | font-weight: var(--base-text-weight-semibold, 600);
1454 | padding-bottom: .3em;
1455 | font-size: 2em;
1456 | border-bottom: 1px solid var(--color-border-muted);
1457 | }
1458 |
1459 | .markdown-body mark {
1460 | background-color: var(--color-attention-subtle);
1461 | color: var(--color-fg-default);
1462 | }
1463 |
1464 | .markdown-body small {
1465 | font-size: 90%;
1466 | }
1467 |
1468 | .markdown-body sub,
1469 | .markdown-body sup {
1470 | font-size: 75%;
1471 | line-height: 0;
1472 | position: relative;
1473 | vertical-align: baseline;
1474 | }
1475 |
1476 | .markdown-body sub {
1477 | bottom: -0.25em;
1478 | }
1479 |
1480 | .markdown-body sup {
1481 | top: -0.5em;
1482 | }
1483 |
1484 | .markdown-body img {
1485 | border-style: none;
1486 | max-width: 100%;
1487 | box-sizing: content-box;
1488 | background-color: var(--color-canvas-default);
1489 | }
1490 |
1491 | .markdown-body code,
1492 | .markdown-body kbd,
1493 | .markdown-body pre,
1494 | .markdown-body samp {
1495 | font-family: monospace;
1496 | font-size: 1em;
1497 | }
1498 |
1499 | .markdown-body figure {
1500 | margin: 1em 40px;
1501 | }
1502 |
1503 | .markdown-body hr {
1504 | box-sizing: content-box;
1505 | overflow: hidden;
1506 | background: transparent;
1507 | border-bottom: 1px solid var(--color-border-muted);
1508 | height: .25em;
1509 | padding: 0;
1510 | margin: 24px 0;
1511 | background-color: var(--color-border-default);
1512 | border: 0;
1513 | }
1514 |
1515 | .markdown-body input {
1516 | font: inherit;
1517 | margin: 0;
1518 | overflow: visible;
1519 | font-family: inherit;
1520 | font-size: inherit;
1521 | line-height: inherit;
1522 | }
1523 |
1524 | .markdown-body [type=button],
1525 | .markdown-body [type=reset],
1526 | .markdown-body [type=submit] {
1527 | -webkit-appearance: button;
1528 | }
1529 |
1530 | .markdown-body [type=checkbox],
1531 | .markdown-body [type=radio] {
1532 | box-sizing: border-box;
1533 | padding: 0;
1534 | }
1535 |
1536 | .markdown-body [type=number]::-webkit-inner-spin-button,
1537 | .markdown-body [type=number]::-webkit-outer-spin-button {
1538 | height: auto;
1539 | }
1540 |
1541 | .markdown-body [type=search]::-webkit-search-cancel-button,
1542 | .markdown-body [type=search]::-webkit-search-decoration {
1543 | -webkit-appearance: none;
1544 | }
1545 |
1546 | .markdown-body ::-webkit-input-placeholder {
1547 | color: inherit;
1548 | opacity: .54;
1549 | }
1550 |
1551 | .markdown-body ::-webkit-file-upload-button {
1552 | -webkit-appearance: button;
1553 | font: inherit;
1554 | }
1555 |
1556 | .markdown-body a:hover {
1557 | text-decoration: underline;
1558 | }
1559 |
1560 | .markdown-body ::-moz-placeholder {
1561 | color: var(--color-fg-subtle);
1562 | opacity: 1;
1563 | }
1564 |
1565 | .markdown-body ::placeholder {
1566 | color: var(--color-fg-subtle);
1567 | opacity: 1;
1568 | }
1569 |
1570 | .markdown-body hr::before {
1571 | display: table;
1572 | content: "";
1573 | }
1574 |
1575 | .markdown-body hr::after {
1576 | display: table;
1577 | clear: both;
1578 | content: "";
1579 | }
1580 |
1581 | .markdown-body table {
1582 | border-spacing: 0;
1583 | border-collapse: collapse;
1584 | display: block;
1585 | width: -moz-max-content;
1586 | width: max-content;
1587 | max-width: 100%;
1588 | overflow: auto;
1589 | }
1590 |
1591 | .markdown-body td,
1592 | .markdown-body th {
1593 | padding: 0;
1594 | }
1595 |
1596 | .markdown-body details summary {
1597 | cursor: pointer;
1598 | }
1599 |
1600 | .markdown-body details:not([open])>*:not(summary) {
1601 | display: none !important;
1602 | }
1603 |
1604 | .markdown-body a:focus,
1605 | .markdown-body [role=button]:focus,
1606 | .markdown-body input[type=radio]:focus,
1607 | .markdown-body input[type=checkbox]:focus {
1608 | outline: 2px solid var(--color-accent-fg);
1609 | outline-offset: -2px;
1610 | box-shadow: none;
1611 | }
1612 |
1613 | .markdown-body a:focus:not(:focus-visible),
1614 | .markdown-body [role=button]:focus:not(:focus-visible),
1615 | .markdown-body input[type=radio]:focus:not(:focus-visible),
1616 | .markdown-body input[type=checkbox]:focus:not(:focus-visible) {
1617 | outline: solid 1px transparent;
1618 | }
1619 |
1620 | .markdown-body a:focus-visible,
1621 | .markdown-body [role=button]:focus-visible,
1622 | .markdown-body input[type=radio]:focus-visible,
1623 | .markdown-body input[type=checkbox]:focus-visible {
1624 | outline: 2px solid var(--color-accent-fg);
1625 | outline-offset: -2px;
1626 | box-shadow: none;
1627 | }
1628 |
1629 | .markdown-body a:not([class]):focus,
1630 | .markdown-body a:not([class]):focus-visible,
1631 | .markdown-body input[type=radio]:focus,
1632 | .markdown-body input[type=radio]:focus-visible,
1633 | .markdown-body input[type=checkbox]:focus,
1634 | .markdown-body input[type=checkbox]:focus-visible {
1635 | outline-offset: 0;
1636 | }
1637 |
1638 | .markdown-body kbd {
1639 | display: inline-block;
1640 | padding: 3px 5px;
1641 | font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
1642 | line-height: 10px;
1643 | color: var(--color-fg-default);
1644 | vertical-align: middle;
1645 | background-color: var(--color-canvas-subtle);
1646 | border: solid 1px var(--color-neutral-muted);
1647 | border-bottom-color: var(--color-neutral-muted);
1648 | border-radius: 6px;
1649 | box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
1650 | }
1651 |
1652 | .markdown-body h1,
1653 | .markdown-body h2,
1654 | .markdown-body h3,
1655 | .markdown-body h4,
1656 | .markdown-body h5,
1657 | .markdown-body h6 {
1658 | margin-top: 24px;
1659 | margin-bottom: 16px;
1660 | font-weight: var(--base-text-weight-semibold, 600);
1661 | line-height: 1.25;
1662 | }
1663 |
1664 | .markdown-body h2 {
1665 | font-weight: var(--base-text-weight-semibold, 600);
1666 | padding-bottom: .3em;
1667 | font-size: 1.5em;
1668 | border-bottom: 1px solid var(--color-border-muted);
1669 | }
1670 |
1671 | .markdown-body h3 {
1672 | font-weight: var(--base-text-weight-semibold, 600);
1673 | font-size: 1.25em;
1674 | }
1675 |
1676 | .markdown-body h4 {
1677 | font-weight: var(--base-text-weight-semibold, 600);
1678 | font-size: 1em;
1679 | }
1680 |
1681 | .markdown-body h5 {
1682 | font-weight: var(--base-text-weight-semibold, 600);
1683 | font-size: .875em;
1684 | }
1685 |
1686 | .markdown-body h6 {
1687 | font-weight: var(--base-text-weight-semibold, 600);
1688 | font-size: .85em;
1689 | color: var(--color-fg-muted);
1690 | }
1691 |
1692 | .markdown-body p {
1693 | margin-top: 0;
1694 | margin-bottom: 10px;
1695 | }
1696 |
1697 | .markdown-body blockquote {
1698 | margin: 0;
1699 | padding: 0 1em;
1700 | color: var(--color-fg-muted);
1701 | border-left: .25em solid var(--color-border-default);
1702 | }
1703 |
1704 | .markdown-body ul,
1705 | .markdown-body ol {
1706 | margin-top: 0;
1707 | margin-bottom: 0;
1708 | padding-left: 1rem;
1709 | }
1710 |
1711 | .markdown-body ul {
1712 | list-style: disc;
1713 | }
1714 |
1715 | .markdown-body ol {
1716 | list-style: decimal;
1717 | }
1718 |
1719 | .markdown-body ol ol,
1720 | .markdown-body ul ol {
1721 | list-style: lower-roman;
1722 | }
1723 |
1724 | .markdown-body ul ul ol,
1725 | .markdown-body ul ol ol,
1726 | .markdown-body ol ul ol,
1727 | .markdown-body ol ol ol {
1728 | list-style: lower-alpha;
1729 | }
1730 |
1731 | .markdown-body dd {
1732 | margin-left: 0;
1733 | }
1734 |
1735 | .markdown-body tt,
1736 | .markdown-body code,
1737 | .markdown-body samp {
1738 | font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
1739 | font-size: 12px;
1740 | }
1741 |
1742 | .markdown-body .octicon {
1743 | display: inline-block;
1744 | overflow: visible !important;
1745 | vertical-align: text-bottom;
1746 | fill: currentColor;
1747 | }
1748 |
1749 | .markdown-body input::-webkit-outer-spin-button,
1750 | .markdown-body input::-webkit-inner-spin-button {
1751 | margin: 0;
1752 | -webkit-appearance: none;
1753 | appearance: none;
1754 | }
1755 |
1756 | .markdown-body::before {
1757 | display: table;
1758 | content: "";
1759 | }
1760 |
1761 | .markdown-body::after {
1762 | display: table;
1763 | clear: both;
1764 | content: "";
1765 | }
1766 |
1767 | .markdown-body>*:first-child {
1768 | margin-top: 0 !important;
1769 | }
1770 |
1771 | .markdown-body>*:last-child {
1772 | margin-bottom: 0 !important;
1773 | }
1774 |
1775 | .markdown-body a:not([href]) {
1776 | color: inherit;
1777 | text-decoration: none;
1778 | }
1779 |
1780 | .markdown-body .absent {
1781 | color: var(--color-danger-fg);
1782 | }
1783 |
1784 | .markdown-body .anchor {
1785 | float: left;
1786 | padding-right: 4px;
1787 | margin-left: -20px;
1788 | line-height: 1;
1789 | }
1790 |
1791 | .markdown-body .anchor:focus {
1792 | outline: none;
1793 | }
1794 |
1795 | .markdown-body p,
1796 | .markdown-body blockquote,
1797 | .markdown-body ul,
1798 | .markdown-body ol,
1799 | .markdown-body dl,
1800 | .markdown-body table,
1801 | .markdown-body>pre,
1802 | .markdown-body details {
1803 | margin-top: 0;
1804 | margin-bottom: 16px;
1805 | }
1806 |
1807 | .markdown-body blockquote>:first-child {
1808 | margin-top: 0;
1809 | }
1810 |
1811 | .markdown-body blockquote>:last-child {
1812 | margin-bottom: 0;
1813 | }
1814 |
1815 | .markdown-body h1 .octicon-link,
1816 | .markdown-body h2 .octicon-link,
1817 | .markdown-body h3 .octicon-link,
1818 | .markdown-body h4 .octicon-link,
1819 | .markdown-body h5 .octicon-link,
1820 | .markdown-body h6 .octicon-link {
1821 | color: var(--color-fg-default);
1822 | vertical-align: middle;
1823 | visibility: hidden;
1824 | }
1825 |
1826 | .markdown-body h1:hover .anchor,
1827 | .markdown-body h2:hover .anchor,
1828 | .markdown-body h3:hover .anchor,
1829 | .markdown-body h4:hover .anchor,
1830 | .markdown-body h5:hover .anchor,
1831 | .markdown-body h6:hover .anchor {
1832 | text-decoration: none;
1833 | }
1834 |
1835 | .markdown-body h1:hover .anchor .octicon-link,
1836 | .markdown-body h2:hover .anchor .octicon-link,
1837 | .markdown-body h3:hover .anchor .octicon-link,
1838 | .markdown-body h4:hover .anchor .octicon-link,
1839 | .markdown-body h5:hover .anchor .octicon-link,
1840 | .markdown-body h6:hover .anchor .octicon-link {
1841 | visibility: visible;
1842 | }
1843 |
1844 | .markdown-body h1 tt,
1845 | .markdown-body h1 code,
1846 | .markdown-body h2 tt,
1847 | .markdown-body h2 code,
1848 | .markdown-body h3 tt,
1849 | .markdown-body h3 code,
1850 | .markdown-body h4 tt,
1851 | .markdown-body h4 code,
1852 | .markdown-body h5 tt,
1853 | .markdown-body h5 code,
1854 | .markdown-body h6 tt,
1855 | .markdown-body h6 code {
1856 | padding: 0 .2em;
1857 | font-size: inherit;
1858 | }
1859 |
1860 | .markdown-body summary h1,
1861 | .markdown-body summary h2,
1862 | .markdown-body summary h3,
1863 | .markdown-body summary h4,
1864 | .markdown-body summary h5,
1865 | .markdown-body summary h6 {
1866 | display: inline-block;
1867 | }
1868 |
1869 | .markdown-body summary h1 .anchor,
1870 | .markdown-body summary h2 .anchor,
1871 | .markdown-body summary h3 .anchor,
1872 | .markdown-body summary h4 .anchor,
1873 | .markdown-body summary h5 .anchor,
1874 | .markdown-body summary h6 .anchor {
1875 | margin-left: -40px;
1876 | }
1877 |
1878 | .markdown-body summary h1,
1879 | .markdown-body summary h2 {
1880 | padding-bottom: 0;
1881 | border-bottom: 0;
1882 | }
1883 |
1884 | .markdown-body ul.no-list,
1885 | .markdown-body ol.no-list {
1886 | padding: 0;
1887 | list-style: none;
1888 | }
1889 |
1890 | .markdown-body ol[type=a] {
1891 | list-style: lower-alpha;
1892 | }
1893 |
1894 | .markdown-body ol[type=A] {
1895 | list-style: upper-alpha;
1896 | }
1897 |
1898 | .markdown-body ol[type=i] {
1899 | list-style: lower-roman;
1900 | }
1901 |
1902 | .markdown-body ol[type=I] {
1903 | list-style: upper-roman;
1904 | }
1905 |
1906 | .markdown-body ol[type="1"] {
1907 | list-style: decimal;
1908 | }
1909 |
1910 | .markdown-body div>ol:not([type]) {
1911 | list-style: decimal;
1912 | }
1913 |
1914 | .markdown-body ul ul,
1915 | .markdown-body ul ol,
1916 | .markdown-body ol ol,
1917 | .markdown-body ol ul {
1918 | margin-top: 0;
1919 | margin-bottom: 0;
1920 | }
1921 |
1922 | .markdown-body li>p {
1923 | margin-top: 16px;
1924 | }
1925 |
1926 | .markdown-body li+li {
1927 | margin-top: .25em;
1928 | }
1929 |
1930 | .markdown-body dl {
1931 | padding: 0;
1932 | }
1933 |
1934 | .markdown-body dl dt {
1935 | padding: 0;
1936 | margin-top: 16px;
1937 | font-size: 1em;
1938 | font-style: italic;
1939 | font-weight: var(--base-text-weight-semibold, 600);
1940 | }
1941 |
1942 | .markdown-body dl dd {
1943 | padding: 0 16px;
1944 | margin-bottom: 16px;
1945 | }
1946 |
1947 | .markdown-body table th {
1948 | font-weight: var(--base-text-weight-semibold, 600);
1949 | }
1950 |
1951 | .markdown-body table th,
1952 | .markdown-body table td {
1953 | padding: 6px 13px;
1954 | border: 1px solid var(--color-border-default);
1955 | }
1956 |
1957 | .markdown-body table tr {
1958 | background-color: var(--color-canvas-default);
1959 | border-top: 1px solid var(--color-border-muted);
1960 | }
1961 |
1962 | .markdown-body table tr:nth-child(2n) {
1963 | background-color: var(--color-canvas-subtle);
1964 | }
1965 |
1966 | .markdown-body table img {
1967 | background-color: transparent;
1968 | }
1969 |
1970 | .markdown-body img[align=right] {
1971 | padding-left: 20px;
1972 | }
1973 |
1974 | .markdown-body img[align=left] {
1975 | padding-right: 20px;
1976 | }
1977 |
1978 | .markdown-body .emoji {
1979 | max-width: none;
1980 | vertical-align: text-top;
1981 | background-color: transparent;
1982 | }
1983 |
1984 | .markdown-body span.frame {
1985 | display: block;
1986 | overflow: hidden;
1987 | }
1988 |
1989 | .markdown-body span.frame>span {
1990 | display: block;
1991 | float: left;
1992 | width: auto;
1993 | padding: 7px;
1994 | margin: 13px 0 0;
1995 | overflow: hidden;
1996 | border: 1px solid var(--color-border-default);
1997 | }
1998 |
1999 | .markdown-body span.frame span img {
2000 | display: block;
2001 | float: left;
2002 | }
2003 |
2004 | .markdown-body span.frame span span {
2005 | display: block;
2006 | padding: 5px 0 0;
2007 | clear: both;
2008 | color: var(--color-fg-default);
2009 | }
2010 |
2011 | .markdown-body span.align-center {
2012 | display: block;
2013 | overflow: hidden;
2014 | clear: both;
2015 | }
2016 |
2017 | .markdown-body span.align-center>span {
2018 | display: block;
2019 | margin: 13px auto 0;
2020 | overflow: hidden;
2021 | text-align: center;
2022 | }
2023 |
2024 | .markdown-body span.align-center span img {
2025 | margin: 0 auto;
2026 | text-align: center;
2027 | }
2028 |
2029 | .markdown-body span.align-right {
2030 | display: block;
2031 | overflow: hidden;
2032 | clear: both;
2033 | }
2034 |
2035 | .markdown-body span.align-right>span {
2036 | display: block;
2037 | margin: 13px 0 0;
2038 | overflow: hidden;
2039 | text-align: right;
2040 | }
2041 |
2042 | .markdown-body span.align-right span img {
2043 | margin: 0;
2044 | text-align: right;
2045 | }
2046 |
2047 | .markdown-body span.float-left {
2048 | display: block;
2049 | float: left;
2050 | margin-right: 13px;
2051 | overflow: hidden;
2052 | }
2053 |
2054 | .markdown-body span.float-left span {
2055 | margin: 13px 0 0;
2056 | }
2057 |
2058 | .markdown-body span.float-right {
2059 | display: block;
2060 | float: right;
2061 | margin-left: 13px;
2062 | overflow: hidden;
2063 | }
2064 |
2065 | .markdown-body span.float-right>span {
2066 | display: block;
2067 | margin: 13px auto 0;
2068 | overflow: hidden;
2069 | text-align: right;
2070 | }
2071 |
2072 | .markdown-body code,
2073 | .markdown-body tt {
2074 | padding: .2em .4em;
2075 | margin: 0;
2076 | font-size: 85%;
2077 | white-space: break-spaces;
2078 | background-color: var(--color-neutral-muted);
2079 | border-radius: 6px;
2080 | }
2081 |
2082 | .markdown-body code br,
2083 | .markdown-body tt br {
2084 | display: none;
2085 | }
2086 |
2087 | .markdown-body del code {
2088 | text-decoration: inherit;
2089 | }
2090 |
2091 | .markdown-body samp {
2092 | font-size: 85%;
2093 | }
2094 |
2095 | .markdown-body pre code {
2096 | font-size: 100%;
2097 | }
2098 |
2099 | .markdown-body pre>code {
2100 | padding: 0;
2101 | margin: 0;
2102 | word-break: normal;
2103 | white-space: pre;
2104 | background: transparent;
2105 | border: 0;
2106 | }
2107 |
2108 | .markdown-body .highlight {
2109 | margin-bottom: 16px;
2110 | }
2111 |
2112 | .markdown-body .highlight pre {
2113 | margin-bottom: 0;
2114 | word-break: normal;
2115 | }
2116 |
2117 | .markdown-body .highlight pre,
2118 | .markdown-body pre {
2119 | font-size: 0.875rem;
2120 | position: relative;
2121 | }
2122 |
2123 | .markdown-body pre code,
2124 | .markdown-body pre tt {
2125 | display: inline;
2126 | max-width: auto;
2127 | padding: 0;
2128 | margin: 0;
2129 | overflow: visible;
2130 | line-height: inherit;
2131 | word-wrap: normal;
2132 | background-color: transparent;
2133 | border: 0;
2134 | }
2135 |
2136 | .markdown-body .csv-data td,
2137 | .markdown-body .csv-data th {
2138 | padding: 5px;
2139 | overflow: hidden;
2140 | font-size: 12px;
2141 | line-height: 1;
2142 | text-align: left;
2143 | white-space: nowrap;
2144 | }
2145 |
2146 | .markdown-body .csv-data .blob-num {
2147 | padding: 10px 8px 9px;
2148 | text-align: right;
2149 | background: var(--color-canvas-default);
2150 | border: 0;
2151 | }
2152 |
2153 | .markdown-body .csv-data tr {
2154 | border-top: 0;
2155 | }
2156 |
2157 | .markdown-body .csv-data th {
2158 | font-weight: var(--base-text-weight-semibold, 600);
2159 | background: var(--color-canvas-subtle);
2160 | border-top: 0;
2161 | }
2162 |
2163 | .markdown-body [data-footnote-ref]::before {
2164 | content: "[";
2165 | }
2166 |
2167 | .markdown-body [data-footnote-ref]::after {
2168 | content: "]";
2169 | }
2170 |
2171 | .markdown-body .footnotes {
2172 | font-size: 12px;
2173 | color: var(--color-fg-muted);
2174 | border-top: 1px solid var(--color-border-default);
2175 | }
2176 |
2177 | .markdown-body .footnotes ol {
2178 | padding-left: 16px;
2179 | }
2180 |
2181 | .markdown-body .footnotes ol ul {
2182 | display: inline-block;
2183 | padding-left: 16px;
2184 | margin-top: 16px;
2185 | }
2186 |
2187 | .markdown-body .footnotes li {
2188 | position: relative;
2189 | }
2190 |
2191 | .markdown-body .footnotes li:target::before {
2192 | position: absolute;
2193 | top: -8px;
2194 | right: -8px;
2195 | bottom: -8px;
2196 | left: -24px;
2197 | pointer-events: none;
2198 | content: "";
2199 | border: 2px solid var(--color-accent-emphasis);
2200 | border-radius: 6px;
2201 | }
2202 |
2203 | .markdown-body .footnotes li:target {
2204 | color: var(--color-fg-default);
2205 | }
2206 |
2207 | .markdown-body .footnotes .data-footnote-backref g-emoji {
2208 | font-family: monospace;
2209 | }
2210 |
2211 | .markdown-body .pl-c {
2212 | color: var(--color-prettylights-syntax-comment);
2213 | }
2214 |
2215 | .markdown-body .pl-c1,
2216 | .markdown-body .pl-s .pl-v {
2217 | color: var(--color-prettylights-syntax-constant);
2218 | }
2219 |
2220 | .markdown-body .pl-e,
2221 | .markdown-body .pl-en {
2222 | color: var(--color-prettylights-syntax-entity);
2223 | }
2224 |
2225 | .markdown-body .pl-smi,
2226 | .markdown-body .pl-s .pl-s1 {
2227 | color: var(--color-prettylights-syntax-storage-modifier-import);
2228 | }
2229 |
2230 | .markdown-body .pl-ent {
2231 | color: var(--color-prettylights-syntax-entity-tag);
2232 | }
2233 |
2234 | .markdown-body .pl-k {
2235 | color: var(--color-prettylights-syntax-keyword);
2236 | }
2237 |
2238 | .markdown-body .pl-s,
2239 | .markdown-body .pl-pds,
2240 | .markdown-body .pl-s .pl-pse .pl-s1,
2241 | .markdown-body .pl-sr,
2242 | .markdown-body .pl-sr .pl-cce,
2243 | .markdown-body .pl-sr .pl-sre,
2244 | .markdown-body .pl-sr .pl-sra {
2245 | color: var(--color-prettylights-syntax-string);
2246 | }
2247 |
2248 | .markdown-body .pl-v,
2249 | .markdown-body .pl-smw {
2250 | color: var(--color-prettylights-syntax-variable);
2251 | }
2252 |
2253 | .markdown-body .pl-bu {
2254 | color: var(--color-prettylights-syntax-brackethighlighter-unmatched);
2255 | }
2256 |
2257 | .markdown-body .pl-ii {
2258 | color: var(--color-prettylights-syntax-invalid-illegal-text);
2259 | background-color: var(--color-prettylights-syntax-invalid-illegal-bg);
2260 | }
2261 |
2262 | .markdown-body .pl-c2 {
2263 | color: var(--color-prettylights-syntax-carriage-return-text);
2264 | background-color: var(--color-prettylights-syntax-carriage-return-bg);
2265 | }
2266 |
2267 | .markdown-body .pl-sr .pl-cce {
2268 | font-weight: bold;
2269 | color: var(--color-prettylights-syntax-string-regexp);
2270 | }
2271 |
2272 | .markdown-body .pl-ml {
2273 | color: var(--color-prettylights-syntax-markup-list);
2274 | }
2275 |
2276 | .markdown-body .pl-mh,
2277 | .markdown-body .pl-mh .pl-en,
2278 | .markdown-body .pl-ms {
2279 | font-weight: bold;
2280 | color: var(--color-prettylights-syntax-markup-heading);
2281 | }
2282 |
2283 | .markdown-body .pl-mi {
2284 | font-style: italic;
2285 | color: var(--color-prettylights-syntax-markup-italic);
2286 | }
2287 |
2288 | .markdown-body .pl-mb {
2289 | font-weight: bold;
2290 | color: var(--color-prettylights-syntax-markup-bold);
2291 | }
2292 |
2293 | .markdown-body .pl-md {
2294 | color: var(--color-prettylights-syntax-markup-deleted-text);
2295 | background-color: var(--color-prettylights-syntax-markup-deleted-bg);
2296 | }
2297 |
2298 | .markdown-body .pl-mi1 {
2299 | color: var(--color-prettylights-syntax-markup-inserted-text);
2300 | background-color: var(--color-prettylights-syntax-markup-inserted-bg);
2301 | }
2302 |
2303 | .markdown-body .pl-mc {
2304 | color: var(--color-prettylights-syntax-markup-changed-text);
2305 | background-color: var(--color-prettylights-syntax-markup-changed-bg);
2306 | }
2307 |
2308 | .markdown-body .pl-mi2 {
2309 | color: var(--color-prettylights-syntax-markup-ignored-text);
2310 | background-color: var(--color-prettylights-syntax-markup-ignored-bg);
2311 | }
2312 |
2313 | .markdown-body .pl-mdr {
2314 | font-weight: bold;
2315 | color: var(--color-prettylights-syntax-meta-diff-range);
2316 | }
2317 |
2318 | .markdown-body .pl-ba {
2319 | color: var(--color-prettylights-syntax-brackethighlighter-angle);
2320 | }
2321 |
2322 | .markdown-body .pl-sg {
2323 | color: var(--color-prettylights-syntax-sublimelinter-gutter-mark);
2324 | }
2325 |
2326 | .markdown-body .pl-corl {
2327 | text-decoration: underline;
2328 | color: var(--color-prettylights-syntax-constant-other-reference-link);
2329 | }
2330 |
2331 | .markdown-body g-emoji {
2332 | display: inline-block;
2333 | min-width: 1ch;
2334 | font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
2335 | font-size: 1em;
2336 | font-style: normal !important;
2337 | font-weight: var(--base-text-weight-normal, 400);
2338 | line-height: 1;
2339 | vertical-align: -0.075em;
2340 | }
2341 |
2342 | .markdown-body g-emoji img {
2343 | width: 1em;
2344 | height: 1em;
2345 | }
2346 |
2347 | .markdown-body .task-list-item {
2348 | list-style: none;
2349 | }
2350 |
2351 | .markdown-body .task-list-item label {
2352 | font-weight: var(--base-text-weight-normal, 400);
2353 | }
2354 |
2355 | .markdown-body .task-list-item.enabled label {
2356 | cursor: pointer;
2357 | }
2358 |
2359 | .markdown-body .task-list-item+.task-list-item {
2360 | margin-top: 4px;
2361 | }
2362 |
2363 | .markdown-body .task-list-item .handle {
2364 | display: none;
2365 | }
2366 |
2367 | .markdown-body .task-list-item-checkbox {
2368 | margin: 0 .2em .25em -1.4em;
2369 | vertical-align: middle;
2370 | }
2371 |
2372 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
2373 | margin: 0 -1.6em .25em .2em;
2374 | }
2375 |
2376 | .markdown-body .contains-task-list {
2377 | position: relative;
2378 | }
2379 |
2380 | .markdown-body .contains-task-list:hover .task-list-item-convert-container,
2381 | .markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
2382 | display: block;
2383 | width: auto;
2384 | height: 24px;
2385 | overflow: visible;
2386 | clip: auto;
2387 | }
2388 |
2389 | .markdown-body ::-webkit-calendar-picker-indicator {
2390 | filter: invert(50%);
2391 | }
2392 |
2393 | .hover\:bg-cyan-700:hover {
2394 | --tw-bg-opacity: 1;
2395 | background-color: rgb(14 116 144 / var(--tw-bg-opacity));
2396 | }
2397 |
2398 | .hover\:bg-gray-200:hover {
2399 | --tw-bg-opacity: 1;
2400 | background-color: rgb(206 211 222 / var(--tw-bg-opacity));
2401 | }
2402 |
2403 | .hover\:bg-gray-300\/20:hover {
2404 | background-color: rgb(175 183 202 / 0.2);
2405 | }
2406 |
2407 | .hover\:bg-gray-300\/50:hover {
2408 | background-color: rgb(175 183 202 / 0.5);
2409 | }
2410 |
2411 | .hover\:text-white:hover {
2412 | --tw-text-opacity: 1;
2413 | color: rgb(255 255 255 / var(--tw-text-opacity));
2414 | }
2415 |
2416 | .focus\:border-cyan-600:focus {
2417 | --tw-border-opacity: 1;
2418 | border-color: rgb(8 145 178 / var(--tw-border-opacity));
2419 | }
2420 |
2421 | .focus\:outline-none:focus {
2422 | outline: 2px solid transparent;
2423 | outline-offset: 2px;
2424 | }
2425 |
2426 | .focus\:ring-1:focus {
2427 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2428 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2429 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2430 | }
2431 |
2432 | .focus\:ring-cyan-600:focus {
2433 | --tw-ring-opacity: 1;
2434 | --tw-ring-color: rgb(8 145 178 / var(--tw-ring-opacity));
2435 | }
2436 |
2437 | .enabled\:hover\:bg-gray-200\/30:hover:enabled {
2438 | background-color: rgb(206 211 222 / 0.3);
2439 | }
2440 |
2441 | .enabled\:hover\:text-gray-700:hover:enabled {
2442 | --tw-text-opacity: 1;
2443 | color: rgb(67 77 101 / var(--tw-text-opacity));
2444 | }
2445 |
2446 | .disabled\:opacity-50:disabled {
2447 | opacity: 0.5;
2448 | }
2449 |
2450 | .disabled\:opacity-75:disabled {
2451 | opacity: 0.75;
2452 | }
2453 |
2454 | .group:hover .group-hover\:bg-white {
2455 | --tw-bg-opacity: 1;
2456 | background-color: rgb(255 255 255 / var(--tw-bg-opacity));
2457 | }
2458 |
2459 | .group:hover .group-hover\:text-cyan-600 {
2460 | --tw-text-opacity: 1;
2461 | color: rgb(8 145 178 / var(--tw-text-opacity));
2462 | }
2463 |
2464 | @media (prefers-color-scheme: dark) {
2465 | .dark\:block {
2466 | display: block;
2467 | }
2468 |
2469 | .dark\:divide-gray-700\/50 > :not([hidden]) ~ :not([hidden]) {
2470 | border-color: rgb(67 77 101 / 0.5);
2471 | }
2472 |
2473 | .dark\:border-none {
2474 | border-style: none;
2475 | }
2476 |
2477 | .dark\:border-gray-200\/10 {
2478 | border-color: rgb(206 211 222 / 0.1);
2479 | }
2480 |
2481 | .dark\:border-gray-700\/40 {
2482 | border-color: rgb(67 77 101 / 0.4);
2483 | }
2484 |
2485 | .dark\:bg-black\/20 {
2486 | background-color: rgb(0 0 0 / 0.2);
2487 | }
2488 |
2489 | .dark\:bg-gray-700\/75 {
2490 | background-color: rgb(67 77 101 / 0.75);
2491 | }
2492 |
2493 | .dark\:bg-gray-800\/50 {
2494 | background-color: rgb(47 54 70 / 0.5);
2495 | }
2496 |
2497 | .dark\:bg-gray-900 {
2498 | --tw-bg-opacity: 1;
2499 | background-color: rgb(26 30 39 / var(--tw-bg-opacity));
2500 | }
2501 |
2502 | .dark\:bg-gray-900\/75 {
2503 | background-color: rgb(26 30 39 / 0.75);
2504 | }
2505 |
2506 | .dark\:bg-gray-950 {
2507 | --tw-bg-opacity: 1;
2508 | background-color: rgb(22 26 34 / var(--tw-bg-opacity));
2509 | }
2510 |
2511 | .dark\:bg-gray-950\/30 {
2512 | background-color: rgb(22 26 34 / 0.3);
2513 | }
2514 |
2515 | .dark\:bg-white {
2516 | --tw-bg-opacity: 1;
2517 | background-color: rgb(255 255 255 / var(--tw-bg-opacity));
2518 | }
2519 |
2520 | .dark\:bg-white\/10 {
2521 | background-color: rgb(255 255 255 / 0.1);
2522 | }
2523 |
2524 | .dark\:bg-white\/5 {
2525 | background-color: rgb(255 255 255 / 0.05);
2526 | }
2527 |
2528 | .dark\:from-gray-900 {
2529 | --tw-gradient-from: #1A1E27 var(--tw-gradient-from-position);
2530 | --tw-gradient-to: rgb(26 30 39 / 0) var(--tw-gradient-to-position);
2531 | --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2532 | }
2533 |
2534 | .dark\:to-gray-900 {
2535 | --tw-gradient-to: #1A1E27 var(--tw-gradient-to-position);
2536 | }
2537 |
2538 | .dark\:fill-gray-900 {
2539 | fill: #1A1E27;
2540 | }
2541 |
2542 | .dark\:fill-white {
2543 | fill: #fff;
2544 | }
2545 |
2546 | .dark\:text-gray-100 {
2547 | --tw-text-opacity: 1;
2548 | color: rgb(240 241 245 / var(--tw-text-opacity));
2549 | }
2550 |
2551 | .dark\:text-gray-300\/50 {
2552 | color: rgb(175 183 202 / 0.5);
2553 | }
2554 |
2555 | .dark\:text-gray-300\/80 {
2556 | color: rgb(175 183 202 / 0.8);
2557 | }
2558 |
2559 | .dark\:text-white {
2560 | --tw-text-opacity: 1;
2561 | color: rgb(255 255 255 / var(--tw-text-opacity));
2562 | }
2563 |
2564 | .dark\:text-white\/20 {
2565 | color: rgb(255 255 255 / 0.2);
2566 | }
2567 |
2568 | .dark\:text-white\/40 {
2569 | color: rgb(255 255 255 / 0.4);
2570 | }
2571 |
2572 | .dark\:placeholder-gray-300\/40::-moz-placeholder {
2573 | color: rgb(175 183 202 / 0.4);
2574 | }
2575 |
2576 | .dark\:placeholder-gray-300\/40::placeholder {
2577 | color: rgb(175 183 202 / 0.4);
2578 | }
2579 |
2580 | .dark\:hover\:bg-gray-800\/60:hover {
2581 | background-color: rgb(47 54 70 / 0.6);
2582 | }
2583 |
2584 | .dark\:hover\:bg-gray-900:hover {
2585 | --tw-bg-opacity: 1;
2586 | background-color: rgb(26 30 39 / var(--tw-bg-opacity));
2587 | }
2588 |
2589 | .dark\:hover\:bg-white\/20:hover {
2590 | background-color: rgb(255 255 255 / 0.2);
2591 | }
2592 |
2593 | .dark\:enabled\:hover\:bg-gray-800\/40:hover:enabled {
2594 | background-color: rgb(47 54 70 / 0.4);
2595 | }
2596 |
2597 | .dark\:enabled\:hover\:text-gray-100:hover:enabled {
2598 | --tw-text-opacity: 1;
2599 | color: rgb(240 241 245 / var(--tw-text-opacity));
2600 | }
2601 | }
2602 |
2603 | @media (min-width: 1024px) {
2604 | .lg\:max-w-\[85\%\] {
2605 | max-width: 85%;
2606 | }
2607 | }
2608 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-ollama",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev-css": "npx tailwindcss -i ./src/css/style.css -o ./public/style.css --watch",
8 | "dev-app": "shadow-cljs watch app",
9 | "dev": "concurrently \"npm:dev-app\" \"npm:dev-css\"",
10 | "build-app": "shadow-cljs release app",
11 | "build-clean": "rimraf dist",
12 | "build-copy": "cp public/index.html public/favicon.ico public/license.txt dist/ && mkdir dist/assets && cp public/assets/* dist/assets && awk 'BEGIN {print \"/*! For license information please see LICENSE.txt */\"} {print}' dist/js/main.js > temp && mv temp dist/js/main.js",
13 | "build-css": "npx tailwindcss -i ./src/css/style.css -o ./dist/style.css",
14 | "build": "npm run build-clean && npm run build-css && npm run build-app && npm run build-copy",
15 | "serve": "npx serve dist -L -s -n -p 1420",
16 | "shadow-cljs": "shadow-cljs"
17 | },
18 | "devDependencies": {
19 | "concurrently": "^9.1.2",
20 | "rimraf": "^6.0.1",
21 | "serve": "^14.2.4",
22 | "shadow-cljs": "^2.28.22",
23 | "tailwindcss": "^3.3.3"
24 | },
25 | "dependencies": {
26 | "@react-spring/web": "^9.7.5",
27 | "date-fns": "^4.1.0",
28 | "lucide-react": "^0.487.0",
29 | "react": "^18.2.0",
30 | "react-dom": "^18.2.0",
31 | "react-hotkeys-hook": "^4.6.1",
32 | "react-markdown": "^10.1.0",
33 | "react-refresh": "^0.17.0",
34 | "react-syntax-highlighter": "^15.6.1",
35 | "use-sync-external-store": "^1.5.0"
36 | },
37 | "overrides": {
38 | "browserify-sign": "^4.2.2",
39 | "postcss": "^8.4.31",
40 | "prismjs": "^1.30.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/assets/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-144x144.png
--------------------------------------------------------------------------------
/public/assets/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/assets/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-36x36.png
--------------------------------------------------------------------------------
/public/assets/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-48x48.png
--------------------------------------------------------------------------------
/public/assets/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-72x72.png
--------------------------------------------------------------------------------
/public/assets/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/android-icon-96x96.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-114x114.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-120x120.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-144x144.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-152x152.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-180x180.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-57x57.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-60x60.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-72x72.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-76x76.png
--------------------------------------------------------------------------------
/public/assets/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/public/assets/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/apple-icon.png
--------------------------------------------------------------------------------
/public/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/public/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/public/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/public/assets/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/favicon-96x96.png
--------------------------------------------------------------------------------
/public/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "App",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/public/assets/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/ms-icon-144x144.png
--------------------------------------------------------------------------------
/public/assets/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/ms-icon-150x150.png
--------------------------------------------------------------------------------
/public/assets/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/ms-icon-310x310.png
--------------------------------------------------------------------------------
/public/assets/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/assets/ms-icon-70x70.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonathandale/chat-ollama/077e18cc11975d0bdeee66d0b25957c080b2b667/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Chat Ollama
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/public/license.txt:
--------------------------------------------------------------------------------
1 | # Licenses
2 |
3 | ## @react-spring/web
4 |
5 | MIT License
6 |
7 | Copyright (c) 2018-present Paul Henschel, react-spring, all contributors
8 |
9 | https://opensource.org/license/mit/
10 |
11 | ## date-fns
12 |
13 | MIT License
14 |
15 | Copyright (c) 2021 Sasha Koss and Lesha Koss https://kossnocorp.mit-license.org
16 |
17 | https://opensource.org/license/mit/
18 |
19 | ## lucide-react
20 |
21 | ISC License
22 |
23 | Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
24 |
25 | https://opensource.org/license/isc-license-txt/
26 |
27 | ## react
28 |
29 | MIT License
30 |
31 | Copyright (c) Facebook, Inc. and its affiliates.
32 |
33 | https://opensource.org/license/mit/
34 |
35 | ## react-dom
36 |
37 | MIT License
38 |
39 | Copyright (c) Facebook, Inc. and its affiliates.
40 |
41 | https://opensource.org/license/mit/
42 |
43 | ## react-hotkeys-hook
44 |
45 | MIT License
46 |
47 | Copyright (c) 2018 Johannes Klauss
48 |
49 | https://opensource.org/license/mit/
50 |
51 | ## react-markdown
52 |
53 | The MIT License (MIT)
54 |
55 | Copyright (c) 2015 Espen Hovlandsdal
56 |
57 | https://opensource.org/license/mit/
58 |
59 | ## react-refresh
60 |
61 | MIT License
62 |
63 | Copyright (c) Facebook, Inc. and its affiliates.
64 |
65 | https://opensource.org/license/mit/
66 |
67 | ## react-syntax-highlighter
68 |
69 | MIT License
70 |
71 | Copyright (c) 2019 Conor Hastings
72 |
73 | https://opensource.org/license/mit/
74 |
75 | ## use-sync-external-store
76 |
77 | MIT License
78 |
79 | Copyright (c) Facebook, Inc. and its affiliates.
80 |
81 | https://opensource.org/license/mit/
82 |
83 | ## lilactown/helix "0.1.10"
84 |
85 | Eclipse Public License - v 2.0
86 |
87 | https://opensource.org/license/epl-2-0/
88 |
89 | ## com.fbeyer/refx "0.0.49"
90 |
91 | MIT License
92 |
93 | Copyright (c) 2022 Ferdinand Beyer
94 |
95 | https://opensource.org/license/mit/
96 |
97 | ## applied-science/js-interop "0.4.2"
98 |
99 | Eclipse Public License - v 2.0
100 |
101 | https://opensource.org/license/epl-2-0/
102 |
103 | ## cljs-bean "1.9.0"
104 |
105 | Eclipse Public License - v 1.0
106 |
107 | https://opensource.org/license/epl-1-0/
108 |
109 | ## funcool/promesa "11.0.678"
110 |
111 | Mozilla Public License Version 2.0
112 |
113 | https://opensource.org/license/mpl-2-0/
114 |
115 | ## com.cognitect/transit-cljs "0.8.280"
116 |
117 | Apache License
118 | Version 2.0, January 2004
119 | http://www.apache.org/licenses/
120 |
--------------------------------------------------------------------------------
/shadow-cljs.edn:
--------------------------------------------------------------------------------
1 | {:source-paths ["src/cljs"]
2 |
3 | :dependencies [[lilactown/helix "0.1.10"]
4 | [com.fbeyer/refx "0.0.49"]
5 | [applied-science/js-interop "0.4.2"]
6 | [cljs-bean "1.9.0"]
7 | [funcool/promesa "11.0.678"]
8 | [com.cognitect/transit-cljs "0.8.280"]]
9 |
10 | :dev-http {1420 "public"}
11 |
12 | :builds {:app {:target :browser
13 | :output-dir "public/js"
14 | :asset-path "/js"
15 | :modules {:main {:init-fn chat-ollama.core/main}}
16 | :release {:output-dir "dist/js"}}}}
17 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/core.cljs:
--------------------------------------------------------------------------------
1 | (ns ^:dev/once chat-ollama.core
2 | (:require [chat-ollama.lib :refer [defnc]]
3 | [helix.core :refer [$]]
4 | [refx.alpha :as refx :refer [dispatch-sync]]
5 | [chat-ollama.fx]
6 | [chat-ollama.events]
7 | [chat-ollama.subs]
8 | [chat-ollama.views :as views]
9 | ["react-dom/client" :as rdc]))
10 |
11 | (enable-console-print!)
12 |
13 | (dispatch-sync [:initialise-db])
14 |
15 | (defnc root-view []
16 | ($ :div {:class ["h-screen" "w-screen" "bg-white" "overflow-hidden" "dark:bg-gray-900" "bg-white" "text-gray-900"]}
17 | ($ views/Main)))
18 |
19 | (defonce root (rdc/createRoot (js/document.getElementById "root")))
20 |
21 | (defn ^:dev/after-load mount-ui []
22 | (refx/clear-subscription-cache!)
23 | (.render root ($ root-view)))
24 |
25 | (defn ^:export main []
26 | (mount-ui))
27 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/db.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.db
2 | (:require [cljs.spec.alpha :as s]
3 | [chat-ollama.db.model :as model]
4 | [chat-ollama.db.dialog :as dialog]))
5 |
6 | (s/def ::ollama-offline? (s/nilable boolean?))
7 |
8 | (s/def ::db (s/keys :req-un [:chat-ollama.db/ollama-offline?
9 | ::model/models
10 | ::model/selected-model
11 | ::dialog/dialogs
12 | ::dialog/selected-dialog]))
13 |
14 | ;; Default DB
15 | (def default-db
16 | {:models nil
17 | :dialogs nil
18 | :selected-model nil
19 | :selected-dialog nil
20 | :ollama-offline? nil})
21 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/db/dialog.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.db.dialog
2 | (:require [cljs.spec.alpha :as s]))
3 |
4 | (s/def ::prompt string?)
5 | (s/def ::timestamp number?)
6 | (s/def ::uuid string?)
7 | (s/def ::model-name (s/nilable string?))
8 | (s/def ::answer (s/nilable string?))
9 | (s/def ::generating? boolean?)
10 | (s/def ::aborted? boolean?)
11 | (s/def ::failed? boolean?)
12 |
13 |
14 | (s/def ::context coll?)
15 | (s/def ::created_at string?)
16 | (s/def ::eval_count int?)
17 | (s/def ::eval_duration int?)
18 | (s/def ::load_duration int?)
19 | (s/def ::prompt_eval_count int?)
20 | (s/def ::prompt_eval_duration int?)
21 | (s/def ::total_duration int?)
22 | (s/def ::response string?)
23 | (s/def ::meta (s/keys :req-un [::context
24 | ::created_at
25 | ::eval_count
26 | ::eval_duration
27 | ::load_duration
28 | ::prompt_eval_count
29 | ::total_duration
30 | ::response]
31 | :opt-un [::prompt_eval_duration]))
32 |
33 | (s/def ::exchange (s/keys :req-un [::timestamp ::prompt]
34 | :opt-un [::answer ::meta ::aborted? ::failed?]))
35 | (s/def ::exchanges (s/nilable (s/map-of ::uuid ::exchange)))
36 | (s/def ::dialog (s/keys :req-un [::uuid
37 | ::model-name
38 | ::timestamp
39 | ::generating?]
40 | :opt-un [::exchanges ::prompt]))
41 | (s/def ::dialogs (s/nilable (s/map-of ::uuid ::dialog)))
42 | (s/def ::selected-dialog (s/nilable ::uuid))
43 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/db/model.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.db.model
2 | (:require [cljs.spec.alpha :as s]))
3 |
4 | (s/def ::digest string?)
5 | (s/def ::size int?)
6 | (s/def ::name string?)
7 |
8 | (s/def ::model (s/keys :req-un [::digest ::name ::size]))
9 | (s/def ::models (s/nilable (s/coll-of ::model :kind coll?)))
10 | (s/def ::selected-model (s/nilable ::name))
11 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/events.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.events
2 | (:require [cljs.spec.alpha :as s]
3 | [chat-ollama.db :refer [default-db]]
4 | [refx.alpha :refer [->interceptor reg-event-db reg-event-fx]]
5 | [refx.interceptors :refer [after]]
6 | ["date-fns" :refer (getUnixTime)]))
7 |
8 | (defonce api-base "http://127.0.0.1:11434")
9 | (defonce wait-multiplier 1.25)
10 | (defonce wait-max (* 1000 60 5))
11 |
12 | (defn check-and-throw
13 | "Throws an exception if `db` doesn't match the Spec `a-spec`."
14 | [a-spec db]
15 | (when-not (s/valid? a-spec db)
16 | (throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
17 |
18 | (def check-spec-interceptor (after (partial check-and-throw :chat-ollama.db/db)))
19 |
20 | (def offline-interceptor
21 | (->interceptor
22 | :id :offline?
23 | :after (fn [{:keys [coeffects] :as context}]
24 | (let [{:keys [event db]} coeffects
25 | offline? (:ollama-offline? db)
26 | [_ wait {:keys [url status]}] event]
27 | (if (and (some? url)
28 | (zero? status))
29 | (let [new-wait (when (number? wait)
30 | (* wait wait-multiplier))]
31 | (cond-> context
32 | true
33 | (assoc-in [:coeffects :db :ollama-offline?] true)
34 |
35 | (and (not (false? offline?))
36 | (number? new-wait)
37 | (< new-wait wait-max))
38 | (update-in [:effects :fx]
39 | conj
40 | [:dispatch-later {:ms new-wait
41 | :dispatch [:get-models new-wait]}])))
42 | context)))))
43 |
44 | (def ollama-interceptors
45 | [offline-interceptor
46 | check-spec-interceptor])
47 |
48 | (reg-event-fx
49 | :initialise-db
50 | ollama-interceptors
51 | (fn [_ _]
52 | {:db default-db
53 | :dispatch [:new-dialog]}))
54 |
55 | (reg-event-db
56 | :set-selected-model
57 | ollama-interceptors
58 | (fn [db [_ model-name]]
59 | (assoc db :selected-model model-name)))
60 |
61 | ;; GET MODELS
62 | (reg-event-fx
63 | :get-models-success
64 | ollama-interceptors
65 | (fn [{:keys [db]} [_ {:keys [models]}]]
66 | {:db (assoc db
67 | :models models
68 | :ollama-offline? false)}))
69 |
70 | (reg-event-db
71 | :get-models-failure
72 | ollama-interceptors
73 | (fn [db [_ _wait {:keys [status]}]]
74 | (assoc db :ollama-offline? (zero? status))))
75 |
76 | (reg-event-fx
77 | :get-models
78 | ollama-interceptors
79 | (fn [_ [_ wait]]
80 | {:fetch {:url (str api-base "/api/tags")
81 | :method :get
82 | :on-success [:get-models-success]
83 | :on-failure [:get-models-failure wait]}}))
84 |
85 | (reg-event-db
86 | :warm-model-success
87 | identity)
88 |
89 | (reg-event-db
90 | :warm-model-failure
91 | ollama-interceptors
92 | (fn [db [_ _wait {:keys [status]}]]
93 | (assoc db :ollama-offline? (zero? status))))
94 |
95 | (reg-event-fx
96 | :warm-model
97 | ollama-interceptors
98 | (fn [_ [_ model-name]]
99 | (if model-name
100 | {:fetch {:url (str api-base "/api/generate")
101 | :method :post
102 | :body {:model model-name}
103 | :on-success [:warm-model-success]
104 | :on-failure [:warm-model-failure]}}
105 | {})))
106 |
107 | ;; DIALOGS
108 |
109 | (reg-event-fx
110 | :set-selected-dialog
111 | ollama-interceptors
112 | (fn [{:keys [db]} [_ dialog-uuid]]
113 | (let [selected-dialog (get-in db [:dialogs dialog-uuid])]
114 | {:db (assoc db :selected-dialog dialog-uuid)
115 | :dispatch [:warm-model (:model-name selected-dialog)]})))
116 |
117 | (reg-event-db
118 | :set-dialog-model
119 | ollama-interceptors
120 | (fn [db [_ dialog-uuid model-name]]
121 | (-> db
122 | (assoc-in [:dialogs dialog-uuid :model-name] model-name)
123 | (assoc :selected-model model-name))))
124 |
125 | (reg-event-fx
126 | :new-dialog
127 | ollama-interceptors
128 | (fn [{:keys [db]} [_ model-name]]
129 | (let [new-uuid (str (random-uuid))
130 | timestamp (getUnixTime (new js/Date))]
131 | {:db (-> db
132 | (assoc-in [:dialogs new-uuid]
133 | {:uuid new-uuid
134 | :generating? false
135 | :model-name model-name
136 | :timestamp timestamp})
137 | (assoc :selected-model model-name
138 | :selected-dialog new-uuid))
139 | :dispatch [:warm-model model-name]})))
140 |
141 | (reg-event-fx
142 | :delete-dialog
143 | ollama-interceptors
144 | (fn [{:keys [db]} [_ dialog-uuid]]
145 | (let [purged (update db :dialogs dissoc dialog-uuid)
146 | next-selected (->> purged
147 | :dialogs
148 | vals
149 | (sort-by :timestamp)
150 | first)
151 | model-name (:model-name next-selected)]
152 | (cond-> {:db (-> purged
153 | (assoc :selected-model model-name
154 | :selected-dialog (:uuid next-selected)))}
155 | (seq? model-name)
156 | (assoc :dispatch [:warm-model model-name])))))
157 |
158 | ;; PROMPTS
159 | (reg-event-fx
160 | :send-prompt
161 | ollama-interceptors
162 | (fn [{:keys [db]} [_ {:keys [selected-dialog prompt set-abort!]}]]
163 | (let [new-uuid (str (random-uuid))
164 | timestamp (getUnixTime (new js/Date))
165 | context (->> (get-in db [:dialogs selected-dialog :exchanges])
166 | vals
167 | (sort-by > :timestamp)
168 | first
169 | :meta
170 | :context)]
171 | {:db (cond-> db
172 | :always
173 | (assoc-in [:dialogs selected-dialog :generating?] true)
174 | :always
175 | (assoc-in [:dialogs selected-dialog :exchanges new-uuid]
176 | {:prompt prompt
177 | :timestamp timestamp})
178 | (nil? context)
179 | (assoc-in [:dialogs selected-dialog :title] prompt))
180 | :dispatch [:get-answer {:prompt prompt
181 | :context context
182 | :set-abort! set-abort!
183 | :dialog-uuid selected-dialog
184 | :exchange-uuid new-uuid}]})))
185 |
186 | (reg-event-fx
187 | :get-answer-success
188 | ollama-interceptors
189 | (fn [{:keys [db]} [_ {:keys [dialog-uuid exchange-uuid]} response]]
190 | {:db (-> db
191 | (assoc-in [:dialogs dialog-uuid :exchanges exchange-uuid :meta] response)
192 | (assoc-in [:dialogs dialog-uuid :generating?] false))}))
193 |
194 | (reg-event-fx
195 | :get-answer-progress
196 | ollama-interceptors
197 | (fn [{:keys [db]}
198 | [_
199 | {:keys [dialog-uuid exchange-uuid]}
200 | {:keys [text _idx _new-line?]}]]
201 | {:db (update-in db [:dialogs dialog-uuid :exchanges exchange-uuid :answer]
202 | #(str % text))}))
203 |
204 | (reg-event-db
205 | :get-answer-failure
206 | ollama-interceptors
207 | (fn [db [_ {:keys [dialog-uuid exchange-uuid]} {:keys [status]}]]
208 | (-> db
209 | (assoc :ollama-offline? (zero? status))
210 | (assoc-in [:dialogs dialog-uuid :generating?] false)
211 | (assoc-in [:dialogs dialog-uuid :exchanges exchange-uuid :failed?] true))))
212 |
213 | (reg-event-db
214 | :get-answer-abort
215 | ollama-interceptors
216 | (fn [db [_ {:keys [dialog-uuid exchange-uuid]} _]]
217 | (-> db
218 | (assoc-in [:dialogs dialog-uuid :generating?] false)
219 | (assoc-in [:dialogs dialog-uuid :exchanges exchange-uuid :aborted?] true))))
220 |
221 | (reg-event-fx
222 | :get-answer
223 | ollama-interceptors
224 | (fn [{:keys [db]} [_ {:keys [prompt context set-abort!] :as payload}]]
225 | {:fetch-stream {:url (str api-base "/api/generate")
226 | :method :post
227 | :body (cond-> {:model (:selected-model db)
228 | :prompt prompt}
229 | (some? context)
230 | (assoc :context context))
231 | :set-abort! set-abort!
232 | :on-progress [:get-answer-progress payload]
233 | :on-success [:get-answer-success payload]
234 | :on-abort [:get-answer-abort payload]
235 | :on-failure [:get-answer-failure payload]}}))
236 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/fx.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.fx
2 | (:require [promesa.core :as p]
3 | [applied-science.js-interop :as j]
4 | [cljs-bean.core :refer [->js ->clj]]
5 | [refx.alpha :refer [dispatch reg-fx]]
6 | [clojure.string :as str]))
7 |
8 | (defn request->fetch
9 | [{:as request
10 | :keys [url body on-success on-failure]
11 | :or {on-success [:http-no-on-success]
12 | on-failure [:http-no-on-failure]}}]
13 | (let [success-> #(dispatch (conj on-success (js->clj % :keywordize-keys true)))
14 | pruned (dissoc request :on-success :on-failure :url)
15 | options (cond-> pruned
16 | (some? body)
17 | (update :body #(as-> % $
18 | (->js $)
19 | (j/call js/JSON :stringify $))))]
20 | (-> (js/fetch url (->js options))
21 | (.then (fn [response]
22 | (if (j/get response :ok)
23 | (let [data (j/get response :data)]
24 | (if (some? data)
25 | (success-> data)
26 | (-> (j/call response :json)
27 | (.then success->))))
28 | (dispatch (conj on-failure request)))))
29 | (.catch #(dispatch (conj on-failure (assoc request :status 0)))))))
30 |
31 | (defn fetch-effect [request]
32 | (let [seq-request-maps (if (sequential? request) request [request])]
33 | (doseq [request seq-request-maps]
34 | (request->fetch request))))
35 |
36 | (reg-fx :fetch fetch-effect)
37 |
38 | (defn- parse-chunk [chunk]
39 | (try
40 | (js/JSON.parse chunk)
41 | (catch js/Error _
42 | (prn "parse error!" chunk))))
43 |
44 | (defn request->fetch-stream
45 | [{:keys [url body on-success on-progress on-failure signal]
46 | :or {on-success [:http-no-on-success]
47 | on-progress [:http-no-on-progress]
48 | on-failure [:http-no-on-failure]}}]
49 | (-> (js/fetch url #js{:method "post"
50 | :body (j/call js/JSON :stringify (->js body))
51 | :signal signal})
52 | (j/call :then
53 | (fn [response]
54 | (if-not (j/get response :ok)
55 | (dispatch (conj on-failure {:status 0}))
56 | (let [reader (-> response
57 | (j/get :body)
58 | (j/call :getReader))]
59 | #_{:clj-kondo/ignore [:unresolved-symbol]}
60 | (p/loop [data {:response ""}]
61 | (p/let [read (j/call reader :read)
62 | {:keys [done value]} (j/lookup read)]
63 | (if (and done (nil? value))
64 | (dispatch (conj on-success data))
65 | (let [chunk (-> (new js/TextDecoder)
66 | (j/call :decode value))
67 | lines (str/split-lines chunk)
68 | buffer (atom "")
69 | final-result (atom nil)]
70 |
71 | (doseq [line lines]
72 | (let [{:keys [response] :as result} (->clj (parse-chunk line))
73 | has-value? (seq response)]
74 | (if has-value?
75 | (do
76 | (swap! buffer str response)
77 | (dispatch (conj on-progress {:text response})))
78 | (reset! final-result result))))
79 |
80 | (p/recur (if (some? @final-result)
81 | (merge data @final-result)
82 | (update data :response #(str % @buffer))))))))))))
83 | (j/call :catch
84 | #(when (re-find #"network error" (j/get % :message))
85 | (dispatch (conj on-failure {:status 0}))))))
86 |
87 | (reg-fx :fetch-stream
88 | (fn [{:keys [set-abort! on-abort] :as request}]
89 | (if (fn? set-abort!)
90 | (let [controller (new js/AbortController)
91 | signal (j/get controller :signal)]
92 | (set-abort! (fn []
93 | #(do
94 | (j/call controller :abort)
95 | (dispatch (conj on-abort request))
96 | (set-abort! nil))))
97 | (request->fetch-stream (assoc request :signal signal)))
98 | (request->fetch-stream request))))
99 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/hooks.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.hooks
2 | (:require [applied-science.js-interop :as j]
3 | [helix.hooks :refer [use-effect use-state]]))
4 |
5 |
6 | (defn use-copy-to-clipboard []
7 | (let [[copied set-copied!] (use-state false)
8 | clipboard (j/get js/navigator :clipboard)
9 | copy! #(do
10 | (j/call clipboard :writeText %)
11 | (set-copied! true))]
12 | (use-effect
13 | [copied]
14 | (when copied
15 | (let [wait (js/setTimeout #(set-copied! false) 1500)]
16 | #(js/clearTimeout wait))))
17 |
18 | (when clipboard
19 | [copied copy!])))
20 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/lib.cljc:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.lib
2 | #?(:clj (:require [helix.core])
3 | :cljs (:require-macros [chat-ollama.lib])))
4 |
5 | #?(:clj
6 | (defmacro defnc [type & form-body]
7 | (let [[docstring form-body] (if (string? (first form-body))
8 | [(first form-body) (rest form-body)]
9 | [nil form-body])
10 | [fn-meta form-body] (if (map? (first form-body))
11 | [(first form-body) (rest form-body)]
12 | [nil form-body])
13 | params (first form-body)
14 | body (rest form-body)
15 | opts-map? (map? (first body))
16 | opts (cond-> (if opts-map?
17 | (first body)
18 | {})
19 | (:wrap fn-meta) (assoc :wrap (:wrap fn-meta)))
20 | ;; feature flags to enable by default
21 | default-opts {:helix/features {:fast-refresh true}}]
22 | `(helix.core/defnc ~type
23 | ~@(when docstring [docstring])
24 | ~@(when fn-meta [fn-meta])
25 | ~params
26 | ;; we use `merge` here to allow indidivual consumers to override feature
27 | ;; flags in special cases
28 | ~(merge default-opts opts)
29 | ~@body))))
30 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/subs.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.subs
2 | (:require [refx.alpha :refer [reg-sub]]))
3 |
4 | ;; Misc
5 | (reg-sub
6 | :ollama-offline?
7 | (fn [db _]
8 | (:ollama-offline? db)))
9 |
10 | (reg-sub
11 | :models
12 | (fn [db _]
13 | (:models db)))
14 |
15 | (reg-sub
16 | :selected-model
17 | (fn [db _]
18 | (:selected-model db)))
19 |
20 | (reg-sub
21 | :dialogs
22 | (fn [db _]
23 | (:dialogs db)))
24 |
25 | (reg-sub
26 | :dialog-list
27 | :<- [:dialogs]
28 | (fn [dialogs]
29 | (->> dialogs
30 | (vals)
31 | (map #(dissoc % :exchanges))
32 | (sort-by :timestamp)
33 | (reverse))))
34 |
35 | (reg-sub
36 | :selected-dialog
37 | (fn [db _]
38 | (:selected-dialog db)))
39 |
40 | (reg-sub
41 | :dialog
42 | :<- [:dialogs]
43 | (fn [dialogs [_ dialog-uuid]]
44 | (get dialogs dialog-uuid)))
45 |
46 | (reg-sub
47 | :dialog-meta
48 | :<- [:dialogs]
49 | (fn [dialogs [_ dialog-uuid]]
50 | (-> (get dialogs dialog-uuid)
51 | (select-keys [:title :model-name]))))
52 |
53 | (reg-sub
54 | :dialog-exchanges
55 | :<- [:dialogs]
56 | (fn [dialogs [_ dialog-uuid]]
57 | (->> (get-in dialogs [dialog-uuid :exchanges])
58 | (map (fn [[k v]]
59 | (assoc v :uuid k)))
60 | (sort-by :timestamp)
61 | (mapv :uuid))))
62 |
63 | (reg-sub
64 | :dialog-exchange
65 | (fn [db [_ {:keys [dialog-uuid exchange-uuid]}]]
66 | (get-in db [:dialogs dialog-uuid :exchanges exchange-uuid])))
67 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/utils.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.utils
2 | (:require [cognitect.transit :as t]
3 | [applied-science.js-interop :as j]))
4 |
5 |
6 | (defn debounce [f delay-ms]
7 | (let [timer (atom nil)]
8 | (fn [& args]
9 | (when @timer (js/clearTimeout @timer))
10 | (reset! timer (js/setTimeout #(apply f args) delay-ms)))))
11 |
12 | (defn throttle [f interval-ms]
13 | (let [timeout (atom nil)
14 | fire? (atom false)
15 | stored-args (atom [])
16 | fire (fn fire []
17 | (reset! timeout
18 | (js/setTimeout #(do
19 | (reset! timeout nil)
20 | (when @fire?
21 | (reset! fire? false)
22 | (fire)))
23 | interval-ms))
24 | (apply f @stored-args))]
25 | (fn [& args]
26 | (reset! stored-args args)
27 | (if @timeout
28 | (reset! fire? true)
29 | (fire)))))
30 |
31 | (defn local-storage-set! [k v]
32 | (try
33 | (let [w (t/writer :json)
34 | tv (t/write w v)]
35 | (-> (j/get js/window :localStorage)
36 | (j/call :setItem k tv)))
37 | (catch js/Error e
38 | (js/console.error e)
39 | (throw e))))
40 |
41 | (defn local-storage-get [k]
42 | (try
43 | (let [r (t/reader :json)]
44 | (as-> (j/get js/window :localStorage) $
45 | (j/call $ :getItem k)
46 | (t/read r $)))
47 | (catch js/Error e
48 | (js/console.error e)
49 | (throw e))))
50 |
--------------------------------------------------------------------------------
/src/cljs/chat_ollama/views.cljs:
--------------------------------------------------------------------------------
1 | (ns chat-ollama.views
2 | (:require [applied-science.js-interop :as j]
3 | [clojure.string :as str]
4 | [clojure.set :refer [union]]
5 | [chat-ollama.lib :refer [defnc]]
6 | [chat-ollama.utils :refer [debounce throttle local-storage-set! local-storage-get]]
7 | [chat-ollama.hooks :refer [use-copy-to-clipboard]]
8 | [helix.core :refer [$ <>]]
9 | [helix.hooks :refer [use-effect use-state use-ref]]
10 | [refx.alpha :refer [use-sub dispatch]]
11 | ["react-markdown$default" :as ReactMarkdown]
12 | ["react-syntax-highlighter/dist/esm/styles/hljs" :as p :refer [nord githubGist]]
13 | ["react-syntax-highlighter" :refer [Light]]
14 | ["react-hotkeys-hook" :refer [useHotkeys]]
15 | ["date-fns" :refer [formatDistance fromUnixTime parseISO]]
16 | ["@react-spring/web" :refer [useSpring animated]]
17 | ["lucide-react" :refer [Clipboard Check Plus User MessagesSquare Trash2
18 | PanelLeftClose PanelLeftOpen SendHorizontal XOctagon
19 | ArrowUpToLine ArrowDownToLine RefreshCw]]))
20 |
21 | (defonce dark-mode? (j/get (js/matchMedia "(prefers-color-scheme: dark)") :matches))
22 | (defonce max-textarea-height 500)
23 | (defonce min-textarea-height 48)
24 | (defonce line-height 48)
25 | (defonce sidebar-width 325)
26 | (defonce ls-chat-ollama-prefs "chat-ollama:prefs:")
27 |
28 | (defn- b->gb [bytes]
29 | (j/call (/ bytes 1024 1024 1024) :toFixed 2))
30 |
31 | (defnc Ollama []
32 | ($ :svg {:class ["dark:fill-gray-900" "fill-white" "w-[21px]" "h-[27px]" "scale-125"]
33 | :xmlns "http://www.w3.org/2000/svg"}
34 | ($ :path {:d "M19.642 27h-1.498c.315-1.119.308-2.208-.022-3.266-.177-.568-.915-1.363-.497-1.933 1.421-1.94 1.16-4.045.06-5.995-.133-.234-.148-.542.014-.74 1.088-1.333 1.29-2.789.606-4.369-.56-1.293-1.861-2.349-3.327-2.3-.253.007-.495.016-.726.027a.29.29 0 0 1-.28-.177c-.498-1.168-1.373-1.928-2.624-2.281-1.737-.49-3.658.459-4.423 2.072-.116.244-.147.388-.468.377-.422-.015-.859-.056-1.255.025-2.717.554-3.876 3.896-2.47 6.136.333.528.816.613.353 1.378-1.063 1.762-1.203 4.146.12 5.822.453.576-.384 1.567-.547 2.18-.26.983-.24 1.998.058 3.044H1.211c-.417-1.445-.269-3.32.508-4.648a.081.081 0 0 0-.002-.092C.424 20.28.52 17.66 1.567 15.603a.092.092 0 0 0-.006-.096c-1.279-1.93-1.228-4.524.15-6.385.304-.41.775-.836 1.173-1.236a.102.102 0 0 0 .029-.093 9.956 9.956 0 0 1 .172-4.504c.262-.967.991-2.224 2.099-2.177 1.7.072 2.336 2.658 2.426 3.966a.045.045 0 0 0 .066.036c1.822-1.041 3.643-1.037 5.463.012a.07.07 0 0 0 .104-.056c.073-1.126.441-2.537 1.234-3.384.534-.57 1.306-.75 1.97-.378 1.819 1.018 1.803 4.83 1.494 6.509a.09.09 0 0 0 .028.087c.4.374.659.622.777.745 1.713 1.775 1.845 4.76.526 6.818a.088.088 0 0 0-.004.094c1.053 2.066 1.175 4.724-.145 6.715a.1.1 0 0 0 0 .108c.248.374.428.785.54 1.234a6.65 6.65 0 0 1-.02 3.382ZM5.197 2.62a.07.07 0 0 0-.048-.018.066.066 0 0 0-.047.02c-.93.929-.984 3.236-.81 4.435.006.046.031.063.075.052a8.11 8.11 0 0 1 1.576-.222.114.114 0 0 0 .083-.04c.113-.13.17-.23.174-.301.044-1.116-.128-3.116-1.003-3.926Zm10.602.046a.165.165 0 0 0-.25.023c-.76 1.06-.933 2.549-.904 3.815.002.087.058.2.168.34.022.029.05.043.086.044a6.516 6.516 0 0 1 1.6.24.045.045 0 0 0 .051-.018.046.046 0 0 0 .007-.018c.154-1.116.127-3.574-.758-4.426Z"})
35 | ($ :path {:d "M13.48 13.144c2.105 2.046.448 4.854-2.154 5.035-.502.035-1.099.037-1.789.006-1.834-.08-3.609-1.734-2.989-3.708.894-2.843 4.981-3.23 6.932-1.333Zm-.323 1.199c-.874-1.46-2.958-1.69-4.342-1.008-.75.369-1.446 1.142-1.387 2.025.148 2.264 3.936 2.163 5.141 1.372.85-.56 1.109-1.518.588-2.39ZM4.607 12.684c-.29.5-.154 1.121.301 1.386.455.265 1.059.075 1.348-.426.289-.5.154-1.12-.302-1.386-.455-.265-1.058-.074-1.347.427ZM14.596 13.65c.293.498.898.683 1.351.414.454-.27.583-.89.29-1.388-.293-.497-.898-.682-1.35-.413-.454.269-.584.89-.29 1.387Z"})
36 | ($ :path {:d "M9.954 15.208c-.297-.103-.445-.31-.444-.622 0-.034.012-.065.033-.09.261-.31.536-.223.812-.034a.085.085 0 0 0 .103-.004c.206-.165.525-.253.728-.033.34.37-.113.64-.37.83a.08.08 0 0 0-.032.073l.06.572a.12.12 0 0 1-.028.091c-.155.195-.359.25-.612.168-.389-.126-.196-.58-.187-.86 0-.046-.02-.077-.063-.091Z"})))
37 |
38 | (defnc OllamaAsleep []
39 | ($ :svg {:class ["fill-gray-900" "dark:fill-white" "w-[77px]" "h-[101px]"]
40 | :xmlns "http://www.w3.org/2000/svg"}
41 | ($ :path {:d "M74.203 100.5h-5.787c1.216-4.322 1.189-8.527-.083-12.616-.684-2.193-3.535-5.262-1.921-7.464 5.49-7.497 4.483-15.626.23-23.157-.511-.905-.57-2.096.055-2.862 4.205-5.145 4.985-10.769 2.34-16.873-2.16-4.992-7.187-9.071-12.848-8.886-.979.03-1.914.066-2.806.105a1.12 1.12 0 0 1-1.082-.682c-1.923-4.51-5.301-7.447-10.135-8.81-6.71-1.895-14.127 1.772-17.084 8.002-.448.943-.566 1.499-1.807 1.456-1.631-.058-3.317-.214-4.848.097-10.496 2.139-14.97 15.05-9.54 23.7 1.284 2.042 3.15 2.37 1.363 5.326-4.105 6.802-4.646 16.013.462 22.487 1.752 2.223-1.48 6.05-2.11 8.42-1.006 3.797-.932 7.716.223 11.757H3.013c-1.61-5.582-1.04-12.823 1.962-17.954a.314.314 0 0 0-.008-.354C-.028 74.54.34 64.42 4.384 56.48a.355.355 0 0 0-.021-.375c-4.94-7.45-4.743-17.474.579-24.66 1.174-1.582 2.994-3.228 4.533-4.773a.393.393 0 0 0 .109-.362c-1.107-5.854-.885-11.652.666-17.394C11.261 5.178 14.08.324 18.356.505c6.571.278 9.024 10.267 9.372 15.319a.172.172 0 0 0 .256.139c7.036-4.022 14.07-4.006 21.101.046a.268.268 0 0 0 .403-.215c.28-4.348 1.702-9.8 4.763-13.07 2.063-2.206 5.045-2.897 7.611-1.461 7.024 3.931 6.965 18.657 5.77 25.14a.344.344 0 0 0 .11.336c1.542 1.445 2.543 2.405 3.002 2.88 6.613 6.853 7.124 18.383 2.03 26.335a.34.34 0 0 0-.017.362c4.067 7.981 4.54 18.249-.558 25.935a.385.385 0 0 0 0 .421 15.113 15.113 0 0 1 2.084 4.766c1.099 4.434 1.072 8.788-.08 13.062ZM18.406 6.331a.272.272 0 0 0-.185-.071.256.256 0 0 0-.18.075c-3.589 3.591-3.803 12.503-3.132 17.133.025.177.123.244.294.202a31.325 31.325 0 0 1 6.084-.858.44.44 0 0 0 .323-.156c.436-.5.66-.887.671-1.162.172-4.31-.495-12.035-3.874-15.163Zm40.953.177a.641.641 0 0 0-.965.088c-2.935 4.096-3.606 9.846-3.493 14.738.009.334.225.772.65 1.314a.41.41 0 0 0 .331.168c2.1.053 4.16.363 6.181.93a.176.176 0 0 0 .223-.143c.595-4.31.49-13.803-2.927-17.095Z"})
42 | ($ :path {:d "M50.402 46.979c8.13 7.906 1.732 18.75-8.32 19.448-1.94.135-4.243.143-6.91.026-7.083-.312-13.94-6.698-11.545-14.326 3.451-10.978 19.24-12.477 26.775-5.148Zm-1.25 4.63c-3.376-5.64-11.423-6.524-16.77-3.893-2.897 1.427-5.585 4.411-5.358 7.825.574 8.744 15.205 8.352 19.86 5.296 3.283-2.16 4.281-5.86 2.268-9.227Z"})
43 | ($ :path {:d "M36.782 54.952c-1.146-.398-1.718-1.2-1.715-2.404 0-.127.044-.25.126-.345 1.01-1.2 2.071-.863 3.136-.13a.33.33 0 0 0 .398-.017c.797-.636 2.03-.977 2.814-.127 1.313 1.428-.436 2.472-1.43 3.208a.312.312 0 0 0-.121.278l.226 2.21a.465.465 0 0 1-.105.354c-.598.749-1.386.965-2.365.648-1.501-.488-.755-2.24-.721-3.326.003-.176-.078-.293-.243-.349Z"})
44 | ($ :path {:d "M13.399 43.411a1.726 1.726 0 0 1 2.42-.322l-1.05 1.37 1.05-1.37-.003-.002.001.001.011.009a6.796 6.796 0 0 0 .293.207c.207.142.497.33.826.514.745.419 1.375.642 1.706.642.508 0 1.155-.246 1.79-.622a7.522 7.522 0 0 0 .904-.632l.041-.035.005-.005a1.726 1.726 0 0 1 2.29 2.584l-1.147-1.29 1.146 1.29-.003.002-.003.003-.01.009-.027.023a9.187 9.187 0 0 1-.391.315c-.253.192-.612.448-1.046.705-.822.487-2.116 1.104-3.55 1.104-1.295 0-2.606-.64-3.397-1.084a14.834 14.834 0 0 1-1.492-.965l-.028-.02-.008-.007-.005-.004 1.048-1.371-1.049 1.37a1.726 1.726 0 0 1-.322-2.419Zm42.974-.322.011.008.057.041a11.403 11.403 0 0 0 1.062.68c.744.419 1.374.642 1.706.642.508 0 1.155-.246 1.79-.622a7.522 7.522 0 0 0 .903-.632l.042-.035.002-.002.003-.003a1.726 1.726 0 0 1 2.289 2.584l-1.147-1.29 1.146 1.29-.005.005-.01.009-.027.023a9.187 9.187 0 0 1-.391.315c-.253.192-.612.448-1.046.705-.822.487-2.116 1.104-3.55 1.104-1.295 0-2.607-.64-3.397-1.084a14.834 14.834 0 0 1-1.493-.965l-.027-.02-.009-.007-.003-.003h-.001s-.001-.002 1.047-1.372l-1.048 1.37a1.726 1.726 0 0 1 2.096-2.741Z"})))
45 |
46 | (defnc IconButton
47 | [{:keys [icon on-click disabled? class]
48 | :or {class []}}]
49 | ($ :button {:class (into class conj ["disabled:opacity-50" "p-2.5" "rounded"
50 | "dark:enabled:hover:bg-gray-800/40"
51 | "dark:enabled:hover:text-gray-100"
52 | "dark:text-gray-300/80"
53 | "enabled:hover:bg-gray-200/30"
54 | "enabled:hover:text-gray-700"
55 | "text-gray-600/80"])
56 | :on-click on-click
57 | :disabled disabled?}
58 | ($ icon {:size 20})))
59 |
60 | (defnc Footer []
61 | (let [selected-dialog (use-sub [:selected-dialog])
62 | selected-model (use-sub [:selected-model])
63 | {:keys [generating?]} (use-sub [:dialog selected-dialog])
64 | [prompt set-prompt!] (use-state nil)
65 | [abort set-abort!] (use-state nil)
66 | slowly-set-prompt (debounce set-prompt! 150)
67 | ref! (use-ref nil)
68 | set-height! #(do
69 | (j/assoc-in! @ref! [:style :height] "auto")
70 | (j/assoc-in! @ref!
71 | [:style :height]
72 | (str (min max-textarea-height
73 | (max min-textarea-height
74 | (j/get @ref! :scrollHeight))) "px")))
75 | send! #(do
76 | (dispatch [:send-prompt {:selected-dialog selected-dialog
77 | :prompt %
78 | :set-abort! set-abort!}])
79 | (j/assoc! @ref! :value "")
80 | (set-prompt! nil)
81 | (set-height!))
82 | on-key-press #(when (and (= (j/get % :key) "Enter")
83 | (not (j/get % :shiftKey)))
84 | (j/call % :preventDefault)
85 | (send! (j/get-in % [:target :value])))]
86 |
87 | (use-effect
88 | [@ref! generating?]
89 | (when (and @ref! (not generating?))
90 | (j/call @ref! :focus)))
91 |
92 | ($ :div {:class ["absolute" "bottom-0" "inset-x-0"]}
93 | (when (some? selected-model)
94 | ($ :div {:class ["dark:bg-gray-900" "bg-white" "z-10" "max-w-5xl" "mx-auto" "absolute" "bottom-0" "pb-6" "inset-x-16"]}
95 | (when (and generating? (fn? abort))
96 | ($ :button {:class ["absolute" "right-0" "bottom-20" "mb-3.5"
97 | "z-20" "dark:text-white" "text-gray-700" "text-sm" "flex" "items-center" "p-2" "gap-2"
98 | "bg-gray-300/40" "hover:bg-gray-300/50"
99 | "dark:bg-white/10" "dark:hover:bg-white/20" "rounded" "shadow" "backdrop-blur"]
100 | :on-click abort}
101 | ($ XOctagon {:size 16})
102 | ($ :span {} "Stop")))
103 | ($ :div {:class ["z-0" "absolute" "top-0" "-translate-y-full" "inset-x-0" "h-9"
104 | "bg-gradient-to-t" "dark:from-gray-900" "from-white" "to-transparent" "pointer-events-none"]})
105 | ($ :textarea {:ref ref!
106 | :key selected-dialog
107 | :autoFocus true
108 | :placeholder (str "Send message to " selected-model)
109 | :onChange #(do
110 | (slowly-set-prompt (j/get-in % [:target :value]))
111 | (set-height!))
112 | :onKeyPress on-key-press
113 | :disabled generating?
114 | :rows 1
115 | :class ["w-full" "resize-none" "rounded" "relative" "z-10" "h-12"
116 | "pl-3.5" "pr-10" "py-2.5" "text-base" "font-normal"
117 | "dark:bg-gray-950" "border" "placeholder-gray-400/75"
118 | "dark:border-gray-200/10" "dark:placeholder-gray-300/40" "border-gray-300/60"
119 | "focus:outline-none" "focus:border-cyan-600" "focus:ring-1" "focus:ring-cyan-600"
120 | "disabled:opacity-75"]})
121 |
122 | ($ :button {:class ["absolute" "right-3.5" "bottom-9" "mb-1.5" "z-20" "dark:text-white" "text-gray-700"
123 | (when-not (seq prompt) "opacity-20")]
124 | :on-click #(send! prompt)}
125 | ($ SendHorizontal)))))))
126 |
127 | (defnc Message [{:keys [user? children copy->clipboard]}]
128 | (let [[copied copy!] (use-copy-to-clipboard)]
129 | ($ :div {:class ["max-w-full" "lg:max-w-[85%]" "flex" "gap-3"
130 | (if user? "place-self-end flex-row-reverse" "place-self-start")]}
131 | ($ :div {:class ["shrink-0" "flex" "flex-col"]}
132 | ($ :div {:class ["rounded" "w-10" "h-10" "flex" "justify-center"
133 | (if user?
134 | "items-center dark:bg-gray-700/75 bg-gray-200"
135 | "items-end dark:bg-white bg-gray-800")
136 | (when copy->clipboard "mb-3")]}
137 | (if user?
138 | ($ User)
139 | ($ Ollama)))
140 | (when copy->clipboard
141 | ($ IconButton {:icon (if copied Check Clipboard)
142 | :on-click #(copy! copy->clipboard)})))
143 | ($ :div {:class ["h-fit" "w-full" "rounded-md" "p-4" "flex" "flex-col" "gap-2.5" "overflow-scroll"
144 | (if user?
145 | "dark:bg-black/20 border dark:border-none border-gray-300/50"
146 | "dark:bg-gray-800/50 bg-gray-50 dark:text-white")]}
147 | children))))
148 |
149 | (defnc Markdown [{:keys [children user?]}]
150 | ($ ReactMarkdown
151 | {:children children
152 | :className "markdown-body"
153 | :components
154 | #js{:code
155 | (fn [props]
156 | (let [{:keys [inline className children]} (j/lookup props)
157 | language (second (str/split className #"-"))
158 | [copied copy!] (use-copy-to-clipboard)]
159 | (if (and (not inline)
160 | (seq language))
161 | (<>
162 | ($ :div {:class ["absolute" "right-1.5" "top-1.5" "z-10"]}
163 | ($ IconButton {:icon (if copied Check Clipboard)
164 | :on-click #(copy! (first children))}))
165 | ($ Light {:children (or (first children) "")
166 | :language language
167 | :style (if dark-mode? nord githubGist)
168 | :customStyle #js {:borderRadius "4px"
169 | :padding "16px"}
170 | :className (when user? "border dark:border-none border-gray-300/50")}))
171 | ($ :code {} children))))}}))
172 |
173 | (defnc Exchange [{:keys [dialog-uuid exchange-uuid]}]
174 | (let [{:keys [prompt answer aborted? failed? meta]}
175 | (use-sub [:dialog-exchange {:dialog-uuid dialog-uuid
176 | :exchange-uuid exchange-uuid}])]
177 | ($ :div {:class ["flex" "flex-col" "gap-6" "mt-6"]}
178 | ($ Message {:user? true}
179 | ($ Markdown {:user? true} prompt))
180 | ($ Message {:user? false
181 | :copy->clipboard (:response meta)}
182 | (if answer
183 | ($ Markdown {} answer)
184 | (when-not (or failed? aborted?)
185 | ($ :div {:class ["flex" "flex-col" "gap-2" "animate-pulse" "min-w-[250px]"]}
186 | ($ :div {:class ["h-2" "dark:bg-white/10" "bg-gray-200/75" "rounded"]})
187 | ($ :div {:class ["h-2" "dark:bg-white/10" "bg-gray-200/75" "rounded" "w-[75%]"]}))))
188 | (when aborted?
189 | ($ :p {:class ["dark:text-white/20" "text-sm" "italic"]}
190 | "The answer was stopped before finishing"))
191 | (when failed?
192 | ($ :p {:class ["dark:text-white/20" "text-sm" "italic"]}
193 | "There was an issue finishing this response."))
194 | (when (some? meta)
195 | ($ :p {:class ["dark:text-white/20" "text-gray-300" "text-sm" "mt-2" "italic"]}
196 | (str "Took ~" (j/call js/Math :round (/ (:total_duration meta) 1e+9))
197 | " seconds, at " (j/call js/Math :round (/ (:eval_count meta) (/ (:eval_duration meta) 1e+9)))
198 | " tokens per second.")))))))
199 |
200 | (defnc StartDialog [{:keys [dialog-uuid]}]
201 | (let [models (use-sub [:models])]
202 | ($ :div {:class ["flex" "flex-col" "grow" "w-full"
203 | "justify-center" "items-center"
204 | "py-12" "px-[4.5rem]" "h-full"]}
205 | ($ :h1 {:class ["dark:text-white" "text-2xl"]}
206 | "Start a new Chat")
207 | ($ :h2 {:class ["text-lg" "dark:text-white/40" "text-gray-800/60" "mb-4"]}
208 | "Choose a model to begin your conversation")
209 | ($ :ul {:class ["dark:text-white" "dark:bg-gray-950/30" "bg-white/75" "w-full" "max-w-2xl" "overflow-scroll"
210 | "rounded" "border" "dark:border-gray-700/40" "border-gray-400/50"
211 | "divide-y" "dark:divide-gray-700/50" "divide-gray-400/50"]}
212 | (for [model models]
213 | (let [[model-name model-version] (str/split (:name model) #":")]
214 | ($ :li {:key (:digest model)
215 | :class ["hover:text-white"]}
216 | ($ :button {:class ["text-left" "w-full" "hover:bg-cyan-700" "pl-3" "pr-3.5" "py-2"
217 | "flex" "items-center" "justify-between" "group"]
218 | :on-click #(dispatch [:set-dialog-model dialog-uuid (:name model)])}
219 | ($ :div
220 | ($ :p {:class ["text-lg"]}
221 | model-name
222 | ($ :span {:class ["opacity-50"]} ":" model-version))
223 | ($ :p {:class ["flex" "text-sm" "gap-3"]}
224 | ($ :span {:class ["opacity-60"]}
225 | (formatDistance
226 | (parseISO (:modified_at model))
227 | (new js/Date)
228 | #js {:addSuffix true}))
229 | ($ :span {:class ["opacity-40"]}
230 | (b->gb (:size model)) "GB")
231 | ($ :span {:class ["opacity-20"]}
232 | (subs (:digest model) 0 7))))
233 | ($ :div {:class ["p-1.5" "rounded" "bg-cyan-600" "text-white"
234 | "group-hover:bg-white" "group-hover:text-cyan-600"]}
235 | ($ Plus))))))))))
236 |
237 | (defnc Dialog []
238 | (let [ref! (use-ref nil)
239 | selected-model (use-sub [:selected-model])
240 | selected-dialog (use-sub [:selected-dialog])
241 | exchanges (use-sub [:dialog-exchanges selected-dialog])
242 | {:keys [title]} (use-sub [:dialog-meta selected-dialog])
243 | [model-name model-version] (str/split selected-model #":")
244 | [->top-disabled? set->top-disabled] (use-state true)
245 | [->bottom-disabled? set->bottom-disabled] (use-state true)
246 | ->top #(j/call @ref!
247 | :scrollTo
248 | #js{:top 0 :behavior "smooth"})
249 | ->bottom #(j/call @ref!
250 | :scrollTo
251 | #js{:top (j/get @ref! :scrollHeight)
252 | :behavior "smooth"})
253 | get-scroll-info #(when @ref!
254 | (let [height (-> @ref!
255 | (j/call :getBoundingClientRect)
256 | (j/get :height))
257 | scroll-height (j/get @ref! :scrollHeight)
258 | scroll-top (j/get @ref! :scrollTop)]
259 | {:scroll-top scroll-top
260 | :scroll-bottom (- scroll-height (+ height scroll-top))}))
261 | slow-on-scroll
262 | (debounce (fn []
263 | (let [{:keys [scroll-bottom scroll-top]}
264 | (get-scroll-info)]
265 | (set->top-disabled (not (pos? scroll-top)))
266 | (set->bottom-disabled (not (pos? scroll-bottom)))))
267 | 500)]
268 |
269 | (use-effect
270 | [title model-name]
271 | (j/assoc! js/document :title
272 | (if (seq model-name)
273 | (str model-name " : " (or title "New Chat") " — Chat Ollama")
274 | "Chat Ollama")))
275 |
276 | (use-effect
277 | [@ref!]
278 | (let [slow-change (throttle #(let [{:keys [scroll-bottom]} (get-scroll-info)]
279 | (when (<= scroll-bottom line-height)
280 | (->bottom)))
281 | 250)
282 | observer (new js/MutationObserver slow-change)]
283 | (when (some? @ref!)
284 | (j/call observer :observe @ref! #js{:childList true :subtree true :characterData true}))
285 | #(j/call observer :disconnect)))
286 |
287 | (use-effect
288 | [(count exchanges)]
289 | (when (some? @ref!)
290 | (j/assoc! @ref!
291 | :scrollTop
292 | (j/get @ref! :scrollHeight))))
293 |
294 | (useHotkeys "ctrl+shift+up" ->top)
295 | (useHotkeys "ctrl+shift+down" ->bottom)
296 |
297 | ($ :div {:class ["flex" "flex-col" "relative" "w-full" "h-screen"]}
298 | ($ :div {:class ["dark:block" "hidden" "z-20" "absolute" "top-0" "inset-x-0" "h-9"
299 | "bg-gradient-to-t" "dark:to-gray-900" "to-white" "from-transparent" "pointer-events-none"]})
300 | ($ :div {:class ["absolute" "top-4" "right-4" "z-30" "flex" "flex-col"]}
301 | ($ IconButton {:on-click #(dispatch [:delete-dialog selected-dialog])
302 | :icon Trash2})
303 | ($ IconButton {:on-click ->top
304 | :disabled? ->top-disabled?
305 | :icon ArrowUpToLine})
306 | ($ IconButton {:on-click ->bottom
307 | :disabled? ->bottom-disabled?
308 | :icon ArrowDownToLine}))
309 | ($ :div {:ref ref!
310 | :class ["relative" "grow" "flex" "flex-col" "w-full" "overflow-scroll"]
311 | :on-scroll slow-on-scroll}
312 | (when (some? selected-model)
313 | ($ :p {:class ["text-sm" "dark:text-gray-100" "text-gray-600" "text-center" "p-6"]}
314 | model-name
315 | ($ :span {:class ["opacity-50"]} ":" model-version)))
316 | (if (some? selected-model)
317 | ($ :div {:class ["flex" "flex-col" "w-full" "grow" "max-w-6xl" "mx-auto" "justify-end" "pt-6" "px-20" "pb-28"]}
318 | (for [exchange-uuid exchanges]
319 | ($ Exchange {:key exchange-uuid
320 | :dialog-uuid selected-dialog
321 | :exchange-uuid exchange-uuid})))
322 | ($ StartDialog {:dialog-uuid selected-dialog})))
323 | ($ Footer))))
324 |
325 | (defnc SidebarItem [{:keys [selected? on-click children]}]
326 | (let [class #{"border-transparent"}
327 | selected-class #{"dark:text-white" "cursor-default" "bg-gray-300/20" "dark:bg-gray-800/50" "border-cyan-600"}]
328 | ($ :button {:class (vec (union #{"px-3" "py-1.5" "text-sm" "w-full" "text-left" "rounded"
329 | "dark:hover:bg-gray-800/60" "hover:bg-gray-300/20" "border-l-4"}
330 | (if selected? selected-class class)))
331 | :on-click on-click}
332 | children)))
333 |
334 | (defnc Sidebar [{:keys [toggle-sidebar! style]}]
335 | (let [selected-dialog (use-sub [:selected-dialog])
336 | dialogs (use-sub [:dialog-list])]
337 | ($ :div {:style (j/assoc! style :width sidebar-width)
338 | :class ["dark:bg-gray-950" "bg-gray-50"
339 | "flex" "flex-col" "shrink-0" "p-6"]}
340 | ($ :div {:class ["flex" "items-center" "justify-between" "mb-4"]}
341 | ($ :div {:class ["flex" "items-center" "gap-3"]}
342 | ($ MessagesSquare)
343 | ($ :p {:class ["text-lg"]}
344 | "Chats"
345 | ($ :span {:class ["opacity-50" "ml-2"]}
346 | (count dialogs))))
347 | ($ IconButton {:class ["-m-2"]
348 | :on-click toggle-sidebar!
349 | :icon PanelLeftClose}))
350 | ($ :div {:class ["grow" "overflow-scroll"]}
351 | ($ :ul {:class ["flex" "flex-col" "gap-y-1"]}
352 | (if (seq dialogs)
353 | (for [{:keys [uuid] :as dialog} dialogs]
354 | (let [selected? (= selected-dialog uuid)
355 | [model-name model-version] (str/split (:model-name dialog) #":")]
356 | ($ :li {:key uuid}
357 | ($ SidebarItem {:selected? selected?
358 | :on-click #(do
359 | (dispatch [:set-selected-dialog uuid])
360 | (dispatch [:set-selected-model (:model-name dialog)]))}
361 | ($ :p {:class ["truncate" (when-not (:title dialog) "italic opacity-75")]} (or (:title dialog) "New Chat"))
362 | ($ :div {:class ["flex" "items-center" "justify-between"]}
363 | (when (:model-name dialog)
364 | ($ :p {:class ["text-xs" "dark:text-gray-100" "text-gray-600/80"]}
365 | model-name
366 | ($ :span {:class ["opacity-60" "grow"]} ":" model-version)))
367 | ($ :p {:class ["text-xs" "dark:text-gray-300/50" "text-gray-400"]}
368 | (formatDistance
369 | (fromUnixTime (:timestamp dialog))
370 | (new js/Date)
371 | #js {:addSuffix true})))))))
372 | ($ :p {:class ["dark:text-white/40"]}
373 | (str "No chats found")))))
374 | ($ :button {:on-click #(dispatch [:new-dialog])
375 | :class ["bg-cyan-600" "hover:bg-cyan-700" "text-white" "flex" "px-4" "py-2.5"
376 | "items-center" "rounded" "justify-between"]}
377 | "New Chat"
378 | ($ Plus)))))
379 |
380 | (defnc Offline []
381 | (let [[copied copy!] (use-copy-to-clipboard)
382 | command (if (= "localhost" (j/get js/location :hostname))
383 | "ollama serve"
384 | (str "OLLAMA_ORIGINS=" (j/get js/location :origin) " ollama serve"))]
385 | ($ :div {:class ["flex" "flex-col" "grow" "w-full"
386 | "justify-center" "items-center"
387 | "py-16" "h-full"]}
388 | ($ :div {:class ["flex" "flex-col" "grow" "w-full" "justify-center" "items-center"]}
389 | ($ OllamaAsleep)
390 | ($ :h1 {:class ["dark:text-white" "text-3xl" "mt-6"]}
391 | "Looks like Ollama is asleep!")
392 | ($ :h2 {:class ["text-lg" "dark:text-white/40" "text-gray-800/60" "mb-10"]}
393 | "Ollama Chat requires an active Ollama server to work")
394 | ($ :div {:class ["flex" "items-center" "rounded-md" "bg-gray-100" "dark:bg-white/5" "py-2" "pr-2" "pl-4"
395 | "dark:text-white" "font-mono" "text-sm"]}
396 | ($ :span {:class ["dark:text-white" "opacity-25" "mr-3" "select-none"]} "$")
397 | command
398 | ($ :button {:class ["ml-6" "p-2" "rounded-sm" "dark:hover:bg-gray-900" "hover:bg-gray-200"]
399 | :on-click #(copy! command)}
400 | (if copied
401 | ($ Check {:size 16})
402 | ($ Clipboard {:size 16}))))
403 | ($ IconButton {:class ["mt-12"]
404 | :icon RefreshCw
405 | :on-click #(dispatch [:get-models])})))))
406 |
407 | (defnc Dialogs []
408 | (let [ollama-offline? (use-sub [:ollama-offline?])
409 | selected-dialog (use-sub [:selected-dialog])
410 | ls-sidebar? (local-storage-get (str ls-chat-ollama-prefs "sidebar?"))
411 | [show-sidebar? set-show-sidebar!]
412 | (use-state (if (some? ls-sidebar?)
413 | ls-sidebar?
414 | true))
415 | toggle-sidebar! #(set-show-sidebar! not)
416 | sidebar-props (useSpring #js {:marginLeft
417 | (if show-sidebar?
418 | "0"
419 | (str (- sidebar-width) "px"))})
420 | sidebar-icon-props (useSpring #js {:transform
421 | (if show-sidebar?
422 | (str "translateX(-300%)")
423 | (str "translateX(0%)"))})
424 | AnimatedSidebar (animated Sidebar)]
425 |
426 | (useHotkeys "ctrl+n" #(dispatch [:new-dialog]))
427 | (useHotkeys "ctrl+d" toggle-sidebar!)
428 |
429 | (use-effect
430 | [show-sidebar?]
431 | (local-storage-set!
432 | (str ls-chat-ollama-prefs "sidebar?")
433 | show-sidebar?))
434 |
435 | (if (some? ollama-offline?)
436 | ($ :div {:class ["flex" "dark:text-white" "relative" "w-full"]}
437 | (when ollama-offline?
438 | ($ :div {:class ["absolute" "inset-0" "dark:bg-gray-900/75" "bg-white/30" "backdrop-blur-md" "z-50" "w-full" "h-full"]}
439 | ($ Offline)))
440 |
441 | ($ AnimatedSidebar {:toggle-sidebar! toggle-sidebar!
442 | :style sidebar-props})
443 |
444 | ($ (j/get animated :div)
445 | {:className "absolute top-0 left-0 p-4 z-30"
446 | :style sidebar-icon-props}
447 | ($ IconButton {:on-click toggle-sidebar!
448 | :icon PanelLeftOpen}))
449 | (when (some? selected-dialog)
450 | ($ Dialog)))
451 | (<>))))
452 |
453 | (defnc Main []
454 |
455 | (use-effect
456 | :once
457 | (dispatch [:get-models 2000]))
458 |
459 | ($ :div {:class ["flex" "w-full" "h-full" "relative"]}
460 | ($ Dialogs)))
461 |
--------------------------------------------------------------------------------
/src/css/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @media (prefers-color-scheme: dark) {
6 | .markdown-body {
7 | color-scheme: dark;
8 | --color-prettylights-syntax-comment: #8b949e;
9 | --color-prettylights-syntax-constant: #79c0ff;
10 | --color-prettylights-syntax-entity: #d2a8ff;
11 | --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
12 | --color-prettylights-syntax-entity-tag: #7ee787;
13 | --color-prettylights-syntax-keyword: #ff7b72;
14 | --color-prettylights-syntax-string: #a5d6ff;
15 | --color-prettylights-syntax-variable: #ffa657;
16 | --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
17 | --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
18 | --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
19 | --color-prettylights-syntax-carriage-return-text: #f0f6fc;
20 | --color-prettylights-syntax-carriage-return-bg: #b62324;
21 | --color-prettylights-syntax-string-regexp: #7ee787;
22 | --color-prettylights-syntax-markup-list: #f2cc60;
23 | --color-prettylights-syntax-markup-heading: #1f6feb;
24 | --color-prettylights-syntax-markup-italic: #c9d1d9;
25 | --color-prettylights-syntax-markup-bold: #c9d1d9;
26 | --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
27 | --color-prettylights-syntax-markup-deleted-bg: #67060c;
28 | --color-prettylights-syntax-markup-inserted-text: #aff5b4;
29 | --color-prettylights-syntax-markup-inserted-bg: #033a16;
30 | --color-prettylights-syntax-markup-changed-text: #ffdfb6;
31 | --color-prettylights-syntax-markup-changed-bg: #5a1e02;
32 | --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
33 | --color-prettylights-syntax-markup-ignored-bg: #1158c7;
34 | --color-prettylights-syntax-meta-diff-range: #d2a8ff;
35 | --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
36 | --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
37 | --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
38 | --color-fg-default: #c9d1d9;
39 | --color-fg-muted: #8b949e;
40 | --color-fg-subtle: #6e7681;
41 | --color-canvas-default: #0d1117;
42 | --color-canvas-subtle: #161b22;
43 | --color-border-default: #30363d;
44 | --color-border-muted: #21262d;
45 | --color-neutral-muted: rgba(110, 118, 129, 0.4);
46 | --color-accent-fg: #58a6ff;
47 | --color-accent-emphasis: #1f6feb;
48 | --color-attention-subtle: rgba(187, 128, 9, 0.15);
49 | --color-danger-fg: #f85149;
50 | }
51 | }
52 |
53 | @media (prefers-color-scheme: light) {
54 | .markdown-body {
55 | color-scheme: light;
56 | --color-prettylights-syntax-comment: #6e7781;
57 | --color-prettylights-syntax-constant: #0550ae;
58 | --color-prettylights-syntax-entity: #8250df;
59 | --color-prettylights-syntax-storage-modifier-import: #24292f;
60 | --color-prettylights-syntax-entity-tag: #116329;
61 | --color-prettylights-syntax-keyword: #cf222e;
62 | --color-prettylights-syntax-string: #0a3069;
63 | --color-prettylights-syntax-variable: #953800;
64 | --color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
65 | --color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
66 | --color-prettylights-syntax-invalid-illegal-bg: #82071e;
67 | --color-prettylights-syntax-carriage-return-text: #f6f8fa;
68 | --color-prettylights-syntax-carriage-return-bg: #cf222e;
69 | --color-prettylights-syntax-string-regexp: #116329;
70 | --color-prettylights-syntax-markup-list: #3b2300;
71 | --color-prettylights-syntax-markup-heading: #0550ae;
72 | --color-prettylights-syntax-markup-italic: #24292f;
73 | --color-prettylights-syntax-markup-bold: #24292f;
74 | --color-prettylights-syntax-markup-deleted-text: #82071e;
75 | --color-prettylights-syntax-markup-deleted-bg: #ffebe9;
76 | --color-prettylights-syntax-markup-inserted-text: #116329;
77 | --color-prettylights-syntax-markup-inserted-bg: #dafbe1;
78 | --color-prettylights-syntax-markup-changed-text: #953800;
79 | --color-prettylights-syntax-markup-changed-bg: #ffd8b5;
80 | --color-prettylights-syntax-markup-ignored-text: #eaeef2;
81 | --color-prettylights-syntax-markup-ignored-bg: #0550ae;
82 | --color-prettylights-syntax-meta-diff-range: #8250df;
83 | --color-prettylights-syntax-brackethighlighter-angle: #57606a;
84 | --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
85 | --color-prettylights-syntax-constant-other-reference-link: #0a3069;
86 | --color-fg-default: #24292f;
87 | --color-fg-muted: #57606a;
88 | --color-fg-subtle: #6e7781;
89 | --color-canvas-default: #ffffff;
90 | --color-canvas-subtle: #f6f8fa;
91 | --color-border-default: #d0d7de;
92 | --color-border-muted: hsla(210, 18%, 87%, 1);
93 | --color-neutral-muted: rgba(175, 184, 193, 0.2);
94 | --color-accent-fg: #0969da;
95 | --color-accent-emphasis: #0969da;
96 | --color-attention-subtle: #fff8c5;
97 | --color-danger-fg: #cf222e;
98 | }
99 | }
100 |
101 | .markdown-body {
102 | -ms-text-size-adjust: 100%;
103 | -webkit-text-size-adjust: 100%;
104 | margin: 0;
105 | color: var(--color-fg-default);
106 | /* background-color: var(--color-canvas-default); */
107 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
108 | font-size: 16px;
109 | line-height: 1.5;
110 | word-wrap: break-word;
111 | }
112 |
113 | .markdown-body .octicon {
114 | display: inline-block;
115 | fill: currentColor;
116 | vertical-align: text-bottom;
117 | }
118 |
119 | .markdown-body h1:hover .anchor .octicon-link:before,
120 | .markdown-body h2:hover .anchor .octicon-link:before,
121 | .markdown-body h3:hover .anchor .octicon-link:before,
122 | .markdown-body h4:hover .anchor .octicon-link:before,
123 | .markdown-body h5:hover .anchor .octicon-link:before,
124 | .markdown-body h6:hover .anchor .octicon-link:before {
125 | width: 16px;
126 | height: 16px;
127 | content: ' ';
128 | display: inline-block;
129 | background-color: currentColor;
130 | -webkit-mask-image: url("data:image/svg+xml,");
131 | mask-image: url("data:image/svg+xml,");
132 | }
133 |
134 | .markdown-body details,
135 | .markdown-body figcaption,
136 | .markdown-body figure {
137 | display: block;
138 | }
139 |
140 | .markdown-body summary {
141 | display: list-item;
142 | }
143 |
144 | .markdown-body [hidden] {
145 | display: none !important;
146 | }
147 |
148 | .markdown-body a {
149 | background-color: transparent;
150 | color: var(--color-accent-fg);
151 | text-decoration: none;
152 | }
153 |
154 | .markdown-body abbr[title] {
155 | border-bottom: none;
156 | text-decoration: underline dotted;
157 | }
158 |
159 | .markdown-body b,
160 | .markdown-body strong {
161 | font-weight: var(--base-text-weight-semibold, 600);
162 | }
163 |
164 | .markdown-body dfn {
165 | font-style: italic;
166 | }
167 |
168 | .markdown-body h1 {
169 | margin: .67em 0;
170 | font-weight: var(--base-text-weight-semibold, 600);
171 | padding-bottom: .3em;
172 | font-size: 2em;
173 | border-bottom: 1px solid var(--color-border-muted);
174 | }
175 |
176 | .markdown-body mark {
177 | background-color: var(--color-attention-subtle);
178 | color: var(--color-fg-default);
179 | }
180 |
181 | .markdown-body small {
182 | font-size: 90%;
183 | }
184 |
185 | .markdown-body sub,
186 | .markdown-body sup {
187 | font-size: 75%;
188 | line-height: 0;
189 | position: relative;
190 | vertical-align: baseline;
191 | }
192 |
193 | .markdown-body sub {
194 | bottom: -0.25em;
195 | }
196 |
197 | .markdown-body sup {
198 | top: -0.5em;
199 | }
200 |
201 | .markdown-body img {
202 | border-style: none;
203 | max-width: 100%;
204 | box-sizing: content-box;
205 | background-color: var(--color-canvas-default);
206 | }
207 |
208 | .markdown-body code,
209 | .markdown-body kbd,
210 | .markdown-body pre,
211 | .markdown-body samp {
212 | font-family: monospace;
213 | font-size: 1em;
214 | }
215 |
216 | .markdown-body figure {
217 | margin: 1em 40px;
218 | }
219 |
220 | .markdown-body hr {
221 | box-sizing: content-box;
222 | overflow: hidden;
223 | background: transparent;
224 | border-bottom: 1px solid var(--color-border-muted);
225 | height: .25em;
226 | padding: 0;
227 | margin: 24px 0;
228 | background-color: var(--color-border-default);
229 | border: 0;
230 | }
231 |
232 | .markdown-body input {
233 | font: inherit;
234 | margin: 0;
235 | overflow: visible;
236 | font-family: inherit;
237 | font-size: inherit;
238 | line-height: inherit;
239 | }
240 |
241 | .markdown-body [type=button],
242 | .markdown-body [type=reset],
243 | .markdown-body [type=submit] {
244 | -webkit-appearance: button;
245 | }
246 |
247 | .markdown-body [type=checkbox],
248 | .markdown-body [type=radio] {
249 | box-sizing: border-box;
250 | padding: 0;
251 | }
252 |
253 | .markdown-body [type=number]::-webkit-inner-spin-button,
254 | .markdown-body [type=number]::-webkit-outer-spin-button {
255 | height: auto;
256 | }
257 |
258 | .markdown-body [type=search]::-webkit-search-cancel-button,
259 | .markdown-body [type=search]::-webkit-search-decoration {
260 | -webkit-appearance: none;
261 | }
262 |
263 | .markdown-body ::-webkit-input-placeholder {
264 | color: inherit;
265 | opacity: .54;
266 | }
267 |
268 | .markdown-body ::-webkit-file-upload-button {
269 | -webkit-appearance: button;
270 | font: inherit;
271 | }
272 |
273 | .markdown-body a:hover {
274 | text-decoration: underline;
275 | }
276 |
277 | .markdown-body ::placeholder {
278 | color: var(--color-fg-subtle);
279 | opacity: 1;
280 | }
281 |
282 | .markdown-body hr::before {
283 | display: table;
284 | content: "";
285 | }
286 |
287 | .markdown-body hr::after {
288 | display: table;
289 | clear: both;
290 | content: "";
291 | }
292 |
293 | .markdown-body table {
294 | border-spacing: 0;
295 | border-collapse: collapse;
296 | display: block;
297 | width: max-content;
298 | max-width: 100%;
299 | overflow: auto;
300 | }
301 |
302 | .markdown-body td,
303 | .markdown-body th {
304 | padding: 0;
305 | }
306 |
307 | .markdown-body details summary {
308 | cursor: pointer;
309 | }
310 |
311 | .markdown-body details:not([open])>*:not(summary) {
312 | display: none !important;
313 | }
314 |
315 | .markdown-body a:focus,
316 | .markdown-body [role=button]:focus,
317 | .markdown-body input[type=radio]:focus,
318 | .markdown-body input[type=checkbox]:focus {
319 | outline: 2px solid var(--color-accent-fg);
320 | outline-offset: -2px;
321 | box-shadow: none;
322 | }
323 |
324 | .markdown-body a:focus:not(:focus-visible),
325 | .markdown-body [role=button]:focus:not(:focus-visible),
326 | .markdown-body input[type=radio]:focus:not(:focus-visible),
327 | .markdown-body input[type=checkbox]:focus:not(:focus-visible) {
328 | outline: solid 1px transparent;
329 | }
330 |
331 | .markdown-body a:focus-visible,
332 | .markdown-body [role=button]:focus-visible,
333 | .markdown-body input[type=radio]:focus-visible,
334 | .markdown-body input[type=checkbox]:focus-visible {
335 | outline: 2px solid var(--color-accent-fg);
336 | outline-offset: -2px;
337 | box-shadow: none;
338 | }
339 |
340 | .markdown-body a:not([class]):focus,
341 | .markdown-body a:not([class]):focus-visible,
342 | .markdown-body input[type=radio]:focus,
343 | .markdown-body input[type=radio]:focus-visible,
344 | .markdown-body input[type=checkbox]:focus,
345 | .markdown-body input[type=checkbox]:focus-visible {
346 | outline-offset: 0;
347 | }
348 |
349 | .markdown-body kbd {
350 | display: inline-block;
351 | padding: 3px 5px;
352 | font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
353 | line-height: 10px;
354 | color: var(--color-fg-default);
355 | vertical-align: middle;
356 | background-color: var(--color-canvas-subtle);
357 | border: solid 1px var(--color-neutral-muted);
358 | border-bottom-color: var(--color-neutral-muted);
359 | border-radius: 6px;
360 | box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
361 | }
362 |
363 | .markdown-body h1,
364 | .markdown-body h2,
365 | .markdown-body h3,
366 | .markdown-body h4,
367 | .markdown-body h5,
368 | .markdown-body h6 {
369 | margin-top: 24px;
370 | margin-bottom: 16px;
371 | font-weight: var(--base-text-weight-semibold, 600);
372 | line-height: 1.25;
373 | }
374 |
375 | .markdown-body h2 {
376 | font-weight: var(--base-text-weight-semibold, 600);
377 | padding-bottom: .3em;
378 | font-size: 1.5em;
379 | border-bottom: 1px solid var(--color-border-muted);
380 | }
381 |
382 | .markdown-body h3 {
383 | font-weight: var(--base-text-weight-semibold, 600);
384 | font-size: 1.25em;
385 | }
386 |
387 | .markdown-body h4 {
388 | font-weight: var(--base-text-weight-semibold, 600);
389 | font-size: 1em;
390 | }
391 |
392 | .markdown-body h5 {
393 | font-weight: var(--base-text-weight-semibold, 600);
394 | font-size: .875em;
395 | }
396 |
397 | .markdown-body h6 {
398 | font-weight: var(--base-text-weight-semibold, 600);
399 | font-size: .85em;
400 | color: var(--color-fg-muted);
401 | }
402 |
403 | .markdown-body p {
404 | margin-top: 0;
405 | margin-bottom: 10px;
406 | }
407 |
408 | .markdown-body blockquote {
409 | margin: 0;
410 | padding: 0 1em;
411 | color: var(--color-fg-muted);
412 | border-left: .25em solid var(--color-border-default);
413 | }
414 |
415 | .markdown-body ul,
416 | .markdown-body ol {
417 | margin-top: 0;
418 | margin-bottom: 0;
419 | padding-left: 1rem;
420 | }
421 |
422 | .markdown-body ul {
423 | list-style: disc;
424 | }
425 |
426 | .markdown-body ol {
427 | list-style: decimal;
428 | }
429 |
430 | .markdown-body ol ol,
431 | .markdown-body ul ol {
432 | list-style: lower-roman;
433 | }
434 |
435 | .markdown-body ul ul ol,
436 | .markdown-body ul ol ol,
437 | .markdown-body ol ul ol,
438 | .markdown-body ol ol ol {
439 | list-style: lower-alpha;
440 | }
441 |
442 | .markdown-body dd {
443 | margin-left: 0;
444 | }
445 |
446 | .markdown-body tt,
447 | .markdown-body code,
448 | .markdown-body samp {
449 | font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
450 | font-size: 12px;
451 | }
452 |
453 | .markdown-body .octicon {
454 | display: inline-block;
455 | overflow: visible !important;
456 | vertical-align: text-bottom;
457 | fill: currentColor;
458 | }
459 |
460 | .markdown-body input::-webkit-outer-spin-button,
461 | .markdown-body input::-webkit-inner-spin-button {
462 | margin: 0;
463 | -webkit-appearance: none;
464 | appearance: none;
465 | }
466 |
467 | .markdown-body::before {
468 | display: table;
469 | content: "";
470 | }
471 |
472 | .markdown-body::after {
473 | display: table;
474 | clear: both;
475 | content: "";
476 | }
477 |
478 | .markdown-body>*:first-child {
479 | margin-top: 0 !important;
480 | }
481 |
482 | .markdown-body>*:last-child {
483 | margin-bottom: 0 !important;
484 | }
485 |
486 | .markdown-body a:not([href]) {
487 | color: inherit;
488 | text-decoration: none;
489 | }
490 |
491 | .markdown-body .absent {
492 | color: var(--color-danger-fg);
493 | }
494 |
495 | .markdown-body .anchor {
496 | float: left;
497 | padding-right: 4px;
498 | margin-left: -20px;
499 | line-height: 1;
500 | }
501 |
502 | .markdown-body .anchor:focus {
503 | outline: none;
504 | }
505 |
506 | .markdown-body p,
507 | .markdown-body blockquote,
508 | .markdown-body ul,
509 | .markdown-body ol,
510 | .markdown-body dl,
511 | .markdown-body table,
512 | .markdown-body>pre,
513 | .markdown-body details {
514 | margin-top: 0;
515 | margin-bottom: 16px;
516 | }
517 |
518 | .markdown-body blockquote>:first-child {
519 | margin-top: 0;
520 | }
521 |
522 | .markdown-body blockquote>:last-child {
523 | margin-bottom: 0;
524 | }
525 |
526 | .markdown-body h1 .octicon-link,
527 | .markdown-body h2 .octicon-link,
528 | .markdown-body h3 .octicon-link,
529 | .markdown-body h4 .octicon-link,
530 | .markdown-body h5 .octicon-link,
531 | .markdown-body h6 .octicon-link {
532 | color: var(--color-fg-default);
533 | vertical-align: middle;
534 | visibility: hidden;
535 | }
536 |
537 | .markdown-body h1:hover .anchor,
538 | .markdown-body h2:hover .anchor,
539 | .markdown-body h3:hover .anchor,
540 | .markdown-body h4:hover .anchor,
541 | .markdown-body h5:hover .anchor,
542 | .markdown-body h6:hover .anchor {
543 | text-decoration: none;
544 | }
545 |
546 | .markdown-body h1:hover .anchor .octicon-link,
547 | .markdown-body h2:hover .anchor .octicon-link,
548 | .markdown-body h3:hover .anchor .octicon-link,
549 | .markdown-body h4:hover .anchor .octicon-link,
550 | .markdown-body h5:hover .anchor .octicon-link,
551 | .markdown-body h6:hover .anchor .octicon-link {
552 | visibility: visible;
553 | }
554 |
555 | .markdown-body h1 tt,
556 | .markdown-body h1 code,
557 | .markdown-body h2 tt,
558 | .markdown-body h2 code,
559 | .markdown-body h3 tt,
560 | .markdown-body h3 code,
561 | .markdown-body h4 tt,
562 | .markdown-body h4 code,
563 | .markdown-body h5 tt,
564 | .markdown-body h5 code,
565 | .markdown-body h6 tt,
566 | .markdown-body h6 code {
567 | padding: 0 .2em;
568 | font-size: inherit;
569 | }
570 |
571 | .markdown-body summary h1,
572 | .markdown-body summary h2,
573 | .markdown-body summary h3,
574 | .markdown-body summary h4,
575 | .markdown-body summary h5,
576 | .markdown-body summary h6 {
577 | display: inline-block;
578 | }
579 |
580 | .markdown-body summary h1 .anchor,
581 | .markdown-body summary h2 .anchor,
582 | .markdown-body summary h3 .anchor,
583 | .markdown-body summary h4 .anchor,
584 | .markdown-body summary h5 .anchor,
585 | .markdown-body summary h6 .anchor {
586 | margin-left: -40px;
587 | }
588 |
589 | .markdown-body summary h1,
590 | .markdown-body summary h2 {
591 | padding-bottom: 0;
592 | border-bottom: 0;
593 | }
594 |
595 | .markdown-body ul.no-list,
596 | .markdown-body ol.no-list {
597 | padding: 0;
598 | list-style: none;
599 | }
600 |
601 | .markdown-body ol[type=a] {
602 | list-style: lower-alpha;
603 | }
604 |
605 | .markdown-body ol[type=A] {
606 | list-style: upper-alpha;
607 | }
608 |
609 | .markdown-body ol[type=i] {
610 | list-style: lower-roman;
611 | }
612 |
613 | .markdown-body ol[type=I] {
614 | list-style: upper-roman;
615 | }
616 |
617 | .markdown-body ol[type="1"] {
618 | list-style: decimal;
619 | }
620 |
621 | .markdown-body div>ol:not([type]) {
622 | list-style: decimal;
623 | }
624 |
625 | .markdown-body ul ul,
626 | .markdown-body ul ol,
627 | .markdown-body ol ol,
628 | .markdown-body ol ul {
629 | margin-top: 0;
630 | margin-bottom: 0;
631 | }
632 |
633 | .markdown-body li>p {
634 | margin-top: 16px;
635 | }
636 |
637 | .markdown-body li+li {
638 | margin-top: .25em;
639 | }
640 |
641 | .markdown-body dl {
642 | padding: 0;
643 | }
644 |
645 | .markdown-body dl dt {
646 | padding: 0;
647 | margin-top: 16px;
648 | font-size: 1em;
649 | font-style: italic;
650 | font-weight: var(--base-text-weight-semibold, 600);
651 | }
652 |
653 | .markdown-body dl dd {
654 | padding: 0 16px;
655 | margin-bottom: 16px;
656 | }
657 |
658 | .markdown-body table th {
659 | font-weight: var(--base-text-weight-semibold, 600);
660 | }
661 |
662 | .markdown-body table th,
663 | .markdown-body table td {
664 | padding: 6px 13px;
665 | border: 1px solid var(--color-border-default);
666 | }
667 |
668 | .markdown-body table tr {
669 | background-color: var(--color-canvas-default);
670 | border-top: 1px solid var(--color-border-muted);
671 | }
672 |
673 | .markdown-body table tr:nth-child(2n) {
674 | background-color: var(--color-canvas-subtle);
675 | }
676 |
677 | .markdown-body table img {
678 | background-color: transparent;
679 | }
680 |
681 | .markdown-body img[align=right] {
682 | padding-left: 20px;
683 | }
684 |
685 | .markdown-body img[align=left] {
686 | padding-right: 20px;
687 | }
688 |
689 | .markdown-body .emoji {
690 | max-width: none;
691 | vertical-align: text-top;
692 | background-color: transparent;
693 | }
694 |
695 | .markdown-body span.frame {
696 | display: block;
697 | overflow: hidden;
698 | }
699 |
700 | .markdown-body span.frame>span {
701 | display: block;
702 | float: left;
703 | width: auto;
704 | padding: 7px;
705 | margin: 13px 0 0;
706 | overflow: hidden;
707 | border: 1px solid var(--color-border-default);
708 | }
709 |
710 | .markdown-body span.frame span img {
711 | display: block;
712 | float: left;
713 | }
714 |
715 | .markdown-body span.frame span span {
716 | display: block;
717 | padding: 5px 0 0;
718 | clear: both;
719 | color: var(--color-fg-default);
720 | }
721 |
722 | .markdown-body span.align-center {
723 | display: block;
724 | overflow: hidden;
725 | clear: both;
726 | }
727 |
728 | .markdown-body span.align-center>span {
729 | display: block;
730 | margin: 13px auto 0;
731 | overflow: hidden;
732 | text-align: center;
733 | }
734 |
735 | .markdown-body span.align-center span img {
736 | margin: 0 auto;
737 | text-align: center;
738 | }
739 |
740 | .markdown-body span.align-right {
741 | display: block;
742 | overflow: hidden;
743 | clear: both;
744 | }
745 |
746 | .markdown-body span.align-right>span {
747 | display: block;
748 | margin: 13px 0 0;
749 | overflow: hidden;
750 | text-align: right;
751 | }
752 |
753 | .markdown-body span.align-right span img {
754 | margin: 0;
755 | text-align: right;
756 | }
757 |
758 | .markdown-body span.float-left {
759 | display: block;
760 | float: left;
761 | margin-right: 13px;
762 | overflow: hidden;
763 | }
764 |
765 | .markdown-body span.float-left span {
766 | margin: 13px 0 0;
767 | }
768 |
769 | .markdown-body span.float-right {
770 | display: block;
771 | float: right;
772 | margin-left: 13px;
773 | overflow: hidden;
774 | }
775 |
776 | .markdown-body span.float-right>span {
777 | display: block;
778 | margin: 13px auto 0;
779 | overflow: hidden;
780 | text-align: right;
781 | }
782 |
783 | .markdown-body code,
784 | .markdown-body tt {
785 | padding: .2em .4em;
786 | margin: 0;
787 | font-size: 85%;
788 | white-space: break-spaces;
789 | background-color: var(--color-neutral-muted);
790 | border-radius: 6px;
791 | }
792 |
793 | .markdown-body code br,
794 | .markdown-body tt br {
795 | display: none;
796 | }
797 |
798 | .markdown-body del code {
799 | text-decoration: inherit;
800 | }
801 |
802 | .markdown-body samp {
803 | font-size: 85%;
804 | }
805 |
806 | .markdown-body pre code {
807 | font-size: 100%;
808 | }
809 |
810 | .markdown-body pre>code {
811 | padding: 0;
812 | margin: 0;
813 | word-break: normal;
814 | white-space: pre;
815 | background: transparent;
816 | border: 0;
817 | }
818 |
819 | .markdown-body .highlight {
820 | margin-bottom: 16px;
821 | }
822 |
823 | .markdown-body .highlight pre {
824 | margin-bottom: 0;
825 | word-break: normal;
826 | }
827 |
828 | .markdown-body .highlight pre,
829 | .markdown-body pre {
830 | font-size: 0.875rem;
831 | position: relative;
832 | }
833 |
834 | .markdown-body pre code,
835 | .markdown-body pre tt {
836 | display: inline;
837 | max-width: auto;
838 | padding: 0;
839 | margin: 0;
840 | overflow: visible;
841 | line-height: inherit;
842 | word-wrap: normal;
843 | background-color: transparent;
844 | border: 0;
845 | }
846 |
847 | .markdown-body .csv-data td,
848 | .markdown-body .csv-data th {
849 | padding: 5px;
850 | overflow: hidden;
851 | font-size: 12px;
852 | line-height: 1;
853 | text-align: left;
854 | white-space: nowrap;
855 | }
856 |
857 | .markdown-body .csv-data .blob-num {
858 | padding: 10px 8px 9px;
859 | text-align: right;
860 | background: var(--color-canvas-default);
861 | border: 0;
862 | }
863 |
864 | .markdown-body .csv-data tr {
865 | border-top: 0;
866 | }
867 |
868 | .markdown-body .csv-data th {
869 | font-weight: var(--base-text-weight-semibold, 600);
870 | background: var(--color-canvas-subtle);
871 | border-top: 0;
872 | }
873 |
874 | .markdown-body [data-footnote-ref]::before {
875 | content: "[";
876 | }
877 |
878 | .markdown-body [data-footnote-ref]::after {
879 | content: "]";
880 | }
881 |
882 | .markdown-body .footnotes {
883 | font-size: 12px;
884 | color: var(--color-fg-muted);
885 | border-top: 1px solid var(--color-border-default);
886 | }
887 |
888 | .markdown-body .footnotes ol {
889 | padding-left: 16px;
890 | }
891 |
892 | .markdown-body .footnotes ol ul {
893 | display: inline-block;
894 | padding-left: 16px;
895 | margin-top: 16px;
896 | }
897 |
898 | .markdown-body .footnotes li {
899 | position: relative;
900 | }
901 |
902 | .markdown-body .footnotes li:target::before {
903 | position: absolute;
904 | top: -8px;
905 | right: -8px;
906 | bottom: -8px;
907 | left: -24px;
908 | pointer-events: none;
909 | content: "";
910 | border: 2px solid var(--color-accent-emphasis);
911 | border-radius: 6px;
912 | }
913 |
914 | .markdown-body .footnotes li:target {
915 | color: var(--color-fg-default);
916 | }
917 |
918 | .markdown-body .footnotes .data-footnote-backref g-emoji {
919 | font-family: monospace;
920 | }
921 |
922 | .markdown-body .pl-c {
923 | color: var(--color-prettylights-syntax-comment);
924 | }
925 |
926 | .markdown-body .pl-c1,
927 | .markdown-body .pl-s .pl-v {
928 | color: var(--color-prettylights-syntax-constant);
929 | }
930 |
931 | .markdown-body .pl-e,
932 | .markdown-body .pl-en {
933 | color: var(--color-prettylights-syntax-entity);
934 | }
935 |
936 | .markdown-body .pl-smi,
937 | .markdown-body .pl-s .pl-s1 {
938 | color: var(--color-prettylights-syntax-storage-modifier-import);
939 | }
940 |
941 | .markdown-body .pl-ent {
942 | color: var(--color-prettylights-syntax-entity-tag);
943 | }
944 |
945 | .markdown-body .pl-k {
946 | color: var(--color-prettylights-syntax-keyword);
947 | }
948 |
949 | .markdown-body .pl-s,
950 | .markdown-body .pl-pds,
951 | .markdown-body .pl-s .pl-pse .pl-s1,
952 | .markdown-body .pl-sr,
953 | .markdown-body .pl-sr .pl-cce,
954 | .markdown-body .pl-sr .pl-sre,
955 | .markdown-body .pl-sr .pl-sra {
956 | color: var(--color-prettylights-syntax-string);
957 | }
958 |
959 | .markdown-body .pl-v,
960 | .markdown-body .pl-smw {
961 | color: var(--color-prettylights-syntax-variable);
962 | }
963 |
964 | .markdown-body .pl-bu {
965 | color: var(--color-prettylights-syntax-brackethighlighter-unmatched);
966 | }
967 |
968 | .markdown-body .pl-ii {
969 | color: var(--color-prettylights-syntax-invalid-illegal-text);
970 | background-color: var(--color-prettylights-syntax-invalid-illegal-bg);
971 | }
972 |
973 | .markdown-body .pl-c2 {
974 | color: var(--color-prettylights-syntax-carriage-return-text);
975 | background-color: var(--color-prettylights-syntax-carriage-return-bg);
976 | }
977 |
978 | .markdown-body .pl-sr .pl-cce {
979 | font-weight: bold;
980 | color: var(--color-prettylights-syntax-string-regexp);
981 | }
982 |
983 | .markdown-body .pl-ml {
984 | color: var(--color-prettylights-syntax-markup-list);
985 | }
986 |
987 | .markdown-body .pl-mh,
988 | .markdown-body .pl-mh .pl-en,
989 | .markdown-body .pl-ms {
990 | font-weight: bold;
991 | color: var(--color-prettylights-syntax-markup-heading);
992 | }
993 |
994 | .markdown-body .pl-mi {
995 | font-style: italic;
996 | color: var(--color-prettylights-syntax-markup-italic);
997 | }
998 |
999 | .markdown-body .pl-mb {
1000 | font-weight: bold;
1001 | color: var(--color-prettylights-syntax-markup-bold);
1002 | }
1003 |
1004 | .markdown-body .pl-md {
1005 | color: var(--color-prettylights-syntax-markup-deleted-text);
1006 | background-color: var(--color-prettylights-syntax-markup-deleted-bg);
1007 | }
1008 |
1009 | .markdown-body .pl-mi1 {
1010 | color: var(--color-prettylights-syntax-markup-inserted-text);
1011 | background-color: var(--color-prettylights-syntax-markup-inserted-bg);
1012 | }
1013 |
1014 | .markdown-body .pl-mc {
1015 | color: var(--color-prettylights-syntax-markup-changed-text);
1016 | background-color: var(--color-prettylights-syntax-markup-changed-bg);
1017 | }
1018 |
1019 | .markdown-body .pl-mi2 {
1020 | color: var(--color-prettylights-syntax-markup-ignored-text);
1021 | background-color: var(--color-prettylights-syntax-markup-ignored-bg);
1022 | }
1023 |
1024 | .markdown-body .pl-mdr {
1025 | font-weight: bold;
1026 | color: var(--color-prettylights-syntax-meta-diff-range);
1027 | }
1028 |
1029 | .markdown-body .pl-ba {
1030 | color: var(--color-prettylights-syntax-brackethighlighter-angle);
1031 | }
1032 |
1033 | .markdown-body .pl-sg {
1034 | color: var(--color-prettylights-syntax-sublimelinter-gutter-mark);
1035 | }
1036 |
1037 | .markdown-body .pl-corl {
1038 | text-decoration: underline;
1039 | color: var(--color-prettylights-syntax-constant-other-reference-link);
1040 | }
1041 |
1042 | .markdown-body g-emoji {
1043 | display: inline-block;
1044 | min-width: 1ch;
1045 | font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
1046 | font-size: 1em;
1047 | font-style: normal !important;
1048 | font-weight: var(--base-text-weight-normal, 400);
1049 | line-height: 1;
1050 | vertical-align: -0.075em;
1051 | }
1052 |
1053 | .markdown-body g-emoji img {
1054 | width: 1em;
1055 | height: 1em;
1056 | }
1057 |
1058 | .markdown-body .task-list-item {
1059 | list-style: none;
1060 | }
1061 |
1062 | .markdown-body .task-list-item label {
1063 | font-weight: var(--base-text-weight-normal, 400);
1064 | }
1065 |
1066 | .markdown-body .task-list-item.enabled label {
1067 | cursor: pointer;
1068 | }
1069 |
1070 | .markdown-body .task-list-item+.task-list-item {
1071 | margin-top: 4px;
1072 | }
1073 |
1074 | .markdown-body .task-list-item .handle {
1075 | display: none;
1076 | }
1077 |
1078 | .markdown-body .task-list-item-checkbox {
1079 | margin: 0 .2em .25em -1.4em;
1080 | vertical-align: middle;
1081 | }
1082 |
1083 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
1084 | margin: 0 -1.6em .25em .2em;
1085 | }
1086 |
1087 | .markdown-body .contains-task-list {
1088 | position: relative;
1089 | }
1090 |
1091 | .markdown-body .contains-task-list:hover .task-list-item-convert-container,
1092 | .markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
1093 | display: block;
1094 | width: auto;
1095 | height: 24px;
1096 | overflow: visible;
1097 | clip: auto;
1098 | }
1099 |
1100 | .markdown-body ::-webkit-calendar-picker-indicator {
1101 | filter: invert(50%);
1102 | }
1103 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{html,js,cljs}"],
4 | theme: {
5 | extend: {
6 | "colors": {
7 | "gray": {
8 | 50: "#F3F5F7",
9 | 100: "#F0F1F5",
10 | 200: "#CED3DE",
11 | 300: "#AFB7CA",
12 | 400: "#919CB6",
13 | 500: "#7280A1",
14 | 600: "#586584",
15 | 700: "#434D65",
16 | 800: "#2F3646",
17 | 900: "#1A1E27",
18 | 950: "#161A22"
19 | }
20 | }
21 | }
22 | },
23 | plugins: [],
24 | }
25 |
--------------------------------------------------------------------------------