ttpearso commited on
Commit
3d41420
·
verified ·
1 Parent(s): 2137a95

Upload folder using huggingface_hub

Browse files
.env.example ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ VESPA_APP_TOKEN_URL=https://abcde.z.vespa-app.cloud
2
+ VESPA_APP_MTLS_URL=https://fghjkl.z.vespa-app.cloud
3
+ HF_TOKEN=hf_xxxxxxxxxx
4
+ VESPA_CLOUD_SECRET_TOKEN=vespa_cloud_xxxxxxxx
5
+ GEMINI_API_KEY=
6
+ USE_MTLS=false
7
+ VESPA_CLOUD_MTLS_KEY="-----BEGIN PRIVATE KEY-----
8
+ ...
9
+ -----END PRIVATE KEY-----"
10
+ VESPA_CLOUD_MTLS_CERT="-----BEGIN CERTIFICATE-----
11
+ ...
12
+ -----END CERTIFICATE-----"
13
+ HOT_RELOAD=true
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ src/static/img/colpali_child.png filter=lfs diff=lfs merge=lfs -text
37
+ src/static/img/vespa-colpali.png filter=lfs diff=lfs merge=lfs -text
38
+ src/static/img/visual-retrieval-demoapp-arch.png filter=lfs diff=lfs merge=lfs -text
39
+ src/tailwindcss filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .sesskey
2
+ .venv/
3
+ __pycache__/
4
+ ipynb_checkpoints/
5
+ .python-version
6
+ .env
7
+ template/
8
+ *.json
9
+ output/
10
+ pdfs/
11
+ colpalidemo/
12
+ src/static/full_images/
13
+ src/static/sim_maps/
14
+ embeddings/
15
+ hf_dataset/
README.md CHANGED
@@ -1,12 +1,212 @@
1
- ---
2
- title: Demo
3
- emoji: 👁
4
- colorFrom: blue
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.42.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://assets.vespa.ai/logos/Vespa-logo-green-RGB.svg">
5
+ <source media="(prefers-color-scheme: light)" srcset="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg">
6
+ <img alt="#Vespa" width="200" src="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg" style="margin-bottom: 25px;">
7
+ </picture>
8
+
9
+ # 🔍 Visual Retrieval ColPali 👀
10
+
11
+ This readme contains the code for a web app including a frontend that can be set up as a user facing interface.
12
+
13
+ ## Why?
14
+
15
+ To enable _you_ to showcase the power of ColPali and Vespa to your users, and to provide a starting point for your own projects.
16
+
17
+ > "But I only know Python, how can I create a web app that's not Gradio or Streamlit?" 🤔
18
+
19
+ No worries! This project uses [FastHTML](https://fastht.ml/) to create a beautiful web app - and it's all Python! 🐍
20
+
21
+ Also, 👇
22
+
23
+ <a href="https://imgflip.com/i/98mhch"><img src="https://i.imgflip.com/98mhch.jpg" title="made at imgflip.com" alt="Funny meme about json output in demo"/></a>
24
+
25
+ As a prerequisite, you should run [this notebook](https://pyvespa.readthedocs.io/en/latest/examples/visual_pdf_rag_with_vespa_colpali_cloud.html)
26
+ to prepare the data and deploy the Vespa application.
27
+
28
+ ## Setting up your .env variables
29
+
30
+ The following variables are required in your `.env` file for the application to be able to connect to the Vespa application and the Gemini API:
31
+
32
+ You can rename the `.env.example` file to `.env` and fill in the required values.
33
+ The other variables are optional, if you want to use mTLS authentication against the Vespa application.
34
+
35
+ ```bash
36
+ VESPA_APP_TOKEN_URL=https://abcde.z.vespa-app.cloud
37
+ VESPA_CLOUD_SECRET_TOKEN=vespa_cloud_xxxxxxxx
38
+ GEMINI_API_KEY=asdf
39
+ ```
40
+
41
+ If you want to deploy the application to Huggingface, you also need to set a `HF_TOKEN` variable, with write permissions.
42
+ This is personal, and must be created at [huggingface](https://huggingface.co/settings/tokens).
43
+
44
+ ```bash
45
+ HF_TOKEN=hf_xxxxxxxxxx
46
+ ```
47
+
48
+ ## Setting up python environment
49
+
50
+ This application should work on Python 3.8 and above.
51
+
52
+ You can install the dependencies with `pip`, but we recommend using `uv`.
53
+ Skip to [Installing dependencies using `uv`](#installing-dependencies-using-uv) if you want to use `uv`.
54
+
55
+ ### Installing dependencies using `pip`
56
+
57
+ You can install the dependencies with `pip`:
58
+
59
+ ```bash
60
+ pip install -r src/requirements.txt
61
+ ```
62
+
63
+ ### Installing dependencies using `uv`
64
+
65
+ We recommend installing the amazing `uv` to manage your python environment:
66
+ See also [installation - uv docs](https://docs.astral.sh/uv/getting-started/installation/) for other installation options.
67
+
68
+ ```bash
69
+ curl -LsSf https://astral.sh/uv/install.sh | sh
70
+ ```
71
+
72
+ Then, create a virtual environment:
73
+
74
+ ```bash
75
+ uv venv
76
+ ```
77
+
78
+ Activate the virtual environment:
79
+
80
+ ```bash
81
+ source .venv/bin/activate
82
+ ```
83
+
84
+ Sync your virtual environment with the dependencies:
85
+
86
+ ```bash
87
+ uv sync --extra dev
88
+ ```
89
+
90
+ ## Running the application locally
91
+
92
+ To run the application locally, you can change into the `src` directory and run:
93
+
94
+ ```bash
95
+ python main.py
96
+ ```
97
+
98
+ This will start a local server, and you can access the application at `http://localhost:7860`.
99
+
100
+ ## Deploy to huggingface 🤗 spaces
101
+
102
+ ### Compiling dependencies
103
+
104
+ Before a deploy, make sure to run this to compile the `uv` lock file to `requirements.txt` if you have made changes to the dependencies:
105
+
106
+ ```bash
107
+ uv pip compile pyproject.toml -o src/requirements.txt
108
+ ```
109
+
110
+ This will make sure that the dependencies in your `pyproject.toml` are compiled to the `requirements.txt` file, which is used by the huggingface space.
111
+
112
+ ### Deploying to huggingface
113
+
114
+ Note that you need to set `HF_TOKEN` environment variable first.
115
+ This is personal, and must be created at [huggingface](https://huggingface.co/settings/tokens).
116
+ Make sure the token has `write` access.
117
+ Be aware that this will not delete existing files, only modify or add,
118
+ see [huggingface-cli](https://huggingface.co/docs/huggingface_hub/en/guides/upload#upload-from-the-cli) for more
119
+ information.
120
+
121
+ #### Update your space configuration
122
+
123
+ The `src/README.md` file contains the configuration for the space.
124
+ Feel free to update this file to match your own configuration - name, description, etc.
125
+
126
+ Note that we can actually use the `gradio` SDK of spaces, to serve FastHTML apps as well, as long as we serve the app on port `7860`.
127
+ See [Custom python spaces](https://huggingface.co/docs/hub/en/spaces-sdks-python) for more information.
128
+
129
+ #### Upload the files
130
+
131
+ To deploy, run
132
+
133
+ (Replace `vespa-engine/colpali-vespa-visual-retrieval` with your own huggingface user/repo name, does not need to exist beforehand)
134
+
135
+ ```bash
136
+ huggingface-cli upload vespa-engine/colpali-vespa-visual-retrieval src . --repo-type=space
137
+ ```
138
+
139
+ Note that we upload only the `src` directory.
140
+
141
+ ## Development
142
+
143
+ This section applies if you want to make changes to the web app.
144
+
145
+ ### Adding dependencies
146
+
147
+ To add dependencies, you can add them to the `pyproject.toml` file and run:
148
+
149
+ ```bash
150
+ uv compile
151
+ ```
152
+
153
+ and then sync the dependencies:
154
+
155
+ ```bash
156
+ uv sync --extra dev
157
+ ```
158
+
159
+ ### Making changes to CSS
160
+
161
+ To make changes to output.css apply, run
162
+
163
+ ```bash
164
+ shad4fast watch # watches all files passed through the tailwind.config.js content section
165
+
166
+ shad4fast build # minifies the current output.css file to reduce bundle size in production.
167
+ ```
168
+
169
+ ### Instructions on creating and feeding the full dataset
170
+
171
+ This section is only relevant if you want to create and feed the full dataset to Vespa.
172
+ The notebook referenced in the beginning of this readme should be sufficient if you just want to spin up a scaled down version of the demo.
173
+
174
+ #### Prepare data and Vespa application
175
+
176
+ First, install `uv`:
177
+
178
+ ```bash
179
+ curl -LsSf https://astral.sh/uv/install.sh | sh
180
+ ```
181
+
182
+ Then, run:
183
+
184
+ ```bash
185
+ uv sync --extra dev --extra feed
186
+ ```
187
+
188
+ #### Converting to notebook
189
+
190
+ If you want to run the `prepare_feed_deploy.py` as a notebook, you can convert it using `jupytext`:
191
+
192
+ Convert the `prepare_feed_deploy.py` to notebook to:
193
+
194
+ ```bash
195
+ jupytext --to notebook prepare_feed_deploy.py
196
+ ```
197
+
198
+ And launch a Jupyter instance with:
199
+
200
+ ```bash
201
+ uv run --with jupyter jupyter lab
202
+ ```
203
+
204
+ ## Credits
205
+
206
+ Huge thanks to the amazing projects that made it a joy to create this demo 🙏🙌
207
+
208
+ - Freeing us from python dependency hell - [uv](https://astral.sh/uv/)
209
+ - Allowing us to build **beautiful** full stack web apps in Python [FastHTML](https://fastht.ml/)
210
+ - Introducing the ColPali architecture - [ColPali](https://huggingface.co/vidore/colpali-v1.2)
211
+ - Adding `shadcn` components to FastHTML - [Shad4Fast](https://www.shad4fasthtml.com/)
212
+
application/deployment.xml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <deployment version="1.0">
2
+ <prod>
3
+ <region>aws-us-east-1c</region>
4
+ </prod>
5
+ </deployment>
application/schemas/pdf_page.sd ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ schema pdf_page {
2
+ document pdf_page {
3
+ field id type string {
4
+ indexing: summary | index
5
+ match {
6
+ word
7
+ }
8
+ }
9
+ field url type string {
10
+ indexing: summary | index
11
+ }
12
+ field year type int {
13
+ indexing: summary | attribute
14
+ }
15
+ field title type string {
16
+ indexing: summary | index
17
+ index: enable-bm25
18
+ match {
19
+ text
20
+ }
21
+ }
22
+ field page_number type int {
23
+ indexing: summary | attribute
24
+ }
25
+ field blur_image type raw {
26
+ indexing: summary
27
+ }
28
+ field full_image type raw {
29
+ indexing: summary
30
+ }
31
+ field text type string {
32
+ indexing: summary | index
33
+ index: enable-bm25
34
+ match {
35
+ text
36
+ }
37
+ }
38
+ field embedding type tensor<int8>(patch{}, v[16]) {
39
+ indexing: attribute | index
40
+ attribute {
41
+ distance-metric: hamming
42
+ }
43
+ index {
44
+ hnsw {
45
+ max-links-per-node: 32
46
+ neighbors-to-explore-at-insert: 400
47
+ }
48
+ }
49
+ }
50
+ field questions type array<string> {
51
+ indexing: summary | attribute
52
+ summary: matched-elements-only
53
+ }
54
+ field queries type array<string> {
55
+ indexing: summary | attribute
56
+ summary: matched-elements-only
57
+ }
58
+ }
59
+ fieldset default {
60
+ fields: title, text
61
+ }
62
+ rank-profile bm25 {
63
+ inputs {
64
+ query(qt) tensor<float>(querytoken{}, v[128])
65
+
66
+ }
67
+ function similarities() {
68
+ expression {
69
+
70
+ sum(
71
+ query(qt) * unpack_bits(attribute(embedding)), v
72
+ )
73
+
74
+ }
75
+ }
76
+ function normalized() {
77
+ expression {
78
+
79
+ (similarities - reduce(similarities, min)) / (reduce((similarities - reduce(similarities, min)), max)) * 2 - 1
80
+
81
+ }
82
+ }
83
+ function quantized() {
84
+ expression {
85
+
86
+ cell_cast(normalized * 127.999, int8)
87
+
88
+ }
89
+ }
90
+ first-phase {
91
+ expression {
92
+ bm25(title) + bm25(text)
93
+ }
94
+ }
95
+ }
96
+ rank-profile bm25_sim inherits bm25 {
97
+ first-phase {
98
+ expression {
99
+ bm25(title) + bm25(text)
100
+ }
101
+ }
102
+ summary-features {
103
+ quantized
104
+ }
105
+ }
106
+ rank-profile colpali {
107
+ inputs {
108
+ query(rq0) tensor<int8>(v[16])
109
+ query(rq1) tensor<int8>(v[16])
110
+ query(rq2) tensor<int8>(v[16])
111
+ query(rq3) tensor<int8>(v[16])
112
+ query(rq4) tensor<int8>(v[16])
113
+ query(rq5) tensor<int8>(v[16])
114
+ query(rq6) tensor<int8>(v[16])
115
+ query(rq7) tensor<int8>(v[16])
116
+ query(rq8) tensor<int8>(v[16])
117
+ query(rq9) tensor<int8>(v[16])
118
+ query(rq10) tensor<int8>(v[16])
119
+ query(rq11) tensor<int8>(v[16])
120
+ query(rq12) tensor<int8>(v[16])
121
+ query(rq13) tensor<int8>(v[16])
122
+ query(rq14) tensor<int8>(v[16])
123
+ query(rq15) tensor<int8>(v[16])
124
+ query(rq16) tensor<int8>(v[16])
125
+ query(rq17) tensor<int8>(v[16])
126
+ query(rq18) tensor<int8>(v[16])
127
+ query(rq19) tensor<int8>(v[16])
128
+ query(rq20) tensor<int8>(v[16])
129
+ query(rq21) tensor<int8>(v[16])
130
+ query(rq22) tensor<int8>(v[16])
131
+ query(rq23) tensor<int8>(v[16])
132
+ query(rq24) tensor<int8>(v[16])
133
+ query(rq25) tensor<int8>(v[16])
134
+ query(rq26) tensor<int8>(v[16])
135
+ query(rq27) tensor<int8>(v[16])
136
+ query(rq28) tensor<int8>(v[16])
137
+ query(rq29) tensor<int8>(v[16])
138
+ query(rq30) tensor<int8>(v[16])
139
+ query(rq31) tensor<int8>(v[16])
140
+ query(rq32) tensor<int8>(v[16])
141
+ query(rq33) tensor<int8>(v[16])
142
+ query(rq34) tensor<int8>(v[16])
143
+ query(rq35) tensor<int8>(v[16])
144
+ query(rq36) tensor<int8>(v[16])
145
+ query(rq37) tensor<int8>(v[16])
146
+ query(rq38) tensor<int8>(v[16])
147
+ query(rq39) tensor<int8>(v[16])
148
+ query(rq40) tensor<int8>(v[16])
149
+ query(rq41) tensor<int8>(v[16])
150
+ query(rq42) tensor<int8>(v[16])
151
+ query(rq43) tensor<int8>(v[16])
152
+ query(rq44) tensor<int8>(v[16])
153
+ query(rq45) tensor<int8>(v[16])
154
+ query(rq46) tensor<int8>(v[16])
155
+ query(rq47) tensor<int8>(v[16])
156
+ query(rq48) tensor<int8>(v[16])
157
+ query(rq49) tensor<int8>(v[16])
158
+ query(rq50) tensor<int8>(v[16])
159
+ query(rq51) tensor<int8>(v[16])
160
+ query(rq52) tensor<int8>(v[16])
161
+ query(rq53) tensor<int8>(v[16])
162
+ query(rq54) tensor<int8>(v[16])
163
+ query(rq55) tensor<int8>(v[16])
164
+ query(rq56) tensor<int8>(v[16])
165
+ query(rq57) tensor<int8>(v[16])
166
+ query(rq58) tensor<int8>(v[16])
167
+ query(rq59) tensor<int8>(v[16])
168
+ query(rq60) tensor<int8>(v[16])
169
+ query(rq61) tensor<int8>(v[16])
170
+ query(rq62) tensor<int8>(v[16])
171
+ query(rq63) tensor<int8>(v[16])
172
+ query(qt) tensor<float>(querytoken{}, v[128])
173
+ query(qtb) tensor<int8>(querytoken{}, v[16])
174
+
175
+ }
176
+ function similarities() {
177
+ expression {
178
+
179
+ sum(
180
+ query(qt) * unpack_bits(attribute(embedding)), v
181
+ )
182
+
183
+ }
184
+ }
185
+ function normalized() {
186
+ expression {
187
+
188
+ (similarities - reduce(similarities, min)) / (reduce((similarities - reduce(similarities, min)), max)) * 2 - 1
189
+
190
+ }
191
+ }
192
+ function quantized() {
193
+ expression {
194
+
195
+ cell_cast(normalized * 127.999, int8)
196
+
197
+ }
198
+ }
199
+ function max_sim() {
200
+ expression {
201
+
202
+ sum(
203
+ reduce(
204
+ sum(
205
+ query(qt) * unpack_bits(attribute(embedding)), v
206
+ ),
207
+ max, patch
208
+ ),
209
+ querytoken
210
+ )
211
+
212
+ }
213
+ }
214
+ function max_sim_binary() {
215
+ expression {
216
+
217
+ sum(
218
+ reduce(
219
+ 1 / (1 + sum(
220
+ hamming(query(qtb), attribute(embedding)), v)
221
+ ),
222
+ max, patch
223
+ ),
224
+ querytoken
225
+ )
226
+
227
+ }
228
+ }
229
+ first-phase {
230
+ expression {
231
+ max_sim_binary
232
+ }
233
+ }
234
+ second-phase {
235
+ rerank-count: 10
236
+ expression {
237
+ max_sim
238
+ }
239
+ }
240
+ }
241
+ rank-profile colpali_sim inherits colpali {
242
+ first-phase {
243
+ expression {
244
+ max_sim_binary
245
+ }
246
+ }
247
+ summary-features {
248
+ quantized
249
+ }
250
+ }
251
+ rank-profile hybrid {
252
+ inputs {
253
+ query(rq0) tensor<int8>(v[16])
254
+ query(rq1) tensor<int8>(v[16])
255
+ query(rq2) tensor<int8>(v[16])
256
+ query(rq3) tensor<int8>(v[16])
257
+ query(rq4) tensor<int8>(v[16])
258
+ query(rq5) tensor<int8>(v[16])
259
+ query(rq6) tensor<int8>(v[16])
260
+ query(rq7) tensor<int8>(v[16])
261
+ query(rq8) tensor<int8>(v[16])
262
+ query(rq9) tensor<int8>(v[16])
263
+ query(rq10) tensor<int8>(v[16])
264
+ query(rq11) tensor<int8>(v[16])
265
+ query(rq12) tensor<int8>(v[16])
266
+ query(rq13) tensor<int8>(v[16])
267
+ query(rq14) tensor<int8>(v[16])
268
+ query(rq15) tensor<int8>(v[16])
269
+ query(rq16) tensor<int8>(v[16])
270
+ query(rq17) tensor<int8>(v[16])
271
+ query(rq18) tensor<int8>(v[16])
272
+ query(rq19) tensor<int8>(v[16])
273
+ query(rq20) tensor<int8>(v[16])
274
+ query(rq21) tensor<int8>(v[16])
275
+ query(rq22) tensor<int8>(v[16])
276
+ query(rq23) tensor<int8>(v[16])
277
+ query(rq24) tensor<int8>(v[16])
278
+ query(rq25) tensor<int8>(v[16])
279
+ query(rq26) tensor<int8>(v[16])
280
+ query(rq27) tensor<int8>(v[16])
281
+ query(rq28) tensor<int8>(v[16])
282
+ query(rq29) tensor<int8>(v[16])
283
+ query(rq30) tensor<int8>(v[16])
284
+ query(rq31) tensor<int8>(v[16])
285
+ query(rq32) tensor<int8>(v[16])
286
+ query(rq33) tensor<int8>(v[16])
287
+ query(rq34) tensor<int8>(v[16])
288
+ query(rq35) tensor<int8>(v[16])
289
+ query(rq36) tensor<int8>(v[16])
290
+ query(rq37) tensor<int8>(v[16])
291
+ query(rq38) tensor<int8>(v[16])
292
+ query(rq39) tensor<int8>(v[16])
293
+ query(rq40) tensor<int8>(v[16])
294
+ query(rq41) tensor<int8>(v[16])
295
+ query(rq42) tensor<int8>(v[16])
296
+ query(rq43) tensor<int8>(v[16])
297
+ query(rq44) tensor<int8>(v[16])
298
+ query(rq45) tensor<int8>(v[16])
299
+ query(rq46) tensor<int8>(v[16])
300
+ query(rq47) tensor<int8>(v[16])
301
+ query(rq48) tensor<int8>(v[16])
302
+ query(rq49) tensor<int8>(v[16])
303
+ query(rq50) tensor<int8>(v[16])
304
+ query(rq51) tensor<int8>(v[16])
305
+ query(rq52) tensor<int8>(v[16])
306
+ query(rq53) tensor<int8>(v[16])
307
+ query(rq54) tensor<int8>(v[16])
308
+ query(rq55) tensor<int8>(v[16])
309
+ query(rq56) tensor<int8>(v[16])
310
+ query(rq57) tensor<int8>(v[16])
311
+ query(rq58) tensor<int8>(v[16])
312
+ query(rq59) tensor<int8>(v[16])
313
+ query(rq60) tensor<int8>(v[16])
314
+ query(rq61) tensor<int8>(v[16])
315
+ query(rq62) tensor<int8>(v[16])
316
+ query(rq63) tensor<int8>(v[16])
317
+ query(qt) tensor<float>(querytoken{}, v[128])
318
+ query(qtb) tensor<int8>(querytoken{}, v[16])
319
+
320
+ }
321
+ function similarities() {
322
+ expression {
323
+
324
+ sum(
325
+ query(qt) * unpack_bits(attribute(embedding)), v
326
+ )
327
+
328
+ }
329
+ }
330
+ function normalized() {
331
+ expression {
332
+
333
+ (similarities - reduce(similarities, min)) / (reduce((similarities - reduce(similarities, min)), max)) * 2 - 1
334
+
335
+ }
336
+ }
337
+ function quantized() {
338
+ expression {
339
+
340
+ cell_cast(normalized * 127.999, int8)
341
+
342
+ }
343
+ }
344
+ function max_sim() {
345
+ expression {
346
+
347
+ sum(
348
+ reduce(
349
+ sum(
350
+ query(qt) * unpack_bits(attribute(embedding)), v
351
+ ),
352
+ max, patch
353
+ ),
354
+ querytoken
355
+ )
356
+
357
+ }
358
+ }
359
+ function max_sim_binary() {
360
+ expression {
361
+
362
+ sum(
363
+ reduce(
364
+ 1 / (1 + sum(
365
+ hamming(query(qtb), attribute(embedding)), v)
366
+ ),
367
+ max, patch
368
+ ),
369
+ querytoken
370
+ )
371
+
372
+ }
373
+ }
374
+ first-phase {
375
+ expression {
376
+ max_sim_binary
377
+ }
378
+ }
379
+ second-phase {
380
+ rerank-count: 10
381
+ expression {
382
+ max_sim + 2 * (bm25(text) + bm25(title))
383
+ }
384
+ }
385
+ }
386
+ rank-profile hybrid_sim inherits hybrid {
387
+ first-phase {
388
+ expression {
389
+ max_sim_binary
390
+ }
391
+ }
392
+ summary-features {
393
+ quantized
394
+ }
395
+ }
396
+ document-summary default {
397
+ summary text {
398
+ bolding: on
399
+ }
400
+ summary snippet {
401
+ source: text
402
+ dynamic
403
+ }
404
+ from-disk
405
+ }
406
+ document-summary suggestions {
407
+ summary questions {}
408
+ from-disk
409
+ }
410
+ }
application/search/query-profiles/default.xml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ <query-profile id="default" type="root">
2
+ </query-profile>
application/search/query-profiles/types/root.xml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ <query-profile-type id="root">
2
+ </query-profile-type>
application/services.xml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <services version="1.0">
3
+ <container id="colpalidemo_container" version="1.0">
4
+ <nodes count="2">
5
+ <resources vcpu="4.0" memory="16Gb" architecture="x86_64" storage-type="remote" disk="48Gb"/>
6
+ </nodes>
7
+ <search></search>
8
+ <document-api></document-api>
9
+ <document-processing></document-processing>
10
+ <clients>
11
+ <client id="mtls" permissions="read,write">
12
+ <certificate file="security/clients.pem"></certificate>
13
+ </client>
14
+ <client id="token_write" permissions="read,write">
15
+ <token id="colpalidemo_write"></token>
16
+ </client>
17
+ </clients>
18
+ <config name="container.qr-searchers">
19
+ <tag>
20
+ <bold>
21
+ <open>&lt;strong&gt;</open>
22
+ <close>&lt;/strong&gt;</close>
23
+ </bold>
24
+ <separator>...</separator>
25
+ </tag>
26
+ </config>
27
+ </container>
28
+ <content id="colpalidemo_content" version="1.0">
29
+ <redundancy>2</redundancy>
30
+ <documents>
31
+ <document type="pdf_page" mode="index"></document>
32
+ </documents>
33
+ <nodes count="2">
34
+ <resources vcpu="8.0" memory="16Gb" architecture="arm64" storage-type="local" disk="474Gb"/>
35
+ </nodes>
36
+ <engine>
37
+ <proton>
38
+ <tuning>
39
+ <searchnode>
40
+ <requestthreads>
41
+ <persearch>4</persearch>
42
+ </requestthreads>
43
+ </searchnode>
44
+ </tuning>
45
+ </proton>
46
+ </engine>
47
+ <config name="vespa.config.search.summary.juniperrc">
48
+ <max_matches>2</max_matches>
49
+ <length>1000</length>
50
+ <surround_max>500</surround_max>
51
+ <min_length>300</min_length>
52
+ </config>
53
+ </content>
54
+ </services>
prepare_feed_deploy.py ADDED
@@ -0,0 +1,995 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # # Visual PDF Retrieval - demo application
2
+ #
3
+ # In this notebook, we will prepare the Vespa backend application for our visual retrieval demo.
4
+ # We will use ColPali as the model to extract patch vectors from images of pdf pages.
5
+ # At query time, we use MaxSim to retrieve and/or (based on the configuration) rank the page results.
6
+ #
7
+ # To see the application in action, visit https://huggingface.co/spaces/vespa-engine/colpali-vespa-visual-retrieval
8
+ #
9
+ # The web application is written in FastHTML, meaning the complete application is written in python.
10
+ #
11
+ # The steps we will take in this notebook are:
12
+ #
13
+ # 0. Setup and configuration
14
+ # 1. Download the data
15
+ # 2. Prepare the data
16
+ # 3. Generate queries for evaluation and typeahead search suggestions
17
+ # 4. Deploy the Vespa application
18
+ # 5. Create the Vespa application
19
+ # 6. Feed the data to the Vespa application
20
+ #
21
+ # All the steps that are needed to provision the Vespa application, including feeding the data, can be done from this notebook.
22
+ # We have tried to make it easy for others to run this notebook, to create your own PDF Enterprise Search application using Vespa.
23
+ #
24
+
25
+ # ## 0. Setup and Configuration
26
+ #
27
+
28
+ # +
29
+ import os
30
+ import asyncio
31
+ import json
32
+ from typing import Tuple
33
+ import hashlib
34
+ import numpy as np
35
+
36
+ # Vespa
37
+ from vespa.package import (
38
+ ApplicationPackage,
39
+ Field,
40
+ Schema,
41
+ Document,
42
+ HNSW,
43
+ RankProfile,
44
+ Function,
45
+ FieldSet,
46
+ SecondPhaseRanking,
47
+ Summary,
48
+ DocumentSummary,
49
+ )
50
+ from vespa.deployment import VespaCloud
51
+ from vespa.application import Vespa
52
+ from vespa.io import VespaResponse
53
+
54
+ # Google Generative AI
55
+ import google.generativeai as genai
56
+
57
+ # Torch and other ML libraries
58
+ import torch
59
+ from torch.utils.data import DataLoader
60
+ from tqdm import tqdm
61
+ from pdf2image import convert_from_path
62
+ from pypdf import PdfReader
63
+
64
+ # ColPali model and processor
65
+ from colpali_engine.models import ColPali, ColPaliProcessor
66
+ from colpali_engine.utils.torch_utils import get_torch_device
67
+ from vidore_benchmark.utils.image_utils import scale_image, get_base64_image
68
+
69
+ # Other utilities
70
+ from bs4 import BeautifulSoup
71
+ import httpx
72
+ from urllib.parse import urljoin, urlparse
73
+
74
+ # Load environment variables
75
+ from dotenv import load_dotenv
76
+
77
+ load_dotenv()
78
+
79
+ # Avoid warning from huggingface tokenizers
80
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
81
+ # -
82
+
83
+ # ### Create a free trial in Vespa Cloud
84
+ #
85
+ # Create a tenant from [here](https://vespa.ai/free-trial/).
86
+ # The trial includes $300 credit.
87
+ # Take note of your tenant name.
88
+ #
89
+
90
+ VESPA_TENANT_NAME = "vespa-team"
91
+
92
+ # Here, set your desired application name. (Will be created in later steps)
93
+ # Note that you can not have hyphen `-` or underscore `_` in the application name.
94
+ #
95
+
96
+ VESPA_APPLICATION_NAME = "colpalidemo"
97
+ VESPA_SCHEMA_NAME = "pdf_page"
98
+
99
+ # Next, you need to create some tokens for feeding data, and querying the application.
100
+ # We recommend separate tokens for feeding and querying, (the former with write permission, and the latter with read permission).
101
+ # The tokens can be created from the [Vespa Cloud console](https://console.vespa-cloud.com/) in the 'Account' -> 'Tokens' section.
102
+ #
103
+
104
+ VESPA_TOKEN_ID_WRITE = "colpalidemo_write"
105
+
106
+ # We also need to set the value of the write token to be able to feed data to the Vespa application.
107
+ #
108
+
109
+ VESPA_CLOUD_SECRET_TOKEN = os.getenv("VESPA_CLOUD_SECRET_TOKEN") or input(
110
+ "Enter Vespa cloud secret token: "
111
+ )
112
+
113
+ # We will also use the Gemini API to create sample queries for our images.
114
+ # You can also use other VLM's to create these queries.
115
+ # Create a Gemini API key from [here](https://aistudio.google.com/app/apikey).
116
+ #
117
+
118
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or input(
119
+ "Enter Google Generative AI API key: "
120
+ )
121
+
122
+ # +
123
+ MODEL_NAME = "vidore/colpali-v1.2"
124
+
125
+ # Configure Google Generative AI
126
+ genai.configure(api_key=GEMINI_API_KEY)
127
+
128
+ # Set device for Torch
129
+ device = get_torch_device("auto")
130
+ print(f"Using device: {device}")
131
+
132
+ # Load the ColPali model and processor
133
+ model = ColPali.from_pretrained(
134
+ MODEL_NAME,
135
+ torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
136
+ device_map=device,
137
+ ).eval()
138
+
139
+ processor = ColPaliProcessor.from_pretrained(MODEL_NAME)
140
+ # -
141
+
142
+ # ## 1. Download PDFs
143
+ #
144
+ # We are going to use public reports from the Norwegian Government Pension Fund Global (also known as the Oil Fund).
145
+ # The fund puts transparency at the forefront and publishes reports on its investments, holdings, and returns, as well as its strategy and governance.
146
+ #
147
+ # These reports are the ones we are going to use for this showcase.
148
+ # Here are some sample images:
149
+ #
150
+ # ![Sample1](./static/img/gfpg-sample-1.png)
151
+ # ![Sample2](./static/img/gfpg-sample-2.png)
152
+ #
153
+
154
+ # As we can see, a lot of the information is in the form of tables, charts and numbers.
155
+ # These are not easily extractable using pdf-readers or OCR tools.
156
+ #
157
+
158
+ # +
159
+ import requests
160
+
161
+ url = "https://www.nbim.no/en/publications/reports/"
162
+ response = requests.get(url)
163
+ response.raise_for_status()
164
+ html_content = response.text
165
+
166
+ # Parse with BeautifulSoup
167
+ soup = BeautifulSoup(html_content, "html.parser")
168
+
169
+ links = []
170
+ url_to_year = {}
171
+
172
+ # Find all the years
173
+ for year_section in soup.find_all("section", attrs={"data-name": "report-year"}):
174
+ year_id = year_section.get("data-filter-section", "")
175
+ year = year_id.replace("year-", "")
176
+
177
+ # Find the yearly report section
178
+ report_section = year_section.find("div", attrs={
179
+ "data-filter-section": "year",
180
+ "data-name": "report-type"
181
+ })
182
+ if not report_section:
183
+ continue
184
+
185
+ # Get the first link
186
+ report_link = report_section.select_one("ul.link-list a")
187
+ if not report_link:
188
+ continue
189
+
190
+ report_url = urljoin(url, report_link["href"])
191
+
192
+ # Visit the report page to find PDF download links
193
+ try:
194
+ report_response = requests.get(report_url)
195
+ report_response.raise_for_status()
196
+ report_soup = BeautifulSoup(report_response.text, "html.parser")
197
+
198
+ # Find only the first PDF download link with the specific class
199
+ pdf_link = report_soup.select_one("a.btn.btn-secondary[data-right-icon='download']")
200
+ if not pdf_link or not pdf_link["href"].endswith(".pdf"):
201
+ continue
202
+
203
+ pdf_url = urljoin(report_url, pdf_link["href"])
204
+ links.append(pdf_url)
205
+ url_to_year[pdf_url] = year
206
+ print(f"Found PDF: {pdf_url} (Year: {year})")
207
+ except Exception as e:
208
+ print(f"Error fetching report page {report_url}: {e}")
209
+
210
+ print(f"Found {len(links)} PDF links")
211
+ # -
212
+
213
+ # Limit the number of PDFs to download
214
+ NUM_PDFS = 2 # Set to None to download all PDFs
215
+ links = links[:NUM_PDFS] if NUM_PDFS else links
216
+ links
217
+
218
+ # +
219
+ from nest_asyncio import apply
220
+ from typing import List
221
+
222
+ apply()
223
+
224
+ max_attempts = 3
225
+
226
+
227
+ async def download_pdf(session, url, filename):
228
+ attempt = 0
229
+ while attempt < max_attempts:
230
+ try:
231
+ response = await session.get(url)
232
+ response.raise_for_status()
233
+
234
+ # Use Content-Disposition header to get the filename if available
235
+ content_disposition = response.headers.get("Content-Disposition")
236
+ if content_disposition:
237
+ import re
238
+
239
+ fname = re.findall('filename="(.+)"', content_disposition)
240
+ if fname:
241
+ filename = fname[0]
242
+
243
+ # Ensure the filename is safe to use on the filesystem
244
+ safe_filename = filename.replace("/", "_").replace("\\", "_")
245
+ if not safe_filename or safe_filename == "_":
246
+ print(f"Invalid filename: {filename}")
247
+ continue
248
+
249
+ filepath = os.path.join("pdfs", safe_filename)
250
+ with open(filepath, "wb") as f:
251
+ f.write(response.content)
252
+ print(f"Downloaded {safe_filename}")
253
+ return filepath
254
+ except Exception as e:
255
+ print(f"Error downloading {filename}: {e}")
256
+ print(f"Retrying ({attempt})...")
257
+ await asyncio.sleep(1) # Wait a bit before retrying
258
+ attempt += 1
259
+ return None
260
+
261
+
262
+ async def download_pdfs(links: List[str]) -> List[dict]:
263
+ """Download PDFs from a list of URLs. Add the filename to the dictionary."""
264
+ async with httpx.AsyncClient() as client:
265
+ tasks = []
266
+
267
+ for idx, link in enumerate(links):
268
+ # Try to get the filename from the URL
269
+ path = urlparse(link).path
270
+ filename = os.path.basename(path)
271
+
272
+ # If filename is empty,skip
273
+ if not filename:
274
+ continue
275
+ tasks.append(download_pdf(client, link, filename))
276
+
277
+ # Run the tasks concurrently
278
+ paths = await asyncio.gather(*tasks)
279
+ pdf_files = [
280
+ {"url": link, "path": path} for link, path in zip(links, paths) if path
281
+ ]
282
+ return pdf_files
283
+
284
+
285
+ # Create the pdfs directory if it doesn't exist
286
+ os.makedirs("pdfs", exist_ok=True)
287
+ # Now run the download_pdfs function with the URL
288
+ pdfs = asyncio.run(download_pdfs(links))
289
+ # -
290
+
291
+ pdfs
292
+
293
+ # ## 2. Convert PDFs to Images
294
+ #
295
+
296
+
297
+ # +
298
+ def get_pdf_images(pdf_path):
299
+ reader = PdfReader(pdf_path)
300
+ page_texts = []
301
+ for page_number in range(len(reader.pages)):
302
+ page = reader.pages[page_number]
303
+ text = page.extract_text()
304
+ page_texts.append(text)
305
+ images = convert_from_path(pdf_path)
306
+ # Convert to PIL images
307
+ assert len(images) == len(page_texts)
308
+ return images, page_texts
309
+
310
+
311
+ pdf_folder = "pdfs"
312
+ pdf_pages = []
313
+ for pdf in tqdm(pdfs):
314
+ pdf_file = pdf["path"]
315
+ title = os.path.splitext(os.path.basename(pdf_file))[0]
316
+ images, texts = get_pdf_images(pdf_file)
317
+ for page_no, (image, text) in enumerate(zip(images, texts)):
318
+ pdf_pages.append(
319
+ {
320
+ "title": title,
321
+ "year": int(url_to_year[pdf["url"]]),
322
+ "url": pdf["url"],
323
+ "path": pdf_file,
324
+ "image": image,
325
+ "text": text,
326
+ "page_no": page_no,
327
+ }
328
+ )
329
+ # -
330
+
331
+ len(pdf_pages)
332
+
333
+ # +
334
+ from collections import Counter
335
+
336
+ # Print the length of the text fields - mean, max and min
337
+ text_lengths = [len(page["text"]) for page in pdf_pages]
338
+ print(f"Mean text length: {np.mean(text_lengths)}")
339
+ print(f"Max text length: {np.max(text_lengths)}")
340
+ print(f"Min text length: {np.min(text_lengths)}")
341
+ print(f"Median text length: {np.median(text_lengths)}")
342
+ print(f"Number of text with length == 0: {Counter(text_lengths)[0]}")
343
+ # -
344
+
345
+ # ## 3. Generate Queries
346
+ #
347
+ # In this step, we want to generate queries for each page image.
348
+ # These will be useful for 2 reasons:
349
+ #
350
+ # 1. We can use these queries as typeahead suggestions in the search bar.
351
+ # 2. We can use the queries to generate an evaluation dataset. See [Improving Retrieval with LLM-as-a-judge](https://blog.vespa.ai/improving-retrieval-with-llm-as-a-judge/) for a deeper dive into this topic.
352
+ #
353
+ # The prompt for generating queries is taken from [this](https://danielvanstrien.xyz/posts/post-with-code/colpali/2024-09-23-generate_colpali_dataset.html#an-update-retrieval-focused-prompt) wonderful blog post by Daniel van Strien.
354
+ #
355
+ # We will use the Gemini API to generate these queries, with `gemini-1.5-flash-8b` as the model.
356
+ #
357
+
358
+ # +
359
+ from pydantic import BaseModel
360
+
361
+
362
+ class GeneratedQueries(BaseModel):
363
+ broad_topical_question: str
364
+ broad_topical_query: str
365
+ specific_detail_question: str
366
+ specific_detail_query: str
367
+ visual_element_question: str
368
+ visual_element_query: str
369
+
370
+
371
+ def get_retrieval_prompt() -> Tuple[str, GeneratedQueries]:
372
+ prompt = (
373
+ prompt
374
+ ) = """You are an investor, stock analyst and financial expert. You will be presented an image of a document page from a report published by the Norwegian Government Pension Fund Global (GPFG). The report may be annual or quarterly reports, or policy reports, on topics such as responsible investment, risk etc.
375
+ Your task is to generate retrieval queries and questions that you would use to retrieve this document (or ask based on this document) in a large corpus.
376
+ Please generate 3 different types of retrieval queries and questions.
377
+ A retrieval query is a keyword based query, made up of 2-5 words, that you would type into a search engine to find this document.
378
+ A question is a natural language question that you would ask, for which the document contains the answer.
379
+ The queries should be of the following types:
380
+ 1. A broad topical query: This should cover the main subject of the document.
381
+ 2. A specific detail query: This should cover a specific detail or aspect of the document.
382
+ 3. A visual element query: This should cover a visual element of the document, such as a chart, graph, or image.
383
+
384
+ Important guidelines:
385
+ - Ensure the queries are relevant for retrieval tasks, not just describing the page content.
386
+ - Use a fact-based natural language style for the questions.
387
+ - Frame the queries as if someone is searching for this document in a large corpus.
388
+ - Make the queries diverse and representative of different search strategies.
389
+
390
+ Format your response as a JSON object with the structure of the following example:
391
+ {
392
+ "broad_topical_question": "What was the Responsible Investment Policy in 2019?",
393
+ "broad_topical_query": "responsible investment policy 2019",
394
+ "specific_detail_question": "What is the percentage of investments in renewable energy?",
395
+ "specific_detail_query": "renewable energy investments percentage",
396
+ "visual_element_question": "What is the trend of total holding value over time?",
397
+ "visual_element_query": "total holding value trend"
398
+ }
399
+
400
+ If there are no relevant visual elements, provide an empty string for the visual element question and query.
401
+ Here is the document image to analyze:
402
+ Generate the queries based on this image and provide the response in the specified JSON format.
403
+ Only return JSON. Don't return any extra explanation text. """
404
+
405
+ return prompt, GeneratedQueries
406
+
407
+
408
+ prompt_text, pydantic_model = get_retrieval_prompt()
409
+
410
+ # +
411
+ gemini_model = genai.GenerativeModel("gemini-1.5-flash-8b")
412
+
413
+
414
+ def generate_queries(image, prompt_text, pydantic_model):
415
+ try:
416
+ response = gemini_model.generate_content(
417
+ [image, "\n\n", prompt_text],
418
+ generation_config=genai.GenerationConfig(
419
+ response_mime_type="application/json",
420
+ response_schema=pydantic_model,
421
+ ),
422
+ )
423
+ queries = json.loads(response.text)
424
+ except Exception as _e:
425
+ queries = {
426
+ "broad_topical_question": "",
427
+ "broad_topical_query": "",
428
+ "specific_detail_question": "",
429
+ "specific_detail_query": "",
430
+ "visual_element_question": "",
431
+ "visual_element_query": "",
432
+ }
433
+ return queries
434
+
435
+
436
+ # -
437
+
438
+ for pdf in tqdm(pdf_pages):
439
+ image = pdf.get("image")
440
+ pdf["queries"] = generate_queries(image, prompt_text, pydantic_model)
441
+
442
+ pdf_pages[46]["image"]
443
+
444
+ pdf_pages[46]["queries"]
445
+
446
+ # +
447
+ # Generate queries async - keeping for now as we probably need when applying to the full dataset
448
+ # import asyncio
449
+ # from tenacity import retry, stop_after_attempt, wait_exponential
450
+ # import google.generativeai as genai
451
+ # from tqdm.asyncio import tqdm_asyncio
452
+
453
+ # max_in_flight = 200 # Maximum number of concurrent requests
454
+
455
+
456
+ # async def generate_queries_for_image_async(model, image, semaphore):
457
+ # @retry(stop=stop_after_attempt(3), wait=wait_exponential(), reraise=True)
458
+ # async def _generate():
459
+ # async with semaphore:
460
+ # result = await model.generate_content_async(
461
+ # [image, "\n\n", prompt_text],
462
+ # generation_config=genai.GenerationConfig(
463
+ # response_mime_type="application/json",
464
+ # response_schema=pydantic_model,
465
+ # ),
466
+ # )
467
+ # return json.loads(result.text)
468
+
469
+ # try:
470
+ # return await _generate()
471
+ # except Exception as e:
472
+ # print(f"Error generating queries for image: {e}")
473
+ # return None # Return None or handle as needed
474
+
475
+
476
+ # async def enrich_pdfs():
477
+ # gemini_model = genai.GenerativeModel("gemini-1.5-flash-8b")
478
+ # semaphore = asyncio.Semaphore(max_in_flight)
479
+ # tasks = []
480
+ # for pdf in pdf_pages:
481
+ # pdf["queries"] = []
482
+ # image = pdf.get("image")
483
+ # if image:
484
+ # task = generate_queries_for_image_async(gemini_model, image, semaphore)
485
+ # tasks.append((pdf, task))
486
+
487
+ # # Run the tasks concurrently using asyncio.gather()
488
+ # for pdf, task in tqdm_asyncio(tasks):
489
+ # result = await task
490
+ # if result:
491
+ # pdf["queries"] = result
492
+ # return pdf_pages
493
+
494
+
495
+ # pdf_pages = asyncio.run(enrich_pdfs())
496
+
497
+ # +
498
+ # write title, url, page_no, text, queries, not image to JSON
499
+ os.makedirs("output", exist_ok=True)
500
+ with open("output/pdf_pages.json", "w") as f:
501
+ to_write = [{k: v for k, v in pdf.items() if k != "image"} for pdf in pdf_pages]
502
+ json.dump(to_write, f, indent=2)
503
+
504
+ # with open("pdfs/pdf_pages.json", "r") as f:
505
+ # saved_pdf_pages = json.load(f)
506
+ # for pdf, saved_pdf in zip(pdf_pages, saved_pdf_pages):
507
+ # pdf.update(saved_pdf)
508
+ # -
509
+
510
+ # ## 4. Generate embeddings
511
+ #
512
+ # Now that we have the queries, we can use the ColPali model to generate embeddings for each page image.
513
+ #
514
+
515
+
516
+ def generate_embeddings(images, model, processor, batch_size=2) -> np.ndarray:
517
+ """
518
+ Generate embeddings for a list of images.
519
+ Move to CPU only once per batch.
520
+
521
+ Args:
522
+ images (List[PIL.Image]): List of PIL images.
523
+ model (nn.Module): The model to generate embeddings.
524
+ processor: The processor to preprocess images.
525
+ batch_size (int, optional): Batch size for processing. Defaults to 64.
526
+
527
+ Returns:
528
+ np.ndarray: Embeddings for the images, shape
529
+ (len(images), processor.max_patch_length (1030 for ColPali), model.config.hidden_size (Patch embedding dimension - 128 for ColPali)).
530
+ """
531
+ embeddings_list = []
532
+
533
+ def collate_fn(batch):
534
+ # Batch is a list of images
535
+ return processor.process_images(batch) # Should return a dict of tensors
536
+
537
+ dataloader = DataLoader(
538
+ images,
539
+ shuffle=False,
540
+ collate_fn=collate_fn,
541
+ )
542
+
543
+ for batch_doc in tqdm(dataloader, desc="Generating embeddings"):
544
+ with torch.no_grad():
545
+ # Move batch to the device
546
+ batch_doc = {k: v.to(model.device) for k, v in batch_doc.items()}
547
+ embeddings_batch = model(**batch_doc)
548
+ embeddings_list.append(torch.unbind(embeddings_batch.to("cpu"), dim=0))
549
+ # Concatenate all embeddings and create a numpy array
550
+ all_embeddings = np.concatenate(embeddings_list, axis=0)
551
+ return all_embeddings
552
+
553
+
554
+ # Generate embeddings for all images
555
+ images = [pdf["image"] for pdf in pdf_pages]
556
+ embeddings = generate_embeddings(images, model, processor)
557
+
558
+ embeddings.shape
559
+
560
+ # ## 5. Prepare Data on Vespa Format
561
+ #
562
+ # Now, that we have all the data we need, all that remains is to make sure it is in the right format for Vespa.
563
+ #
564
+
565
+
566
+ def float_to_binary_embedding(float_query_embedding: dict) -> dict:
567
+ """Utility function to convert float query embeddings to binary query embeddings."""
568
+ binary_query_embeddings = {}
569
+ for k, v in float_query_embedding.items():
570
+ binary_vector = (
571
+ np.packbits(np.where(np.array(v) > 0, 1, 0)).astype(np.int8).tolist()
572
+ )
573
+ binary_query_embeddings[k] = binary_vector
574
+ return binary_query_embeddings
575
+
576
+
577
+ vespa_feed = []
578
+ for pdf, embedding in zip(pdf_pages, embeddings):
579
+ url = pdf["url"]
580
+ year = pdf["year"]
581
+ title = pdf["title"]
582
+ image = pdf["image"]
583
+ text = pdf.get("text", "")
584
+ page_no = pdf["page_no"]
585
+ query_dict = pdf["queries"]
586
+ questions = [v for k, v in query_dict.items() if "question" in k and v]
587
+ queries = [v for k, v in query_dict.items() if "query" in k and v]
588
+ base_64_image = get_base64_image(
589
+ scale_image(image, 32), add_url_prefix=False
590
+ ) # Scaled down image to return fast on search (~1kb)
591
+ base_64_full_image = get_base64_image(image, add_url_prefix=False)
592
+ embedding_dict = {k: v for k, v in enumerate(embedding)}
593
+ binary_embedding = float_to_binary_embedding(embedding_dict)
594
+ # id_hash should be md5 hash of url and page_number
595
+ id_hash = hashlib.md5(f"{url}_{page_no}".encode()).hexdigest()
596
+ page = {
597
+ "id": id_hash,
598
+ "fields": {
599
+ "id": id_hash,
600
+ "url": url,
601
+ "title": title,
602
+ "year": year,
603
+ "page_number": page_no,
604
+ "blur_image": base_64_image,
605
+ "full_image": base_64_full_image,
606
+ "text": text,
607
+ "embedding": binary_embedding,
608
+ "queries": queries,
609
+ "questions": questions,
610
+ },
611
+ }
612
+ vespa_feed.append(page)
613
+
614
+ # +
615
+ # We will prepare the Vespa feed data, including the embeddings and the generated queries
616
+
617
+
618
+ # Save vespa_feed to vespa_feed.json
619
+ with open("output/vespa_feed.json", "w") as f:
620
+ vespa_feed_to_save = []
621
+ for page in vespa_feed:
622
+ document_id = page["id"]
623
+ put_id = f"id:{VESPA_APPLICATION_NAME}:{VESPA_SCHEMA_NAME}::{document_id}"
624
+ vespa_feed_to_save.append({"put": put_id, "fields": page["fields"]})
625
+ json.dump(vespa_feed_to_save, f)
626
+
627
+ # +
628
+ # import json
629
+
630
+ # with open("output/vespa_feed.json", "r") as f:
631
+ # vespa_feed = json.load(f)
632
+ # -
633
+
634
+ len(vespa_feed)
635
+
636
+ # ## 5. Prepare Vespa Application
637
+ #
638
+
639
+ # +
640
+ # Define the Vespa schema
641
+ colpali_schema = Schema(
642
+ name=VESPA_SCHEMA_NAME,
643
+ document=Document(
644
+ fields=[
645
+ Field(
646
+ name="id",
647
+ type="string",
648
+ indexing=["summary", "index"],
649
+ match=["word"],
650
+ ),
651
+ Field(name="url", type="string", indexing=["summary", "index"]),
652
+ Field(name="year", type="int", indexing=["summary", "attribute"]),
653
+ Field(
654
+ name="title",
655
+ type="string",
656
+ indexing=["summary", "index"],
657
+ match=["text"],
658
+ index="enable-bm25",
659
+ ),
660
+ Field(name="page_number", type="int", indexing=["summary", "attribute"]),
661
+ Field(name="blur_image", type="raw", indexing=["summary"]),
662
+ Field(name="full_image", type="raw", indexing=["summary"]),
663
+ Field(
664
+ name="text",
665
+ type="string",
666
+ indexing=["summary", "index"],
667
+ match=["text"],
668
+ index="enable-bm25",
669
+ ),
670
+ Field(
671
+ name="embedding",
672
+ type="tensor<int8>(patch{}, v[16])",
673
+ indexing=[
674
+ "attribute",
675
+ "index",
676
+ ],
677
+ ann=HNSW(
678
+ distance_metric="hamming",
679
+ max_links_per_node=32,
680
+ neighbors_to_explore_at_insert=400,
681
+ ),
682
+ ),
683
+ Field(
684
+ name="questions",
685
+ type="array<string>",
686
+ indexing=["summary", "attribute"],
687
+ summary=Summary(fields=["matched-elements-only"]),
688
+ ),
689
+ Field(
690
+ name="queries",
691
+ type="array<string>",
692
+ indexing=["summary", "attribute"],
693
+ summary=Summary(fields=["matched-elements-only"]),
694
+ ),
695
+ ]
696
+ ),
697
+ fieldsets=[
698
+ FieldSet(
699
+ name="default",
700
+ fields=["title", "text"],
701
+ ),
702
+ ],
703
+ document_summaries=[
704
+ DocumentSummary(
705
+ name="default",
706
+ summary_fields=[
707
+ Summary(
708
+ name="text",
709
+ fields=[("bolding", "on")],
710
+ ),
711
+ Summary(
712
+ name="snippet",
713
+ fields=[("source", "text"), "dynamic"],
714
+ ),
715
+ ],
716
+ from_disk=True,
717
+ ),
718
+ DocumentSummary(
719
+ name="suggestions",
720
+ summary_fields=[
721
+ Summary(name="questions"),
722
+ ],
723
+ from_disk=True,
724
+ ),
725
+ ],
726
+ )
727
+
728
+ # Define similarity functions used in all rank profiles
729
+ mapfunctions = [
730
+ Function(
731
+ name="similarities", # computes similarity scores between each query token and image patch
732
+ expression="""
733
+ sum(
734
+ query(qt) * unpack_bits(attribute(embedding)), v
735
+ )
736
+ """,
737
+ ),
738
+ Function(
739
+ name="normalized", # normalizes the similarity scores to [-1, 1]
740
+ expression="""
741
+ (similarities - reduce(similarities, min)) / (reduce((similarities - reduce(similarities, min)), max)) * 2 - 1
742
+ """,
743
+ ),
744
+ Function(
745
+ name="quantized", # quantizes the normalized similarity scores to signed 8-bit integers [-128, 127]
746
+ expression="""
747
+ cell_cast(normalized * 127.999, int8)
748
+ """,
749
+ ),
750
+ ]
751
+
752
+ # Define the 'bm25' rank profile
753
+ bm25 = RankProfile(
754
+ name="bm25",
755
+ inputs=[("query(qt)", "tensor<float>(querytoken{}, v[128])")],
756
+ first_phase="bm25(title) + bm25(text)",
757
+ functions=mapfunctions,
758
+ )
759
+
760
+
761
+ # A function to create an inherited rank profile which also returns quantized similarity scores
762
+ def with_quantized_similarity(rank_profile: RankProfile) -> RankProfile:
763
+ return RankProfile(
764
+ name=f"{rank_profile.name}_sim",
765
+ first_phase=rank_profile.first_phase,
766
+ inherits=rank_profile.name,
767
+ summary_features=["quantized"],
768
+ )
769
+
770
+
771
+ colpali_schema.add_rank_profile(bm25)
772
+ colpali_schema.add_rank_profile(with_quantized_similarity(bm25))
773
+
774
+
775
+ # Update the 'colpali' rank profile
776
+ input_query_tensors = []
777
+ MAX_QUERY_TERMS = 64
778
+ for i in range(MAX_QUERY_TERMS):
779
+ input_query_tensors.append((f"query(rq{i})", "tensor<int8>(v[16])"))
780
+
781
+ input_query_tensors.extend(
782
+ [
783
+ ("query(qt)", "tensor<float>(querytoken{}, v[128])"),
784
+ ("query(qtb)", "tensor<int8>(querytoken{}, v[16])"),
785
+ ]
786
+ )
787
+
788
+ colpali = RankProfile(
789
+ name="colpali",
790
+ inputs=input_query_tensors,
791
+ first_phase="max_sim_binary",
792
+ second_phase=SecondPhaseRanking(expression="max_sim", rerank_count=10),
793
+ functions=mapfunctions
794
+ + [
795
+ Function(
796
+ name="max_sim",
797
+ expression="""
798
+ sum(
799
+ reduce(
800
+ sum(
801
+ query(qt) * unpack_bits(attribute(embedding)), v
802
+ ),
803
+ max, patch
804
+ ),
805
+ querytoken
806
+ )
807
+ """,
808
+ ),
809
+ Function(
810
+ name="max_sim_binary",
811
+ expression="""
812
+ sum(
813
+ reduce(
814
+ 1 / (1 + sum(
815
+ hamming(query(qtb), attribute(embedding)), v)
816
+ ),
817
+ max, patch
818
+ ),
819
+ querytoken
820
+ )
821
+ """,
822
+ ),
823
+ ],
824
+ )
825
+ colpali_schema.add_rank_profile(colpali)
826
+ colpali_schema.add_rank_profile(with_quantized_similarity(colpali))
827
+
828
+ # Update the 'hybrid' rank profile
829
+ hybrid = RankProfile(
830
+ name="hybrid",
831
+ inputs=input_query_tensors,
832
+ first_phase="max_sim_binary",
833
+ second_phase=SecondPhaseRanking(
834
+ expression="max_sim + 2 * (bm25(text) + bm25(title))", rerank_count=10
835
+ ),
836
+ functions=mapfunctions
837
+ + [
838
+ Function(
839
+ name="max_sim",
840
+ expression="""
841
+ sum(
842
+ reduce(
843
+ sum(
844
+ query(qt) * unpack_bits(attribute(embedding)), v
845
+ ),
846
+ max, patch
847
+ ),
848
+ querytoken
849
+ )
850
+ """,
851
+ ),
852
+ Function(
853
+ name="max_sim_binary",
854
+ expression="""
855
+ sum(
856
+ reduce(
857
+ 1 / (1 + sum(
858
+ hamming(query(qtb), attribute(embedding)), v)
859
+ ),
860
+ max, patch
861
+ ),
862
+ querytoken
863
+ )
864
+ """,
865
+ ),
866
+ ],
867
+ )
868
+ colpali_schema.add_rank_profile(hybrid)
869
+ colpali_schema.add_rank_profile(with_quantized_similarity(hybrid))
870
+
871
+ # +
872
+ from vespa.configuration.services import (
873
+ services,
874
+ container,
875
+ search,
876
+ document_api,
877
+ document_processing,
878
+ clients,
879
+ client,
880
+ config,
881
+ content,
882
+ redundancy,
883
+ documents,
884
+ node,
885
+ certificate,
886
+ token,
887
+ document,
888
+ nodes,
889
+ )
890
+ from vespa.configuration.vt import vt
891
+ from vespa.package import ServicesConfiguration
892
+
893
+ service_config = ServicesConfiguration(
894
+ application_name=VESPA_APPLICATION_NAME,
895
+ services_config=services(
896
+ container(
897
+ search(),
898
+ document_api(),
899
+ document_processing(),
900
+ clients(
901
+ client(
902
+ certificate(file="security/clients.pem"),
903
+ id="mtls",
904
+ permissions="read,write",
905
+ ),
906
+ client(
907
+ token(id=f"{VESPA_TOKEN_ID_WRITE}"),
908
+ id="token_write",
909
+ permissions="read,write",
910
+ ),
911
+ ),
912
+ config(
913
+ vt("tag")(
914
+ vt("bold")(
915
+ vt("open", "<strong>"),
916
+ vt("close", "</strong>"),
917
+ ),
918
+ vt("separator", "..."),
919
+ ),
920
+ name="container.qr-searchers",
921
+ ),
922
+ id=f"{VESPA_APPLICATION_NAME}_container",
923
+ version="1.0",
924
+ ),
925
+ content(
926
+ redundancy("1"),
927
+ documents(document(type="pdf_page", mode="index")),
928
+ nodes(node(distribution_key="0", hostalias="node1")),
929
+ config(
930
+ vt("max_matches", "2", replace_underscores=False),
931
+ vt("length", "1000"),
932
+ vt("surround_max", "500", replace_underscores=False),
933
+ vt("min_length", "300", replace_underscores=False),
934
+ name="vespa.config.search.summary.juniperrc",
935
+ ),
936
+ id=f"{VESPA_APPLICATION_NAME}_content",
937
+ version="1.0",
938
+ ),
939
+ version="1.0",
940
+ ),
941
+ )
942
+ # -
943
+
944
+ # Create the Vespa application package
945
+ vespa_application_package = ApplicationPackage(
946
+ name=VESPA_APPLICATION_NAME,
947
+ schema=[colpali_schema],
948
+ services_config=service_config,
949
+ )
950
+
951
+ # ## 6. Deploy Vespa Application
952
+ #
953
+
954
+ VESPA_TEAM_API_KEY = os.getenv("VESPA_TEAM_API_KEY") or input(
955
+ "Enter Vespa team API key: "
956
+ )
957
+
958
+ # +
959
+ vespa_cloud = VespaCloud(
960
+ tenant=VESPA_TENANT_NAME,
961
+ application=VESPA_APPLICATION_NAME,
962
+ key_content=VESPA_TEAM_API_KEY,
963
+ application_package=vespa_application_package,
964
+ )
965
+
966
+ # Deploy the application
967
+ vespa_cloud.deploy()
968
+
969
+ # Output the endpoint URL
970
+ endpoint_url = vespa_cloud.get_token_endpoint()
971
+ print(f"Application deployed. Token endpoint URL: {endpoint_url}")
972
+ # -
973
+
974
+ # Make sure to take note of the token endpoint_url.
975
+ # You need to put this in your `.env` file - `VESPA_APP_URL=https://abcd.vespa-app.cloud` - to access the Vespa application from your web application.
976
+ #
977
+
978
+ # ## 8. Feed Data to Vespa
979
+ #
980
+
981
+ # Instantiate Vespa connection using token
982
+ app = Vespa(url=endpoint_url, vespa_cloud_secret_token=VESPA_CLOUD_SECRET_TOKEN)
983
+ app.get_application_status()
984
+
985
+
986
+ # +
987
+ def callback(response: VespaResponse, id: str):
988
+ if not response.is_successful():
989
+ print(
990
+ f"Failed to feed document {id} with status code {response.status_code}: Reason {response.get_json()}"
991
+ )
992
+
993
+
994
+ # Feed data into Vespa asynchronously
995
+ app.feed_async_iterable(vespa_feed, schema=VESPA_SCHEMA_NAME, callback=callback)
pyproject.toml ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "visual-retrieval-colpali"
3
+ version = "0.1.0"
4
+ description = "Visual retrieval with ColPali"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10, <3.12"
7
+ license = { text = "Apache-2.0" }
8
+ dependencies = [
9
+ "python-fasthtml",
10
+ "huggingface-hub",
11
+ "pyvespa>=0.50.0",
12
+ "vespacli",
13
+ "torch",
14
+ "vidore-benchmark[interpretability]>=4.0.0,<5.0.0",
15
+ "colpali-engine",
16
+ "einops",
17
+ "pypdf",
18
+ "setuptools",
19
+ "python-dotenv",
20
+ "shad4fast>=1.2.1",
21
+ "google-generativeai>=0.7.2",
22
+ "spacy",
23
+ "pip",
24
+ ]
25
+
26
+ # dev-dependencies
27
+ [project.optional-dependencies]
28
+ dev = ["ruff", "python-dotenv", "huggingface_hub[cli]"]
29
+ feed = [
30
+ "ipykernel",
31
+ "jupytext",
32
+ "pydantic",
33
+ "beautifulsoup4",
34
+ "pdf2image",
35
+ "google-generativeai",
36
+ ]
37
+ [tool.ruff]
38
+ # Exclude a variety of commonly ignored directories.
39
+ exclude = [
40
+ ".bzr",
41
+ ".direnv",
42
+ ".eggs",
43
+ ".git",
44
+ ".git-rewrite",
45
+ ".hg",
46
+ ".ipynb_checkpoints",
47
+ ".mypy_cache",
48
+ ".nox",
49
+ ".pants.d",
50
+ ".pyenv",
51
+ ".pytest_cache",
52
+ ".pytype",
53
+ ".ruff_cache",
54
+ ".svn",
55
+ ".tox",
56
+ ".venv",
57
+ ".vscode",
58
+ "__pypackages__",
59
+ "_build",
60
+ "buck-out",
61
+ "build",
62
+ "dist",
63
+ "node_modules",
64
+ "site-packages",
65
+ "venv",
66
+ ]
67
+
68
+ # Same as Black.
69
+ line-length = 88
70
+ indent-width = 4
71
+
72
+ # Assume Python 3.8
73
+ target-version = "py38"
74
+
75
+ [tool.ruff.lint]
76
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
77
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
78
+ # McCabe complexity (`C901`) by default.
79
+ select = ["E4", "E7", "E9", "F"]
80
+ ignore = []
81
+
82
+ # Allow fix for all enabled rules (when `--fix`) is provided.
83
+ fixable = ["ALL"]
84
+ unfixable = []
85
+
86
+ # Allow unused variables when underscore-prefixed.
87
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
88
+
89
+ [tool.ruff.format]
90
+ # Like Black, use double quotes for strings.
91
+ quote-style = "double"
92
+
93
+ # Like Black, indent with spaces, rather than tabs.
94
+ indent-style = "space"
95
+
96
+ # Like Black, respect magic trailing commas.
97
+ skip-magic-trailing-comma = false
98
+
99
+ # Like Black, automatically detect the appropriate line ending.
100
+ line-ending = "auto"
101
+
102
+ # Enable auto-formatting of code examples in docstrings. Markdown,
103
+ # reStructuredText code/literal blocks and doctests are all supported.
104
+ #
105
+ # This is currently disabled by default, but it is planned for this
106
+ # to be opt-out in the future.
107
+ docstring-code-format = false
108
+
109
+ # Set the line length limit used when formatting code snippets in
110
+ # docstrings.
111
+ #
112
+ # This only has an effect when the `docstring-code-format` setting is
113
+ # enabled.
114
+ docstring-code-line-length = "dynamic"
src/README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ColPali 🤝 Vespa - Visual Retrieval
3
+ short_description: Visual Retrieval with ColPali and Vespa
4
+ emoji: 👀
5
+ colorFrom: purple
6
+ colorTo: blue
7
+ sdk: gradio
8
+ sdk_version: 4.44.0
9
+ app_file: main.py
10
+ pinned: false
11
+ license: apache-2.0
12
+ suggested_hardware: t4-small
13
+ models:
14
+ - vidore/colpaligemma-3b-pt-448-base
15
+ - vidore/colpali-v1.2
16
+ preload_from_hub:
17
+ - vidore/colpaligemma-3b-pt-448-base config.json,model-00001-of-00002.safetensors,model-00002-of-00002.safetensors,model.safetensors.index.json,preprocessor_config.json,special_tokens_map.json,tokenizer.json,tokenizer_config.json 12c59eb7e23bc4c26876f7be7c17760d5d3a1ffa
18
+ - vidore/colpali-v1.2 adapter_config.json,adapter_model.safetensors,preprocessor_config.json,special_tokens_map.json,tokenizer.json,tokenizer_config.json 9912ce6f8a462d8cf2269f5606eabbd2784e764f
19
+ ---
src/backend/__init__.py ADDED
File without changes
src/backend/cache.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import OrderedDict
2
+
3
+
4
+ # Initialize LRU Cache
5
+ class LRUCache:
6
+ def __init__(self, max_size=20):
7
+ self.max_size = max_size
8
+ self.cache = OrderedDict()
9
+
10
+ def get(self, key):
11
+ if key in self.cache:
12
+ self.cache.move_to_end(key)
13
+ return self.cache[key]
14
+ return None
15
+
16
+ def set(self, key, value):
17
+ if key in self.cache:
18
+ self.cache.move_to_end(key)
19
+ else:
20
+ if len(self.cache) >= self.max_size:
21
+ self.cache.popitem(last=False)
22
+ self.cache[key] = value
23
+
24
+ def delete(self, key):
25
+ if key in self.cache:
26
+ del self.cache[key]
src/backend/colpali.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from PIL import Image
3
+ import numpy as np
4
+ from typing import Generator, Tuple, List, Union, Dict
5
+ from pathlib import Path
6
+ import base64
7
+ from io import BytesIO
8
+ import re
9
+ import io
10
+ import matplotlib.cm as cm
11
+
12
+ from colpali_engine.models import ColPali, ColPaliProcessor
13
+ from colpali_engine.utils.torch_utils import get_torch_device
14
+ from vidore_benchmark.interpretability.torch_utils import (
15
+ normalize_similarity_map_per_query_token,
16
+ )
17
+ from functools import lru_cache
18
+ import logging
19
+
20
+
21
+ class SimMapGenerator:
22
+ """
23
+ Generates similarity maps based on query embeddings and image patches using the ColPali model.
24
+ """
25
+
26
+ colormap = cm.get_cmap("viridis") # Preload colormap for efficiency
27
+
28
+ def __init__(
29
+ self,
30
+ logger: logging.Logger,
31
+ model_name: str = "vidore/colpali-v1.2",
32
+ n_patch: int = 32,
33
+ ):
34
+ """
35
+ Initializes the SimMapGenerator class with a specified model and patch dimension.
36
+
37
+ Args:
38
+ model_name (str): The model name for loading the ColPali model.
39
+ n_patch (int): The number of patches per dimension.
40
+ """
41
+ self.model_name = model_name
42
+ self.n_patch = n_patch
43
+ self.device = get_torch_device("auto")
44
+ self.logger = logger
45
+ self.logger.info(f"Using device: {self.device}")
46
+ self.model, self.processor = self.load_model()
47
+
48
+ def load_model(self) -> Tuple[ColPali, ColPaliProcessor]:
49
+ """
50
+ Loads the ColPali model and processor.
51
+
52
+ Returns:
53
+ Tuple[ColPali, ColPaliProcessor]: Loaded model and processor.
54
+ """
55
+ model = ColPali.from_pretrained(
56
+ self.model_name,
57
+ torch_dtype=torch.bfloat16, # Note that the embeddings created during feed were float32 -> binarized, yet setting this seem to produce the most similar results both locally (mps) and HF (Cuda)
58
+ device_map=self.device,
59
+ ).eval()
60
+
61
+ processor = ColPaliProcessor.from_pretrained(self.model_name)
62
+ return model, processor
63
+
64
+ def gen_similarity_maps(
65
+ self,
66
+ query: str,
67
+ query_embs: torch.Tensor,
68
+ token_idx_map: Dict[int, str],
69
+ images: List[Union[Path, str]],
70
+ vespa_sim_maps: List[Dict],
71
+ ) -> Generator[Tuple[int, str, str], None, None]:
72
+ """
73
+ Generates similarity maps for the provided images and query, and returns base64-encoded blended images.
74
+
75
+ Args:
76
+ query (str): The query string.
77
+ query_embs (torch.Tensor): Query embeddings tensor.
78
+ token_idx_map (dict): Mapping from indices to tokens.
79
+ images (List[Union[Path, str]]): List of image paths or base64-encoded strings.
80
+ vespa_sim_maps (List[Dict]): List of Vespa similarity maps.
81
+
82
+ Yields:
83
+ Tuple[int, str, str]: A tuple containing the image index, selected token, and base64-encoded image.
84
+ """
85
+ processed_images, original_images, original_sizes = [], [], []
86
+ for img in images:
87
+ img_pil = self._load_image(img)
88
+ original_images.append(img_pil.copy())
89
+ original_sizes.append(img_pil.size)
90
+ processed_images.append(img_pil)
91
+
92
+ vespa_sim_map_tensor = self._prepare_similarity_map_tensor(
93
+ query_embs, vespa_sim_maps
94
+ )
95
+ similarity_map_normalized = normalize_similarity_map_per_query_token(
96
+ vespa_sim_map_tensor
97
+ )
98
+
99
+ for idx, img in enumerate(original_images):
100
+ for token_idx, token in token_idx_map.items():
101
+ if self.should_filter_token(token):
102
+ continue
103
+
104
+ sim_map = similarity_map_normalized[idx, token_idx, :, :]
105
+ blended_img_base64 = self._blend_image(
106
+ img, sim_map, original_sizes[idx]
107
+ )
108
+ yield idx, token, token_idx, blended_img_base64
109
+
110
+ def _load_image(self, img: Union[Path, str]) -> Image:
111
+ """
112
+ Loads an image from a file path or a base64-encoded string.
113
+
114
+ Args:
115
+ img (Union[Path, str]): The image to load.
116
+
117
+ Returns:
118
+ Image: The loaded PIL image.
119
+ """
120
+ try:
121
+ if isinstance(img, Path):
122
+ return Image.open(img).convert("RGB")
123
+ elif isinstance(img, str):
124
+ return Image.open(BytesIO(base64.b64decode(img))).convert("RGB")
125
+ except Exception as e:
126
+ raise ValueError(f"Failed to load image: {e}")
127
+
128
+ def _prepare_similarity_map_tensor(
129
+ self, query_embs: torch.Tensor, vespa_sim_maps: List[Dict]
130
+ ) -> torch.Tensor:
131
+ """
132
+ Prepares a similarity map tensor from Vespa similarity maps.
133
+
134
+ Args:
135
+ query_embs (torch.Tensor): Query embeddings tensor.
136
+ vespa_sim_maps (List[Dict]): List of Vespa similarity maps.
137
+
138
+ Returns:
139
+ torch.Tensor: The prepared similarity map tensor.
140
+ """
141
+ vespa_sim_map_tensor = torch.zeros(
142
+ (len(vespa_sim_maps), query_embs.size(1), self.n_patch, self.n_patch)
143
+ )
144
+ for idx, vespa_sim_map in enumerate(vespa_sim_maps):
145
+ for cell in vespa_sim_map["quantized"]["cells"]:
146
+ patch = int(cell["address"]["patch"])
147
+ query_token = int(cell["address"]["querytoken"])
148
+ value = cell["value"]
149
+ if hasattr(self.processor, "image_seq_length"):
150
+ image_seq_length = self.processor.image_seq_length
151
+ else:
152
+ image_seq_length = 1024
153
+
154
+ if patch >= image_seq_length:
155
+ continue
156
+ vespa_sim_map_tensor[
157
+ idx,
158
+ query_token,
159
+ patch // self.n_patch,
160
+ patch % self.n_patch,
161
+ ] = value
162
+ return vespa_sim_map_tensor
163
+
164
+ def _blend_image(
165
+ self, img: Image, sim_map: torch.Tensor, original_size: Tuple[int, int]
166
+ ) -> str:
167
+ """
168
+ Blends an image with a similarity map and encodes it to base64.
169
+
170
+ Args:
171
+ img (Image): The original image.
172
+ sim_map (torch.Tensor): The similarity map tensor.
173
+ original_size (Tuple[int, int]): The original size of the image.
174
+
175
+ Returns:
176
+ str: The base64-encoded blended image.
177
+ """
178
+ SCALING_FACTOR = 8
179
+ sim_map_resolution = (
180
+ max(32, int(original_size[0] / SCALING_FACTOR)),
181
+ max(32, int(original_size[1] / SCALING_FACTOR)),
182
+ )
183
+
184
+ sim_map_np = sim_map.cpu().float().numpy()
185
+ sim_map_img = Image.fromarray(sim_map_np).resize(
186
+ sim_map_resolution, resample=Image.BICUBIC
187
+ )
188
+ sim_map_resized_np = np.array(sim_map_img, dtype=np.float32)
189
+ sim_map_normalized = self._normalize_sim_map(sim_map_resized_np)
190
+
191
+ heatmap = self.colormap(sim_map_normalized)
192
+ heatmap_img = Image.fromarray((heatmap * 255).astype(np.uint8)).convert("RGBA")
193
+
194
+ buffer = io.BytesIO()
195
+ heatmap_img.save(buffer, format="PNG")
196
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
197
+
198
+ @staticmethod
199
+ def _normalize_sim_map(sim_map: np.ndarray) -> np.ndarray:
200
+ """
201
+ Normalizes a similarity map to range [0, 1].
202
+
203
+ Args:
204
+ sim_map (np.ndarray): The similarity map.
205
+
206
+ Returns:
207
+ np.ndarray: The normalized similarity map.
208
+ """
209
+ sim_map_min, sim_map_max = sim_map.min(), sim_map.max()
210
+ if sim_map_max - sim_map_min > 1e-6:
211
+ return (sim_map - sim_map_min) / (sim_map_max - sim_map_min)
212
+ return np.zeros_like(sim_map)
213
+
214
+ @staticmethod
215
+ def should_filter_token(token: str) -> bool:
216
+ """
217
+ Determines if a token should be filtered out based on predefined patterns.
218
+
219
+ The function filters out tokens that:
220
+
221
+ - Start with '<' (e.g., '<bos>')
222
+ - Consist entirely of whitespace
223
+ - Are purely punctuation (excluding tokens that contain digits or start with '▁')
224
+ - Start with an underscore '_'
225
+ - Exactly match the word 'Question'
226
+ - Are exactly the single character '▁'
227
+
228
+ Output of test:
229
+ Token: '2' | False
230
+ Token: '0' | False
231
+ Token: '2' | False
232
+ Token: '3' | False
233
+ Token: '▁2' | False
234
+ Token: '▁hi' | False
235
+ Token: 'norwegian' | False
236
+ Token: 'unlisted' | False
237
+ Token: '<bos>' | True
238
+ Token: 'Question' | True
239
+ Token: ':' | True
240
+ Token: '<pad>' | True
241
+ Token: '\n' | True
242
+ Token: '▁' | True
243
+ Token: '?' | True
244
+ Token: ')' | True
245
+ Token: '%' | True
246
+ Token: '/)' | True
247
+
248
+
249
+ Args:
250
+ token (str): The token to check.
251
+
252
+ Returns:
253
+ bool: True if the token should be filtered out, False otherwise.
254
+ """
255
+ pattern = re.compile(
256
+ r"^<.*$|^\s+$|^(?!.*\d)(?!▁)[^\w\s]+$|^_.*$|^Question$|^▁$"
257
+ )
258
+ return bool(pattern.match(token))
259
+
260
+ @lru_cache(maxsize=128)
261
+ def get_query_embeddings_and_token_map(
262
+ self, query: str
263
+ ) -> Tuple[torch.Tensor, dict]:
264
+ """
265
+ Retrieves query embeddings and a token index map.
266
+
267
+ Args:
268
+ query (str): The query string.
269
+
270
+ Returns:
271
+ Tuple[torch.Tensor, dict]: Query embeddings and token index map.
272
+ """
273
+ inputs = self.processor.process_queries([query]).to(self.model.device)
274
+ with torch.no_grad():
275
+ q_emb = self.model(**inputs).to("cpu")[0]
276
+
277
+ query_tokens = self.processor.tokenizer.tokenize(
278
+ self.processor.decode(inputs.input_ids[0])
279
+ )
280
+ idx_to_token = {idx: token for idx, token in enumerate(query_tokens)}
281
+ return q_emb, idx_to_token
src/backend/stopwords.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spacy
2
+ import os
3
+
4
+ # Download the model if it is not already present
5
+ if not spacy.util.is_package("en_core_web_sm"):
6
+ spacy.cli.download("en_core_web_sm")
7
+ nlp = spacy.load("en_core_web_sm")
8
+
9
+
10
+ # It would be possible to remove bolding for stopwords without removing them from the query,
11
+ # but that would require a java plugin which we didn't want to complicate this sample app with.
12
+ def filter(text):
13
+ doc = nlp(text)
14
+ tokens = [token.text for token in doc if not token.is_stop]
15
+ if len(tokens) == 0:
16
+ # if we remove all the words we don't have a query at all, so use the original
17
+ return text
18
+ return " ".join(tokens)
src/backend/testquery.py ADDED
@@ -0,0 +1,3013 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ token_to_idx = {
4
+ "<bos>": 0,
5
+ "Question": 1,
6
+ ":": 2,
7
+ "▁Percentage": 3,
8
+ "▁of": 4,
9
+ "▁non": 5,
10
+ "-": 6,
11
+ "fresh": 7,
12
+ "▁water": 8,
13
+ "▁as": 9,
14
+ "▁source": 10,
15
+ "?": 11,
16
+ "<pad>": 21,
17
+ "\n": 22,
18
+ }
19
+ idx_to_token = {v: k for k, v in token_to_idx.items()}
20
+ q_embs = torch.tensor(
21
+ [
22
+ [
23
+ 1.6547e-01,
24
+ -1.8838e-02,
25
+ 1.0150e-01,
26
+ -2.2643e-02,
27
+ 5.0652e-02,
28
+ 3.2039e-02,
29
+ 6.8322e-02,
30
+ 2.7134e-02,
31
+ 7.2197e-03,
32
+ -4.2341e-02,
33
+ -5.8006e-02,
34
+ -1.1389e-01,
35
+ 7.0041e-02,
36
+ -6.8021e-02,
37
+ 2.4681e-02,
38
+ 5.3306e-02,
39
+ 4.1714e-02,
40
+ 6.2021e-02,
41
+ 1.3488e-01,
42
+ 3.4943e-02,
43
+ 3.8032e-02,
44
+ -3.2724e-02,
45
+ -1.2960e-01,
46
+ 1.1453e-02,
47
+ -2.6477e-02,
48
+ 3.5219e-02,
49
+ -7.6606e-02,
50
+ 2.2387e-01,
51
+ -3.4888e-02,
52
+ -4.0333e-02,
53
+ 1.4128e-01,
54
+ 4.2248e-02,
55
+ -1.2664e-01,
56
+ -7.8376e-02,
57
+ -2.0356e-02,
58
+ 4.2198e-02,
59
+ -7.0776e-02,
60
+ 1.3965e-02,
61
+ 4.2442e-03,
62
+ 7.1987e-02,
63
+ 8.5172e-04,
64
+ -6.4878e-02,
65
+ -1.8954e-01,
66
+ -8.6171e-02,
67
+ 9.1983e-02,
68
+ -9.3358e-02,
69
+ 2.2704e-01,
70
+ 1.3102e-02,
71
+ 6.5327e-02,
72
+ 2.4815e-02,
73
+ -1.4533e-01,
74
+ 5.8823e-02,
75
+ -6.1434e-02,
76
+ 5.2004e-02,
77
+ -8.4065e-02,
78
+ 1.6298e-01,
79
+ 8.1965e-02,
80
+ 2.6553e-02,
81
+ -1.2377e-01,
82
+ -5.3495e-02,
83
+ -3.4537e-02,
84
+ 5.1438e-02,
85
+ 8.2665e-03,
86
+ 7.9407e-02,
87
+ 5.8799e-02,
88
+ -3.5538e-02,
89
+ 1.9870e-01,
90
+ 6.2459e-02,
91
+ 1.6154e-01,
92
+ 7.2921e-02,
93
+ -9.7275e-02,
94
+ 3.0933e-02,
95
+ -1.0579e-02,
96
+ -1.4484e-01,
97
+ -4.8761e-02,
98
+ -5.3119e-02,
99
+ 6.2644e-02,
100
+ 2.2985e-02,
101
+ -2.1209e-01,
102
+ 9.0963e-02,
103
+ -2.6955e-02,
104
+ -7.7520e-02,
105
+ 1.2072e-01,
106
+ -1.9626e-02,
107
+ 5.8813e-02,
108
+ -1.2730e-01,
109
+ 1.5610e-01,
110
+ -1.6914e-01,
111
+ 6.5033e-02,
112
+ 8.5765e-02,
113
+ 1.2701e-01,
114
+ 6.5633e-02,
115
+ -1.0309e-01,
116
+ -8.0259e-02,
117
+ 4.5913e-02,
118
+ -3.3277e-02,
119
+ 1.9227e-01,
120
+ -2.3351e-02,
121
+ -8.0545e-02,
122
+ -9.8760e-03,
123
+ -4.1836e-02,
124
+ 1.2041e-01,
125
+ 8.1419e-02,
126
+ 1.4848e-01,
127
+ 7.2537e-02,
128
+ -4.7115e-03,
129
+ 4.1489e-02,
130
+ 4.3031e-02,
131
+ -1.3515e-01,
132
+ 1.0383e-01,
133
+ -9.4411e-02,
134
+ 2.8965e-02,
135
+ 1.9185e-01,
136
+ -1.4600e-02,
137
+ -8.0910e-02,
138
+ -2.9022e-02,
139
+ -4.4347e-02,
140
+ -8.8980e-03,
141
+ 2.8737e-03,
142
+ 4.2124e-02,
143
+ -1.6609e-02,
144
+ 4.2994e-02,
145
+ -7.2814e-02,
146
+ -2.9573e-02,
147
+ -1.2666e-01,
148
+ -4.3703e-02,
149
+ -7.2094e-02,
150
+ -2.7486e-02,
151
+ ],
152
+ [
153
+ -9.8454e-02,
154
+ -1.2954e-01,
155
+ 6.5702e-02,
156
+ -9.0006e-03,
157
+ -1.0934e-01,
158
+ 3.2155e-02,
159
+ -1.1444e-01,
160
+ -1.0309e-01,
161
+ 5.7024e-02,
162
+ 1.0124e-01,
163
+ 2.0721e-02,
164
+ -5.2608e-03,
165
+ 6.9916e-02,
166
+ 1.8036e-02,
167
+ 3.1653e-02,
168
+ 2.1923e-02,
169
+ -9.2523e-02,
170
+ -1.8215e-02,
171
+ 1.2974e-01,
172
+ -2.9632e-02,
173
+ -1.3854e-01,
174
+ 2.5710e-02,
175
+ -1.1727e-03,
176
+ 1.0245e-01,
177
+ -2.0731e-01,
178
+ 4.3669e-02,
179
+ -7.0196e-02,
180
+ -1.6697e-01,
181
+ 6.6050e-02,
182
+ 9.9776e-02,
183
+ -1.2227e-01,
184
+ 9.4000e-02,
185
+ 1.1945e-01,
186
+ 2.4611e-02,
187
+ -1.4073e-01,
188
+ 9.3476e-02,
189
+ 2.1170e-01,
190
+ -7.4522e-02,
191
+ -5.3362e-02,
192
+ -4.1198e-03,
193
+ 8.3880e-02,
194
+ 2.6590e-02,
195
+ -4.8489e-02,
196
+ -3.1279e-02,
197
+ -9.3401e-03,
198
+ -1.5945e-01,
199
+ -6.6368e-02,
200
+ 7.5715e-02,
201
+ 5.6884e-02,
202
+ 1.2861e-01,
203
+ 1.0073e-02,
204
+ -1.7185e-02,
205
+ 1.1545e-01,
206
+ 2.8725e-02,
207
+ -9.4969e-02,
208
+ -4.5517e-02,
209
+ 3.1253e-02,
210
+ 2.1135e-02,
211
+ -1.4505e-02,
212
+ 9.0893e-02,
213
+ -6.2680e-02,
214
+ -7.2855e-02,
215
+ -1.1275e-01,
216
+ -1.8433e-01,
217
+ 1.4693e-01,
218
+ 4.0366e-02,
219
+ 6.6879e-02,
220
+ -8.5653e-03,
221
+ 1.0663e-01,
222
+ -1.2342e-01,
223
+ 2.5350e-01,
224
+ -3.2227e-02,
225
+ 9.9404e-02,
226
+ 3.7340e-02,
227
+ 4.5462e-04,
228
+ -2.3015e-01,
229
+ 4.9006e-02,
230
+ 1.0079e-01,
231
+ -4.7179e-02,
232
+ 6.7642e-02,
233
+ -1.0833e-01,
234
+ 1.0030e-01,
235
+ 1.2838e-01,
236
+ -9.2911e-03,
237
+ 1.2342e-01,
238
+ -4.8455e-02,
239
+ 5.3904e-03,
240
+ 4.5178e-02,
241
+ 3.8961e-02,
242
+ 1.3383e-01,
243
+ -1.2236e-02,
244
+ 8.2026e-03,
245
+ 4.3735e-03,
246
+ -7.1725e-02,
247
+ 6.4360e-02,
248
+ 1.0004e-01,
249
+ 6.6840e-02,
250
+ 6.5649e-02,
251
+ -1.3978e-02,
252
+ 1.2810e-01,
253
+ 4.4325e-03,
254
+ 1.1136e-01,
255
+ -1.7329e-01,
256
+ -3.4472e-02,
257
+ -1.4066e-01,
258
+ 1.5641e-02,
259
+ -3.3600e-02,
260
+ -7.6192e-02,
261
+ 5.3085e-02,
262
+ -7.2859e-03,
263
+ 2.8798e-02,
264
+ -3.3748e-02,
265
+ -7.7591e-02,
266
+ -4.0927e-02,
267
+ -3.6577e-02,
268
+ 1.4012e-02,
269
+ -8.1780e-02,
270
+ -4.6315e-03,
271
+ -1.6508e-02,
272
+ -2.4506e-02,
273
+ 1.5122e-01,
274
+ 7.8270e-02,
275
+ 8.6502e-02,
276
+ -2.4651e-02,
277
+ -1.0286e-01,
278
+ -1.2171e-02,
279
+ -7.9000e-02,
280
+ 1.1161e-01,
281
+ ],
282
+ [
283
+ 6.4320e-02,
284
+ -2.6815e-02,
285
+ 1.2390e-01,
286
+ 8.6642e-02,
287
+ 5.3320e-02,
288
+ 9.1074e-02,
289
+ -8.9753e-02,
290
+ 1.6141e-02,
291
+ -5.0281e-02,
292
+ 1.0177e-01,
293
+ 4.5343e-02,
294
+ 9.1281e-02,
295
+ -8.2592e-03,
296
+ -1.4100e-02,
297
+ -4.7048e-02,
298
+ -8.6034e-02,
299
+ -1.1608e-01,
300
+ -8.4754e-02,
301
+ 1.0302e-01,
302
+ -1.2210e-02,
303
+ 8.5147e-02,
304
+ 1.3103e-01,
305
+ -3.3592e-03,
306
+ -1.4328e-02,
307
+ -4.6128e-03,
308
+ 2.1401e-02,
309
+ -1.7464e-01,
310
+ -3.9111e-02,
311
+ 2.0886e-02,
312
+ 8.9284e-02,
313
+ 1.3262e-01,
314
+ 7.0918e-02,
315
+ -1.3693e-02,
316
+ -9.7673e-02,
317
+ -5.3411e-02,
318
+ -6.7563e-02,
319
+ 2.3017e-02,
320
+ -3.4614e-02,
321
+ 3.6464e-02,
322
+ -4.6408e-02,
323
+ 1.4866e-01,
324
+ -2.1191e-01,
325
+ -6.4368e-02,
326
+ -3.0555e-02,
327
+ 7.2177e-02,
328
+ 2.4685e-02,
329
+ 8.6115e-02,
330
+ -5.8688e-02,
331
+ -2.8230e-03,
332
+ 1.1166e-01,
333
+ 1.8380e-01,
334
+ 3.6462e-02,
335
+ -1.4943e-02,
336
+ -1.4276e-01,
337
+ 1.0795e-01,
338
+ 1.9204e-02,
339
+ -6.5320e-02,
340
+ 1.0561e-01,
341
+ -1.3642e-01,
342
+ -4.3444e-02,
343
+ -6.4725e-02,
344
+ -1.3094e-01,
345
+ 1.9447e-02,
346
+ -1.3199e-01,
347
+ 8.7880e-02,
348
+ 5.8078e-02,
349
+ -2.5612e-02,
350
+ 7.7620e-02,
351
+ -2.5044e-02,
352
+ 1.0772e-02,
353
+ 6.5417e-02,
354
+ 8.2137e-02,
355
+ -2.8482e-02,
356
+ 5.5003e-02,
357
+ -1.1163e-01,
358
+ 1.7200e-03,
359
+ -1.0106e-01,
360
+ -1.8413e-02,
361
+ 1.2838e-01,
362
+ -1.2991e-01,
363
+ -1.8546e-02,
364
+ 1.0517e-01,
365
+ 1.0279e-01,
366
+ -8.1887e-02,
367
+ 1.0885e-01,
368
+ -1.0635e-01,
369
+ 1.2035e-01,
370
+ 1.1769e-01,
371
+ -2.8768e-02,
372
+ -3.3413e-02,
373
+ 1.3779e-01,
374
+ 1.4403e-02,
375
+ 2.3429e-02,
376
+ -1.2761e-01,
377
+ 7.2160e-02,
378
+ -1.0512e-01,
379
+ -1.7202e-02,
380
+ 5.3549e-02,
381
+ 5.5205e-02,
382
+ 9.2863e-02,
383
+ -2.3728e-02,
384
+ -5.1368e-02,
385
+ -3.7719e-02,
386
+ -4.7308e-02,
387
+ -2.2489e-02,
388
+ 4.5195e-02,
389
+ 1.0398e-01,
390
+ -2.2197e-02,
391
+ -1.7208e-01,
392
+ 7.4649e-02,
393
+ -7.7925e-02,
394
+ -6.4237e-02,
395
+ 2.8195e-02,
396
+ 2.2692e-01,
397
+ -9.7749e-02,
398
+ 2.4283e-01,
399
+ -4.0124e-02,
400
+ 1.8797e-02,
401
+ -6.1516e-02,
402
+ 6.5331e-03,
403
+ 1.3717e-01,
404
+ 9.9761e-02,
405
+ 5.4705e-02,
406
+ 3.5325e-02,
407
+ 1.9071e-01,
408
+ -6.1137e-02,
409
+ 1.6656e-01,
410
+ 3.4067e-02,
411
+ ],
412
+ [
413
+ 1.0896e-01,
414
+ -9.4366e-03,
415
+ 1.2956e-01,
416
+ 7.8127e-02,
417
+ 5.5422e-02,
418
+ 3.9155e-02,
419
+ -5.8379e-03,
420
+ -4.4257e-02,
421
+ -7.5182e-02,
422
+ 1.0452e-01,
423
+ 4.4595e-02,
424
+ 2.0972e-02,
425
+ 1.1071e-01,
426
+ 7.4710e-02,
427
+ -7.5500e-02,
428
+ -5.3393e-02,
429
+ -1.5478e-02,
430
+ -5.4455e-03,
431
+ 5.6779e-02,
432
+ -6.3919e-02,
433
+ 5.1792e-02,
434
+ 1.2070e-01,
435
+ -5.3707e-02,
436
+ 4.5715e-02,
437
+ -9.3062e-02,
438
+ 3.0224e-02,
439
+ -1.5892e-01,
440
+ -2.1702e-02,
441
+ 1.7942e-03,
442
+ 1.4574e-01,
443
+ 2.0721e-01,
444
+ -2.8224e-02,
445
+ -5.8104e-02,
446
+ -1.1645e-01,
447
+ -1.1515e-01,
448
+ -1.5202e-01,
449
+ -3.9751e-02,
450
+ 6.0342e-02,
451
+ 7.8182e-02,
452
+ -2.1132e-02,
453
+ 9.6468e-02,
454
+ -5.3148e-02,
455
+ -3.0343e-02,
456
+ 7.9363e-02,
457
+ 1.0752e-01,
458
+ 3.1086e-02,
459
+ 1.9322e-02,
460
+ -1.1134e-01,
461
+ 1.6342e-02,
462
+ 3.0358e-02,
463
+ 1.8543e-01,
464
+ 5.5353e-02,
465
+ -8.6656e-02,
466
+ -1.5650e-01,
467
+ 1.2087e-01,
468
+ -3.7852e-02,
469
+ -6.9116e-02,
470
+ 5.9981e-03,
471
+ 1.9205e-02,
472
+ -1.0314e-01,
473
+ -6.8082e-02,
474
+ -1.3078e-01,
475
+ 3.8448e-02,
476
+ -9.2233e-02,
477
+ 1.0965e-01,
478
+ 6.6332e-02,
479
+ -2.5805e-02,
480
+ 1.2299e-01,
481
+ 2.8629e-02,
482
+ -4.4949e-02,
483
+ 4.5560e-02,
484
+ 1.0507e-01,
485
+ -1.0271e-01,
486
+ 1.6237e-02,
487
+ -1.4555e-01,
488
+ -4.5335e-02,
489
+ -1.3477e-01,
490
+ 1.0230e-02,
491
+ 1.2380e-01,
492
+ -1.0681e-01,
493
+ 1.4412e-02,
494
+ 1.2396e-01,
495
+ 8.5290e-02,
496
+ -2.5138e-02,
497
+ 1.0191e-01,
498
+ -1.3413e-01,
499
+ 8.5871e-02,
500
+ 1.3389e-01,
501
+ -3.6357e-02,
502
+ -3.9740e-02,
503
+ 2.1128e-01,
504
+ 1.0263e-02,
505
+ 2.5547e-02,
506
+ -7.0139e-02,
507
+ 1.0178e-01,
508
+ -1.2729e-01,
509
+ -1.0717e-01,
510
+ -4.9394e-02,
511
+ 7.7645e-02,
512
+ 7.4589e-02,
513
+ -8.2835e-02,
514
+ -4.2227e-02,
515
+ -3.6417e-02,
516
+ -2.2900e-02,
517
+ -2.1010e-02,
518
+ 2.7898e-02,
519
+ 2.7314e-02,
520
+ 2.2172e-02,
521
+ -7.1122e-02,
522
+ 5.1570e-02,
523
+ 2.1860e-02,
524
+ -3.5103e-03,
525
+ -5.4524e-02,
526
+ 1.7485e-01,
527
+ -8.3810e-02,
528
+ 2.3868e-01,
529
+ 5.9468e-02,
530
+ -2.6706e-02,
531
+ -2.6617e-02,
532
+ 3.3851e-02,
533
+ 6.3651e-02,
534
+ 1.0611e-01,
535
+ 9.6252e-02,
536
+ -6.8701e-02,
537
+ 1.8108e-01,
538
+ -1.0178e-01,
539
+ 1.6935e-01,
540
+ 5.9301e-02,
541
+ ],
542
+ [
543
+ 1.3364e-01,
544
+ 6.6797e-02,
545
+ 1.0182e-01,
546
+ 6.1569e-02,
547
+ -1.4169e-04,
548
+ 1.1567e-01,
549
+ -3.1255e-03,
550
+ -8.9336e-02,
551
+ 5.3206e-03,
552
+ 1.7179e-01,
553
+ 1.3974e-01,
554
+ -6.5797e-02,
555
+ 1.2566e-01,
556
+ 6.1290e-02,
557
+ -1.3671e-01,
558
+ -1.1113e-01,
559
+ 1.6596e-01,
560
+ 6.2694e-02,
561
+ -1.6573e-02,
562
+ -1.8648e-02,
563
+ 2.1086e-02,
564
+ 9.6320e-03,
565
+ -1.0017e-01,
566
+ 2.3561e-02,
567
+ -6.2797e-02,
568
+ 2.1202e-02,
569
+ -1.3333e-01,
570
+ 1.4728e-01,
571
+ 1.9824e-03,
572
+ 1.6832e-01,
573
+ 1.7138e-01,
574
+ 4.6578e-02,
575
+ -1.2989e-01,
576
+ 3.2167e-02,
577
+ -7.7753e-02,
578
+ -1.1548e-01,
579
+ 4.3367e-02,
580
+ -4.5640e-02,
581
+ 4.3583e-02,
582
+ -8.9393e-03,
583
+ 5.3231e-02,
584
+ -4.8284e-02,
585
+ 3.1841e-04,
586
+ 6.6374e-02,
587
+ 7.7363e-02,
588
+ 4.0000e-02,
589
+ 1.5414e-02,
590
+ -9.6156e-02,
591
+ 1.1389e-01,
592
+ 7.1118e-02,
593
+ 3.0042e-02,
594
+ 5.9752e-02,
595
+ -6.4565e-02,
596
+ -1.2914e-01,
597
+ 1.1048e-01,
598
+ 1.2409e-02,
599
+ -9.9385e-02,
600
+ 1.8671e-02,
601
+ 2.1383e-02,
602
+ 3.7012e-03,
603
+ -1.1497e-01,
604
+ -6.8653e-02,
605
+ 3.0582e-02,
606
+ -1.1567e-01,
607
+ 1.4165e-01,
608
+ 3.7493e-02,
609
+ -6.0779e-02,
610
+ 1.0989e-01,
611
+ 8.6001e-02,
612
+ -5.6139e-02,
613
+ 2.0710e-02,
614
+ 9.8577e-02,
615
+ -9.9427e-02,
616
+ 5.8372e-02,
617
+ -1.3443e-01,
618
+ -1.3021e-02,
619
+ -1.3802e-01,
620
+ 7.6053e-02,
621
+ 1.2181e-01,
622
+ -8.7719e-02,
623
+ 1.0967e-02,
624
+ 1.3160e-01,
625
+ 4.7032e-02,
626
+ -6.3351e-02,
627
+ 7.1883e-02,
628
+ -9.7565e-02,
629
+ 1.4424e-01,
630
+ 1.2353e-01,
631
+ -6.1527e-02,
632
+ 2.4263e-02,
633
+ 2.9356e-01,
634
+ 6.2813e-02,
635
+ -4.5265e-03,
636
+ -1.0213e-01,
637
+ 1.4227e-02,
638
+ -7.9267e-02,
639
+ -1.0845e-01,
640
+ -2.0014e-02,
641
+ 2.8542e-02,
642
+ 9.7207e-02,
643
+ -2.5234e-02,
644
+ -7.3668e-02,
645
+ -3.0084e-02,
646
+ -1.2958e-02,
647
+ -3.9597e-02,
648
+ -7.2243e-02,
649
+ 5.3054e-02,
650
+ 3.1470e-03,
651
+ -1.9800e-02,
652
+ 1.3476e-01,
653
+ -1.6873e-02,
654
+ 5.2286e-02,
655
+ 2.0254e-02,
656
+ 1.0554e-01,
657
+ -3.0395e-02,
658
+ 8.6349e-02,
659
+ 7.6580e-02,
660
+ -3.0139e-02,
661
+ -4.8131e-02,
662
+ -4.5770e-02,
663
+ 1.5154e-01,
664
+ 1.1276e-01,
665
+ 5.7244e-02,
666
+ 8.0574e-02,
667
+ 1.5610e-01,
668
+ -1.5523e-01,
669
+ 1.0428e-01,
670
+ 5.7947e-02,
671
+ ],
672
+ [
673
+ 7.0440e-02,
674
+ 1.4300e-01,
675
+ 6.0559e-02,
676
+ 1.9177e-02,
677
+ -9.0313e-02,
678
+ 1.7104e-01,
679
+ -5.1137e-02,
680
+ -1.0229e-01,
681
+ -2.8831e-02,
682
+ 1.5385e-01,
683
+ -8.4017e-03,
684
+ -4.9185e-03,
685
+ 1.0820e-01,
686
+ 1.0022e-01,
687
+ -1.9284e-01,
688
+ -2.7293e-02,
689
+ 4.9526e-02,
690
+ 5.5152e-02,
691
+ -6.7003e-02,
692
+ -7.0313e-03,
693
+ -3.3208e-02,
694
+ 1.3815e-02,
695
+ -6.0694e-02,
696
+ 6.2041e-02,
697
+ -3.2288e-02,
698
+ 1.1629e-01,
699
+ -7.5270e-02,
700
+ 2.1824e-01,
701
+ -2.5215e-03,
702
+ 1.8179e-01,
703
+ 1.5514e-01,
704
+ 1.0494e-01,
705
+ -1.2390e-01,
706
+ -1.2241e-03,
707
+ 6.1079e-04,
708
+ -5.8730e-02,
709
+ 6.1313e-02,
710
+ -7.8853e-02,
711
+ 6.0292e-02,
712
+ -9.1497e-04,
713
+ 7.9087e-02,
714
+ -1.8246e-02,
715
+ 5.0215e-03,
716
+ 3.5083e-02,
717
+ 5.9616e-02,
718
+ 5.9520e-02,
719
+ 6.0224e-02,
720
+ -1.3079e-01,
721
+ 1.6500e-01,
722
+ 4.4308e-03,
723
+ 4.2712e-02,
724
+ 5.5916e-02,
725
+ -5.4616e-02,
726
+ -8.5617e-02,
727
+ 1.1235e-01,
728
+ 6.5911e-03,
729
+ -6.1463e-02,
730
+ 3.7832e-02,
731
+ 3.4189e-02,
732
+ -1.1295e-02,
733
+ -8.0972e-02,
734
+ -1.0051e-02,
735
+ -2.6856e-02,
736
+ -7.9570e-02,
737
+ 1.2776e-01,
738
+ 6.5826e-02,
739
+ -3.1759e-02,
740
+ 9.6016e-02,
741
+ 6.7249e-02,
742
+ -4.5115e-02,
743
+ 6.3695e-03,
744
+ 1.2092e-01,
745
+ -1.3821e-01,
746
+ -9.7066e-02,
747
+ -1.5063e-02,
748
+ 2.4618e-02,
749
+ -1.9589e-01,
750
+ 5.8625e-02,
751
+ 1.7886e-01,
752
+ -6.3740e-02,
753
+ -1.7241e-02,
754
+ 7.3394e-02,
755
+ 5.8903e-02,
756
+ -1.6557e-02,
757
+ -1.9226e-02,
758
+ -1.0912e-01,
759
+ 8.7902e-02,
760
+ 6.4426e-02,
761
+ 4.8019e-02,
762
+ 8.1223e-02,
763
+ 2.9888e-01,
764
+ 8.7458e-02,
765
+ 4.4167e-02,
766
+ -1.3228e-01,
767
+ 5.9629e-02,
768
+ -1.0696e-01,
769
+ -1.4102e-01,
770
+ -5.2509e-02,
771
+ -1.9981e-02,
772
+ 1.6788e-01,
773
+ 9.7499e-02,
774
+ -5.4125e-02,
775
+ -8.2383e-02,
776
+ -6.3908e-02,
777
+ -6.8830e-02,
778
+ -1.2622e-01,
779
+ 3.1651e-02,
780
+ 4.4592e-02,
781
+ -1.3325e-02,
782
+ 1.1260e-01,
783
+ -3.9567e-02,
784
+ 6.9631e-03,
785
+ 1.4943e-01,
786
+ 8.6930e-02,
787
+ -3.6171e-03,
788
+ -5.6886e-02,
789
+ -8.3102e-03,
790
+ -2.6001e-02,
791
+ -1.5187e-02,
792
+ -1.8835e-02,
793
+ 2.3583e-02,
794
+ 9.5520e-02,
795
+ -3.4944e-02,
796
+ 4.5537e-02,
797
+ 6.1444e-02,
798
+ -1.7165e-01,
799
+ 1.0230e-01,
800
+ 2.0319e-02,
801
+ ],
802
+ [
803
+ 7.7065e-02,
804
+ 1.4466e-01,
805
+ 1.0796e-01,
806
+ 9.7362e-03,
807
+ -9.3062e-02,
808
+ 2.0065e-01,
809
+ -9.3982e-03,
810
+ -1.2871e-01,
811
+ -4.6724e-02,
812
+ 1.6573e-01,
813
+ -1.7444e-02,
814
+ -3.1211e-02,
815
+ 9.7404e-02,
816
+ 9.6222e-02,
817
+ -1.4772e-01,
818
+ -5.6838e-02,
819
+ 9.6276e-02,
820
+ 7.7819e-02,
821
+ -1.1884e-01,
822
+ -4.5898e-02,
823
+ -7.3665e-02,
824
+ 2.1005e-02,
825
+ -2.8032e-02,
826
+ 7.0900e-02,
827
+ -7.7625e-02,
828
+ 7.0269e-02,
829
+ -7.0747e-02,
830
+ 2.5605e-01,
831
+ 1.9427e-04,
832
+ 1.4614e-01,
833
+ 1.6723e-01,
834
+ 1.0016e-01,
835
+ -7.8961e-02,
836
+ 1.3307e-02,
837
+ -1.1207e-02,
838
+ -7.1492e-02,
839
+ 7.5561e-02,
840
+ -1.3290e-02,
841
+ 4.4527e-02,
842
+ -2.4224e-02,
843
+ 8.7846e-02,
844
+ 4.7492e-02,
845
+ -7.0398e-02,
846
+ -2.6663e-02,
847
+ 4.9730e-02,
848
+ 6.6743e-02,
849
+ 3.1060e-02,
850
+ -7.8352e-02,
851
+ 1.0100e-01,
852
+ 6.4963e-02,
853
+ 1.6746e-02,
854
+ 3.0324e-02,
855
+ -2.6583e-02,
856
+ -7.8028e-02,
857
+ 7.0180e-02,
858
+ 3.8924e-02,
859
+ -8.5861e-02,
860
+ -3.3792e-02,
861
+ 6.1542e-02,
862
+ 1.6180e-02,
863
+ -7.7865e-02,
864
+ 3.9551e-02,
865
+ 2.9772e-02,
866
+ -7.0824e-02,
867
+ 1.5235e-01,
868
+ 4.8718e-02,
869
+ 1.5973e-03,
870
+ 8.7719e-02,
871
+ 7.7414e-02,
872
+ -6.4385e-02,
873
+ -6.4330e-02,
874
+ 1.3965e-01,
875
+ -1.6355e-01,
876
+ -6.5261e-02,
877
+ -6.2693e-02,
878
+ 4.9435e-02,
879
+ -1.5245e-01,
880
+ 6.6557e-02,
881
+ 1.5213e-01,
882
+ -8.2073e-02,
883
+ 1.4664e-02,
884
+ 8.4507e-02,
885
+ 3.0684e-02,
886
+ -8.7932e-02,
887
+ 1.9927e-02,
888
+ -7.1788e-02,
889
+ 9.4965e-02,
890
+ 3.9220e-02,
891
+ 4.5944e-02,
892
+ 8.7249e-02,
893
+ 3.3315e-01,
894
+ 5.9872e-02,
895
+ 2.6362e-02,
896
+ -1.7888e-01,
897
+ -1.6042e-02,
898
+ -9.4593e-02,
899
+ -1.7915e-01,
900
+ -2.9888e-02,
901
+ -4.3776e-02,
902
+ 1.1388e-01,
903
+ 2.3778e-02,
904
+ -4.0233e-02,
905
+ -4.7893e-02,
906
+ -4.4371e-02,
907
+ -2.7491e-02,
908
+ -9.6716e-02,
909
+ -2.0120e-02,
910
+ 5.6864e-02,
911
+ 1.8953e-02,
912
+ 1.2741e-01,
913
+ -5.3045e-02,
914
+ 3.2240e-02,
915
+ 1.4479e-01,
916
+ 9.5315e-02,
917
+ -2.7717e-02,
918
+ -5.7349e-02,
919
+ 3.3824e-03,
920
+ -3.5642e-02,
921
+ -1.6905e-02,
922
+ -4.5765e-02,
923
+ 1.1481e-02,
924
+ 4.2545e-02,
925
+ 1.2632e-02,
926
+ 4.5401e-02,
927
+ 7.7769e-02,
928
+ -1.7107e-01,
929
+ 7.7265e-02,
930
+ 1.3922e-02,
931
+ ],
932
+ [
933
+ -2.5666e-02,
934
+ 9.8268e-02,
935
+ 2.1571e-01,
936
+ 6.8094e-02,
937
+ -1.0440e-01,
938
+ 1.4299e-01,
939
+ 6.5751e-02,
940
+ -5.0693e-02,
941
+ -5.3796e-02,
942
+ 1.5239e-01,
943
+ 2.9566e-02,
944
+ -7.9492e-02,
945
+ 9.3274e-02,
946
+ 7.6112e-02,
947
+ -1.8187e-02,
948
+ -1.1190e-01,
949
+ 9.7962e-02,
950
+ -2.8204e-02,
951
+ -8.1216e-02,
952
+ -5.5618e-02,
953
+ -6.2378e-02,
954
+ 4.4238e-02,
955
+ -1.6572e-02,
956
+ -3.4035e-02,
957
+ -8.8068e-02,
958
+ 1.4164e-02,
959
+ -2.6908e-02,
960
+ 1.9650e-01,
961
+ 6.8845e-03,
962
+ 8.7550e-02,
963
+ 1.7410e-01,
964
+ 1.0088e-01,
965
+ -1.1340e-02,
966
+ 3.2057e-04,
967
+ -5.5130e-02,
968
+ -2.5234e-02,
969
+ 8.2460e-02,
970
+ -6.0768e-02,
971
+ 1.2448e-01,
972
+ 6.8736e-02,
973
+ 4.6176e-02,
974
+ 1.0866e-01,
975
+ 4.9560e-02,
976
+ -5.8322e-02,
977
+ 4.4106e-02,
978
+ 2.0739e-02,
979
+ -9.0032e-02,
980
+ -5.8815e-02,
981
+ 7.8127e-03,
982
+ 1.7999e-01,
983
+ 1.2519e-01,
984
+ 7.1377e-02,
985
+ 9.3219e-02,
986
+ -1.3311e-01,
987
+ 6.0305e-02,
988
+ 5.9400e-02,
989
+ -1.6119e-01,
990
+ 5.4173e-02,
991
+ -5.8663e-02,
992
+ -4.8149e-02,
993
+ 1.6295e-02,
994
+ -6.9787e-02,
995
+ 5.7512e-03,
996
+ -8.9745e-03,
997
+ 1.3492e-01,
998
+ 3.0659e-02,
999
+ -6.8611e-02,
1000
+ 2.1200e-02,
1001
+ 8.5522e-02,
1002
+ -4.3482e-02,
1003
+ -8.4601e-02,
1004
+ 1.4191e-01,
1005
+ -1.5514e-01,
1006
+ -5.8989e-03,
1007
+ -4.5591e-02,
1008
+ 6.4905e-02,
1009
+ -1.3198e-01,
1010
+ 1.3764e-01,
1011
+ 9.5549e-02,
1012
+ -9.4689e-02,
1013
+ -1.9705e-02,
1014
+ 2.1147e-01,
1015
+ -9.8519e-03,
1016
+ -7.7839e-02,
1017
+ 6.1447e-02,
1018
+ -6.4708e-02,
1019
+ 3.1579e-03,
1020
+ 7.6588e-02,
1021
+ -1.2452e-01,
1022
+ -6.1076e-02,
1023
+ 2.5150e-01,
1024
+ 2.3101e-02,
1025
+ -1.3632e-02,
1026
+ -8.5695e-02,
1027
+ -8.5841e-02,
1028
+ -1.3152e-01,
1029
+ -1.5294e-01,
1030
+ -4.4509e-03,
1031
+ 8.6619e-02,
1032
+ -1.0974e-02,
1033
+ -3.9592e-02,
1034
+ -3.0472e-02,
1035
+ -1.4011e-01,
1036
+ 8.6485e-03,
1037
+ -1.3633e-02,
1038
+ -1.7940e-02,
1039
+ 3.3016e-02,
1040
+ -5.7245e-02,
1041
+ 1.0200e-01,
1042
+ 1.2807e-01,
1043
+ 1.5249e-02,
1044
+ -1.2197e-02,
1045
+ -1.6867e-02,
1046
+ 3.4516e-02,
1047
+ -9.0908e-02,
1048
+ -2.6167e-02,
1049
+ 2.2975e-01,
1050
+ 4.2693e-02,
1051
+ 2.5415e-03,
1052
+ 7.1921e-03,
1053
+ 1.2855e-01,
1054
+ 5.5747e-03,
1055
+ -2.7843e-02,
1056
+ 2.4283e-02,
1057
+ -1.3484e-02,
1058
+ -1.5948e-01,
1059
+ 2.1127e-02,
1060
+ 3.8017e-02,
1061
+ ],
1062
+ [
1063
+ 5.8410e-02,
1064
+ 3.7501e-02,
1065
+ 1.7189e-01,
1066
+ 4.5894e-02,
1067
+ -6.7739e-02,
1068
+ 1.1421e-01,
1069
+ 4.6982e-02,
1070
+ -1.2963e-01,
1071
+ -4.2804e-02,
1072
+ 1.2137e-01,
1073
+ 1.0761e-01,
1074
+ -7.2615e-02,
1075
+ 1.1811e-01,
1076
+ 1.1291e-01,
1077
+ -1.6041e-01,
1078
+ -6.6820e-02,
1079
+ 1.9071e-01,
1080
+ -3.6201e-02,
1081
+ -1.0659e-01,
1082
+ -3.3226e-02,
1083
+ -2.6535e-02,
1084
+ 7.8536e-02,
1085
+ -3.2975e-02,
1086
+ 1.3015e-02,
1087
+ -1.0903e-01,
1088
+ -1.2502e-02,
1089
+ -7.0142e-02,
1090
+ 1.6872e-01,
1091
+ -3.7569e-02,
1092
+ 2.2090e-01,
1093
+ 1.5620e-01,
1094
+ 9.5620e-02,
1095
+ -7.9306e-02,
1096
+ 7.8558e-02,
1097
+ -6.6709e-02,
1098
+ -6.6446e-02,
1099
+ 3.1198e-02,
1100
+ -4.8808e-02,
1101
+ 6.7798e-02,
1102
+ -4.9524e-02,
1103
+ 1.2496e-01,
1104
+ 3.1661e-02,
1105
+ 4.0690e-02,
1106
+ -1.1341e-02,
1107
+ 2.9852e-03,
1108
+ 3.6166e-02,
1109
+ -1.1723e-01,
1110
+ -8.2746e-02,
1111
+ 1.2874e-01,
1112
+ 1.0894e-01,
1113
+ 7.6219e-02,
1114
+ 5.5564e-02,
1115
+ 4.3571e-02,
1116
+ -1.4745e-01,
1117
+ 5.1090e-02,
1118
+ -6.8233e-04,
1119
+ -1.7107e-01,
1120
+ -2.5612e-02,
1121
+ 1.9185e-02,
1122
+ 3.4466e-03,
1123
+ -2.5362e-02,
1124
+ -3.4807e-02,
1125
+ 8.1003e-02,
1126
+ -2.1582e-02,
1127
+ 1.1581e-01,
1128
+ -5.5891e-04,
1129
+ 4.2455e-02,
1130
+ 6.3277e-02,
1131
+ 7.9868e-02,
1132
+ -2.0369e-02,
1133
+ -7.6229e-02,
1134
+ 1.2525e-01,
1135
+ -1.4795e-01,
1136
+ 5.8798e-03,
1137
+ -2.8584e-02,
1138
+ -3.2508e-04,
1139
+ -5.5666e-02,
1140
+ 7.1217e-02,
1141
+ 1.3122e-01,
1142
+ -1.8671e-02,
1143
+ -6.6402e-02,
1144
+ 8.1978e-02,
1145
+ -5.5194e-02,
1146
+ -1.7259e-02,
1147
+ 7.1373e-02,
1148
+ -2.4721e-02,
1149
+ 1.0176e-01,
1150
+ 1.7447e-02,
1151
+ -8.2872e-02,
1152
+ 4.7707e-02,
1153
+ 2.9338e-01,
1154
+ 3.0481e-02,
1155
+ 1.3437e-02,
1156
+ -1.1888e-01,
1157
+ -6.1243e-02,
1158
+ -8.7197e-02,
1159
+ -1.1085e-01,
1160
+ 3.2847e-03,
1161
+ 1.0611e-02,
1162
+ 1.2908e-01,
1163
+ -2.9064e-02,
1164
+ 4.5522e-03,
1165
+ -7.8045e-02,
1166
+ -1.4551e-02,
1167
+ -3.3958e-02,
1168
+ -1.3626e-01,
1169
+ 1.0859e-01,
1170
+ -7.3438e-03,
1171
+ 9.5942e-02,
1172
+ 1.4176e-01,
1173
+ 7.3675e-02,
1174
+ -2.3085e-03,
1175
+ -1.1689e-02,
1176
+ 6.1119e-02,
1177
+ -9.3085e-02,
1178
+ -1.9003e-03,
1179
+ 1.9013e-01,
1180
+ -2.5321e-02,
1181
+ -7.0616e-02,
1182
+ -3.7145e-02,
1183
+ 1.2449e-01,
1184
+ 5.3496e-02,
1185
+ 6.4021e-02,
1186
+ 2.2668e-02,
1187
+ 7.3348e-02,
1188
+ -1.7717e-01,
1189
+ 8.7206e-02,
1190
+ -9.0490e-03,
1191
+ ],
1192
+ [
1193
+ 1.0227e-01,
1194
+ 4.4796e-02,
1195
+ 4.0916e-02,
1196
+ 1.2557e-01,
1197
+ 6.2699e-02,
1198
+ 8.8194e-02,
1199
+ 1.0194e-02,
1200
+ -1.8886e-01,
1201
+ -2.6260e-02,
1202
+ 1.8107e-01,
1203
+ 1.1127e-01,
1204
+ -4.9960e-03,
1205
+ 8.2291e-02,
1206
+ 6.0932e-02,
1207
+ -1.3735e-01,
1208
+ -8.1350e-02,
1209
+ 1.2948e-01,
1210
+ 1.0171e-01,
1211
+ -7.5950e-02,
1212
+ 4.7169e-02,
1213
+ 3.5525e-03,
1214
+ -1.4790e-02,
1215
+ -3.9063e-02,
1216
+ -1.9754e-02,
1217
+ -4.6450e-02,
1218
+ 4.9071e-03,
1219
+ -3.5914e-02,
1220
+ 1.7644e-01,
1221
+ 4.1860e-02,
1222
+ 1.0857e-01,
1223
+ 8.5257e-02,
1224
+ 1.0368e-01,
1225
+ -1.7223e-01,
1226
+ 4.6316e-02,
1227
+ -3.9480e-02,
1228
+ -9.5817e-02,
1229
+ 1.5301e-03,
1230
+ -4.5519e-02,
1231
+ 1.2380e-01,
1232
+ -3.0402e-02,
1233
+ 4.9047e-02,
1234
+ 3.4955e-02,
1235
+ -1.3018e-02,
1236
+ 7.4193e-02,
1237
+ -3.0426e-02,
1238
+ 1.8414e-02,
1239
+ -1.6606e-02,
1240
+ -1.6301e-01,
1241
+ 1.5784e-01,
1242
+ 1.3759e-01,
1243
+ 3.1906e-02,
1244
+ 2.9389e-02,
1245
+ -9.4501e-02,
1246
+ -1.9930e-01,
1247
+ 1.3336e-01,
1248
+ 3.0685e-02,
1249
+ -7.3809e-02,
1250
+ 6.2165e-02,
1251
+ 7.3050e-02,
1252
+ 3.2870e-02,
1253
+ -1.2857e-01,
1254
+ -7.5806e-04,
1255
+ 1.3985e-01,
1256
+ -5.5108e-02,
1257
+ 9.2220e-02,
1258
+ 7.2490e-02,
1259
+ -2.6251e-02,
1260
+ 3.4508e-02,
1261
+ -3.1215e-02,
1262
+ -8.1111e-02,
1263
+ 1.4316e-02,
1264
+ 1.2390e-01,
1265
+ -9.6291e-03,
1266
+ 6.4214e-02,
1267
+ -9.6013e-02,
1268
+ 7.5809e-02,
1269
+ -1.5899e-01,
1270
+ 8.6961e-02,
1271
+ 1.5239e-03,
1272
+ -5.6370e-02,
1273
+ -4.3367e-02,
1274
+ 1.5813e-02,
1275
+ 9.7189e-04,
1276
+ -5.2946e-02,
1277
+ 4.5950e-02,
1278
+ -1.0028e-01,
1279
+ 1.1108e-01,
1280
+ 9.0491e-03,
1281
+ -3.5540e-02,
1282
+ -1.2020e-02,
1283
+ 2.2980e-01,
1284
+ 2.7125e-02,
1285
+ -2.4191e-02,
1286
+ -1.1363e-01,
1287
+ -1.0109e-01,
1288
+ -1.4781e-01,
1289
+ -4.7656e-02,
1290
+ 3.9481e-02,
1291
+ 5.4198e-02,
1292
+ 8.2908e-02,
1293
+ 1.4034e-02,
1294
+ -1.8492e-02,
1295
+ -6.8612e-02,
1296
+ -4.8741e-02,
1297
+ 1.1223e-02,
1298
+ 3.9220e-02,
1299
+ 5.4551e-04,
1300
+ 6.5554e-02,
1301
+ 4.3087e-02,
1302
+ 1.4678e-01,
1303
+ -4.4496e-02,
1304
+ 6.2379e-02,
1305
+ 4.7876e-02,
1306
+ 7.0156e-02,
1307
+ -6.4684e-02,
1308
+ 6.1076e-02,
1309
+ 1.4685e-01,
1310
+ -6.3639e-02,
1311
+ -8.7487e-02,
1312
+ -1.3756e-02,
1313
+ 1.2724e-01,
1314
+ 1.7404e-01,
1315
+ 6.7980e-02,
1316
+ 6.8036e-02,
1317
+ 1.9786e-01,
1318
+ -9.2910e-02,
1319
+ 1.9158e-01,
1320
+ 4.2686e-02,
1321
+ ],
1322
+ [
1323
+ 4.7626e-02,
1324
+ 9.3338e-02,
1325
+ 9.8020e-02,
1326
+ 9.2408e-02,
1327
+ 1.8267e-02,
1328
+ 1.9572e-02,
1329
+ 1.1056e-01,
1330
+ -1.2639e-01,
1331
+ -3.1999e-02,
1332
+ 1.3731e-01,
1333
+ 1.4826e-01,
1334
+ -6.7136e-02,
1335
+ 8.9095e-02,
1336
+ 1.9683e-01,
1337
+ -6.5839e-02,
1338
+ -1.0708e-01,
1339
+ 1.4414e-01,
1340
+ 4.7389e-02,
1341
+ -7.9921e-02,
1342
+ 2.7793e-02,
1343
+ 9.7240e-02,
1344
+ 3.8000e-03,
1345
+ -4.2359e-02,
1346
+ 5.9870e-02,
1347
+ -8.2957e-02,
1348
+ -2.1429e-02,
1349
+ -5.1302e-02,
1350
+ 7.6202e-02,
1351
+ 3.5426e-02,
1352
+ 8.1509e-02,
1353
+ 1.0849e-01,
1354
+ 2.1857e-01,
1355
+ -9.1445e-02,
1356
+ 1.1869e-01,
1357
+ -1.1225e-02,
1358
+ -1.6530e-01,
1359
+ 2.9216e-02,
1360
+ -7.2792e-02,
1361
+ 2.9544e-02,
1362
+ 5.3102e-02,
1363
+ 2.7746e-02,
1364
+ 1.8145e-01,
1365
+ 4.4050e-02,
1366
+ 8.2549e-02,
1367
+ 2.3603e-02,
1368
+ 1.1494e-02,
1369
+ -9.1516e-02,
1370
+ -6.2333e-02,
1371
+ 1.0970e-01,
1372
+ 1.4544e-01,
1373
+ -2.3549e-02,
1374
+ 3.8348e-02,
1375
+ -8.8504e-03,
1376
+ -1.6986e-01,
1377
+ 5.3215e-02,
1378
+ 4.0085e-02,
1379
+ -1.4667e-01,
1380
+ 8.6047e-02,
1381
+ 4.7751e-02,
1382
+ -9.0753e-03,
1383
+ -8.0938e-02,
1384
+ 7.1735e-02,
1385
+ 1.1711e-01,
1386
+ -4.2525e-02,
1387
+ 9.4244e-02,
1388
+ 6.2260e-02,
1389
+ -8.4798e-02,
1390
+ -6.2766e-02,
1391
+ 6.1025e-02,
1392
+ -7.7480e-02,
1393
+ -5.3010e-02,
1394
+ 1.0182e-01,
1395
+ -7.1081e-03,
1396
+ 1.4029e-01,
1397
+ -8.2788e-02,
1398
+ 3.0182e-02,
1399
+ -1.4279e-01,
1400
+ 4.7882e-02,
1401
+ 3.2736e-02,
1402
+ 1.0058e-02,
1403
+ -1.2673e-02,
1404
+ 3.2723e-02,
1405
+ 5.7034e-02,
1406
+ -2.0343e-02,
1407
+ -6.3037e-03,
1408
+ 6.8914e-03,
1409
+ 2.8643e-02,
1410
+ 8.2739e-02,
1411
+ -2.5124e-02,
1412
+ -7.0611e-02,
1413
+ 2.7142e-01,
1414
+ -1.6370e-02,
1415
+ 1.1240e-02,
1416
+ -1.5776e-01,
1417
+ -8.8755e-02,
1418
+ -1.2389e-01,
1419
+ -2.5785e-02,
1420
+ -3.1258e-02,
1421
+ 2.2023e-02,
1422
+ 8.5497e-02,
1423
+ 6.8360e-02,
1424
+ 9.6662e-03,
1425
+ -1.2843e-01,
1426
+ -7.4122e-02,
1427
+ 7.1105e-02,
1428
+ 5.8334e-02,
1429
+ -1.8609e-02,
1430
+ 7.2775e-02,
1431
+ -1.1696e-03,
1432
+ 1.3165e-01,
1433
+ -4.7326e-02,
1434
+ 6.8737e-02,
1435
+ -2.3211e-02,
1436
+ 5.5425e-02,
1437
+ -6.8077e-02,
1438
+ -7.3743e-04,
1439
+ 1.6215e-01,
1440
+ -4.9965e-02,
1441
+ -6.8493e-02,
1442
+ -6.8358e-03,
1443
+ 9.9518e-02,
1444
+ 1.5627e-01,
1445
+ 1.3100e-01,
1446
+ 9.0073e-03,
1447
+ 1.8023e-01,
1448
+ -2.7992e-02,
1449
+ 1.3087e-01,
1450
+ 7.5672e-02,
1451
+ ],
1452
+ [
1453
+ 9.0390e-03,
1454
+ 1.3137e-01,
1455
+ 4.6586e-02,
1456
+ 1.1836e-01,
1457
+ 1.1406e-01,
1458
+ -7.3655e-02,
1459
+ -1.4340e-02,
1460
+ -1.4285e-01,
1461
+ 2.0426e-02,
1462
+ 1.3532e-01,
1463
+ 1.3353e-01,
1464
+ -4.7242e-02,
1465
+ 2.6449e-02,
1466
+ 2.0707e-02,
1467
+ -7.5049e-02,
1468
+ -4.4098e-02,
1469
+ 1.4599e-01,
1470
+ 1.1299e-01,
1471
+ -2.2097e-02,
1472
+ 8.4679e-02,
1473
+ 5.8946e-02,
1474
+ 2.1213e-02,
1475
+ -5.8343e-03,
1476
+ -4.5902e-02,
1477
+ -6.1018e-02,
1478
+ 4.4368e-02,
1479
+ -5.7242e-02,
1480
+ 1.4734e-01,
1481
+ -2.6191e-02,
1482
+ 8.6116e-02,
1483
+ 4.6004e-02,
1484
+ 9.4532e-02,
1485
+ -1.7424e-01,
1486
+ 1.3697e-01,
1487
+ 4.6544e-02,
1488
+ -1.1718e-01,
1489
+ 8.2567e-02,
1490
+ -3.1494e-02,
1491
+ 6.2073e-02,
1492
+ -4.6328e-02,
1493
+ 1.0782e-02,
1494
+ 6.6071e-02,
1495
+ 9.6953e-03,
1496
+ 2.0415e-02,
1497
+ -7.0939e-02,
1498
+ -3.1883e-02,
1499
+ -4.9926e-02,
1500
+ -1.2694e-01,
1501
+ 1.6351e-01,
1502
+ 1.1670e-01,
1503
+ 3.8149e-02,
1504
+ 7.2657e-02,
1505
+ -1.3930e-01,
1506
+ -1.6682e-01,
1507
+ 1.2471e-01,
1508
+ 1.9094e-02,
1509
+ -6.1618e-02,
1510
+ 5.8628e-02,
1511
+ 1.8804e-03,
1512
+ -9.8104e-03,
1513
+ -1.8998e-01,
1514
+ 1.4179e-02,
1515
+ 1.4856e-01,
1516
+ -4.2194e-02,
1517
+ 5.0934e-02,
1518
+ 7.4201e-02,
1519
+ 1.5546e-02,
1520
+ 2.3192e-02,
1521
+ -5.5886e-02,
1522
+ -4.3618e-02,
1523
+ 3.6677e-02,
1524
+ 1.2750e-01,
1525
+ 2.1240e-02,
1526
+ 9.5849e-02,
1527
+ -1.2006e-01,
1528
+ 6.0424e-02,
1529
+ -1.6516e-01,
1530
+ 7.8015e-02,
1531
+ -7.6026e-02,
1532
+ -1.6711e-02,
1533
+ -3.3572e-02,
1534
+ 4.3704e-02,
1535
+ 1.2728e-02,
1536
+ -6.9630e-02,
1537
+ 4.1065e-02,
1538
+ -8.6589e-02,
1539
+ 1.1581e-01,
1540
+ 2.2666e-02,
1541
+ -5.1476e-02,
1542
+ 2.2756e-02,
1543
+ 1.9309e-01,
1544
+ 7.0417e-02,
1545
+ 8.9253e-03,
1546
+ -1.3546e-01,
1547
+ -7.7783e-02,
1548
+ -1.5058e-01,
1549
+ -4.7874e-02,
1550
+ 3.3498e-02,
1551
+ 6.4585e-02,
1552
+ 7.8633e-02,
1553
+ 1.0162e-01,
1554
+ 4.5821e-02,
1555
+ -2.8424e-02,
1556
+ 4.4524e-02,
1557
+ 4.6649e-02,
1558
+ 4.5805e-02,
1559
+ 8.9723e-02,
1560
+ 3.7597e-02,
1561
+ -6.8016e-03,
1562
+ 1.1727e-01,
1563
+ -6.6387e-02,
1564
+ 3.4958e-02,
1565
+ 4.0205e-02,
1566
+ 7.5854e-02,
1567
+ -5.1322e-02,
1568
+ 6.0060e-02,
1569
+ 1.4631e-01,
1570
+ -1.1550e-01,
1571
+ -1.5388e-01,
1572
+ 6.0975e-03,
1573
+ 1.2013e-01,
1574
+ 2.1669e-01,
1575
+ 4.8245e-02,
1576
+ 8.4634e-02,
1577
+ 1.4934e-01,
1578
+ -1.2099e-01,
1579
+ 1.6673e-01,
1580
+ 6.2539e-02,
1581
+ ],
1582
+ [
1583
+ 1.6222e-01,
1584
+ 7.5644e-03,
1585
+ 1.0667e-01,
1586
+ 8.3084e-02,
1587
+ 5.1888e-02,
1588
+ 8.7477e-02,
1589
+ -1.1550e-01,
1590
+ -4.7037e-02,
1591
+ -3.9418e-02,
1592
+ 8.0987e-02,
1593
+ 1.2812e-01,
1594
+ -6.6527e-02,
1595
+ 6.5405e-02,
1596
+ 5.2723e-03,
1597
+ -6.2940e-02,
1598
+ -5.8805e-02,
1599
+ 2.8526e-02,
1600
+ 4.0631e-02,
1601
+ 9.8513e-02,
1602
+ -2.8448e-02,
1603
+ 9.2361e-02,
1604
+ 6.9724e-02,
1605
+ -1.1792e-01,
1606
+ -1.4806e-02,
1607
+ -6.6172e-02,
1608
+ 5.9468e-02,
1609
+ -1.8325e-01,
1610
+ 3.2851e-02,
1611
+ -2.7042e-02,
1612
+ 1.7569e-01,
1613
+ 1.9061e-01,
1614
+ 4.1868e-02,
1615
+ -1.4116e-01,
1616
+ -1.6780e-02,
1617
+ -6.2881e-02,
1618
+ -9.4655e-02,
1619
+ 5.1488e-02,
1620
+ -3.6501e-03,
1621
+ 5.6754e-02,
1622
+ -6.3285e-03,
1623
+ 9.2234e-02,
1624
+ -1.0761e-01,
1625
+ -5.5392e-02,
1626
+ 7.5249e-02,
1627
+ 8.7539e-02,
1628
+ 4.3377e-02,
1629
+ 3.2473e-03,
1630
+ -1.0990e-01,
1631
+ 5.2611e-02,
1632
+ 3.4955e-02,
1633
+ 1.1722e-01,
1634
+ 4.0339e-02,
1635
+ -9.6605e-02,
1636
+ -9.3660e-02,
1637
+ 9.6494e-02,
1638
+ -1.4790e-02,
1639
+ -5.7770e-02,
1640
+ 4.3192e-02,
1641
+ -8.1741e-02,
1642
+ -4.0909e-02,
1643
+ -9.5212e-02,
1644
+ -1.2459e-01,
1645
+ 3.6241e-02,
1646
+ -1.2878e-01,
1647
+ 1.1544e-01,
1648
+ 3.6392e-03,
1649
+ -7.7986e-03,
1650
+ 8.2339e-02,
1651
+ 1.4319e-01,
1652
+ -5.8374e-02,
1653
+ 1.2483e-01,
1654
+ 5.6826e-02,
1655
+ -6.2649e-02,
1656
+ 4.5884e-02,
1657
+ -1.8261e-01,
1658
+ -9.1633e-02,
1659
+ -1.9414e-01,
1660
+ 2.8376e-02,
1661
+ 9.5654e-02,
1662
+ -1.3532e-01,
1663
+ 2.1669e-03,
1664
+ 7.9960e-02,
1665
+ 5.0917e-02,
1666
+ -5.4874e-02,
1667
+ 7.6823e-02,
1668
+ -1.3932e-01,
1669
+ 1.2431e-01,
1670
+ 1.3001e-01,
1671
+ -6.0069e-02,
1672
+ 2.6470e-02,
1673
+ 2.2378e-01,
1674
+ 2.7335e-02,
1675
+ 1.6487e-02,
1676
+ -9.6473e-02,
1677
+ 8.3110e-02,
1678
+ -8.8776e-02,
1679
+ -6.2299e-02,
1680
+ 4.0821e-02,
1681
+ 6.5279e-02,
1682
+ 8.4030e-02,
1683
+ -4.9298e-02,
1684
+ -3.7441e-02,
1685
+ -4.9233e-02,
1686
+ -1.9428e-03,
1687
+ -1.9763e-02,
1688
+ -9.1249e-02,
1689
+ 1.1593e-01,
1690
+ 6.6291e-03,
1691
+ -1.0686e-01,
1692
+ 5.9383e-02,
1693
+ -3.1088e-02,
1694
+ -1.9844e-03,
1695
+ 2.5891e-02,
1696
+ 1.3471e-01,
1697
+ -3.5624e-02,
1698
+ 1.2647e-01,
1699
+ 6.7167e-02,
1700
+ -1.8682e-02,
1701
+ -2.4806e-02,
1702
+ -1.1453e-02,
1703
+ 9.6992e-02,
1704
+ 1.2253e-01,
1705
+ 5.6707e-02,
1706
+ 1.0860e-02,
1707
+ 2.4775e-01,
1708
+ -8.8641e-02,
1709
+ 1.1375e-01,
1710
+ 6.9383e-02,
1711
+ ],
1712
+ [
1713
+ 1.4724e-01,
1714
+ 3.7511e-02,
1715
+ 1.1238e-01,
1716
+ 6.3809e-02,
1717
+ 1.8174e-03,
1718
+ 8.4854e-02,
1719
+ -7.8965e-02,
1720
+ -3.1542e-02,
1721
+ -3.7643e-02,
1722
+ 1.0136e-01,
1723
+ 1.5837e-01,
1724
+ -7.5701e-02,
1725
+ 1.0313e-01,
1726
+ 2.2795e-02,
1727
+ -1.1227e-01,
1728
+ -4.3783e-02,
1729
+ 4.6561e-02,
1730
+ 4.1225e-02,
1731
+ 7.4273e-02,
1732
+ -4.8200e-02,
1733
+ 2.7666e-02,
1734
+ 5.1417e-02,
1735
+ -1.3928e-01,
1736
+ 2.0020e-02,
1737
+ -9.3673e-02,
1738
+ 5.1440e-02,
1739
+ -2.0076e-01,
1740
+ 1.0715e-01,
1741
+ -3.0323e-02,
1742
+ 1.7241e-01,
1743
+ 1.9730e-01,
1744
+ 7.7685e-02,
1745
+ -1.5949e-01,
1746
+ 4.8495e-02,
1747
+ -8.0068e-02,
1748
+ -7.1965e-02,
1749
+ 8.7222e-02,
1750
+ -4.8383e-02,
1751
+ 7.0627e-02,
1752
+ 3.9791e-03,
1753
+ 7.0469e-02,
1754
+ -8.6697e-02,
1755
+ -3.7149e-02,
1756
+ 7.9965e-02,
1757
+ 8.8967e-02,
1758
+ 5.3325e-02,
1759
+ -5.6073e-03,
1760
+ -1.1336e-01,
1761
+ 1.0338e-01,
1762
+ 2.3824e-02,
1763
+ 6.9393e-02,
1764
+ 6.1018e-02,
1765
+ -7.2880e-02,
1766
+ -6.6328e-02,
1767
+ 8.9040e-02,
1768
+ -8.8268e-03,
1769
+ -7.5720e-02,
1770
+ 3.7544e-02,
1771
+ -8.3740e-02,
1772
+ -4.7977e-02,
1773
+ -1.2425e-01,
1774
+ -1.0304e-01,
1775
+ -3.7850e-03,
1776
+ -1.1540e-01,
1777
+ 1.0803e-01,
1778
+ 2.5964e-02,
1779
+ -1.0915e-02,
1780
+ 1.1758e-01,
1781
+ 1.6561e-01,
1782
+ -4.5171e-02,
1783
+ 1.1272e-01,
1784
+ 6.0721e-02,
1785
+ -9.6027e-02,
1786
+ 6.0908e-02,
1787
+ -1.6545e-01,
1788
+ -6.5444e-02,
1789
+ -1.6802e-01,
1790
+ 2.5850e-02,
1791
+ 1.2434e-01,
1792
+ -1.2358e-01,
1793
+ -5.2647e-04,
1794
+ 1.2329e-01,
1795
+ 6.3928e-02,
1796
+ -5.5478e-02,
1797
+ 5.4642e-02,
1798
+ -1.0372e-01,
1799
+ 1.0955e-01,
1800
+ 1.1810e-01,
1801
+ -7.7028e-02,
1802
+ 5.0302e-02,
1803
+ 2.7576e-01,
1804
+ 6.4704e-02,
1805
+ 3.7824e-03,
1806
+ -8.3016e-02,
1807
+ 6.7355e-02,
1808
+ -4.9660e-02,
1809
+ -1.2677e-01,
1810
+ 3.4498e-02,
1811
+ 2.7027e-02,
1812
+ 9.1476e-02,
1813
+ -5.1170e-02,
1814
+ -3.4786e-02,
1815
+ -4.4356e-02,
1816
+ 6.2440e-03,
1817
+ -3.5473e-02,
1818
+ -1.1076e-01,
1819
+ 1.0014e-01,
1820
+ -1.5927e-02,
1821
+ -7.9539e-02,
1822
+ 1.1542e-01,
1823
+ -3.6775e-03,
1824
+ 2.5437e-02,
1825
+ 2.1185e-02,
1826
+ 1.0472e-01,
1827
+ -4.8937e-03,
1828
+ 4.5876e-02,
1829
+ 5.3122e-02,
1830
+ -1.9815e-02,
1831
+ -1.8126e-02,
1832
+ -2.3358e-02,
1833
+ 1.2271e-01,
1834
+ 9.8860e-02,
1835
+ 4.5235e-02,
1836
+ 6.2841e-02,
1837
+ 1.6294e-01,
1838
+ -1.0909e-01,
1839
+ 7.2012e-02,
1840
+ 5.3389e-02,
1841
+ ],
1842
+ [
1843
+ 1.2605e-01,
1844
+ 5.1723e-02,
1845
+ 1.2152e-01,
1846
+ 5.8291e-02,
1847
+ -3.8019e-02,
1848
+ 8.8117e-02,
1849
+ -4.1927e-02,
1850
+ -2.3022e-02,
1851
+ -3.1158e-02,
1852
+ 1.2518e-01,
1853
+ 1.4279e-01,
1854
+ -7.1922e-02,
1855
+ 1.0867e-01,
1856
+ 3.3116e-02,
1857
+ -1.3669e-01,
1858
+ -3.2413e-02,
1859
+ 9.0301e-02,
1860
+ 5.9203e-02,
1861
+ -7.5021e-03,
1862
+ -6.9837e-02,
1863
+ -1.2463e-03,
1864
+ 1.8891e-02,
1865
+ -1.1878e-01,
1866
+ 6.1658e-02,
1867
+ -1.2649e-01,
1868
+ 4.2450e-02,
1869
+ -1.6298e-01,
1870
+ 1.5450e-01,
1871
+ -2.6732e-02,
1872
+ 1.7891e-01,
1873
+ 2.1083e-01,
1874
+ 1.0432e-01,
1875
+ -1.3396e-01,
1876
+ 6.9416e-02,
1877
+ -7.3193e-02,
1878
+ -6.8281e-02,
1879
+ 9.9454e-02,
1880
+ -4.4528e-02,
1881
+ 7.7992e-02,
1882
+ -2.6336e-03,
1883
+ 6.9279e-02,
1884
+ -3.8836e-02,
1885
+ -2.8872e-02,
1886
+ 5.6951e-02,
1887
+ 8.7154e-02,
1888
+ 6.1652e-02,
1889
+ -1.8049e-02,
1890
+ -1.2096e-01,
1891
+ 1.3820e-01,
1892
+ 5.1705e-02,
1893
+ 3.4052e-02,
1894
+ 1.1524e-01,
1895
+ -4.1216e-02,
1896
+ -8.5670e-02,
1897
+ 7.1571e-02,
1898
+ -1.6001e-02,
1899
+ -8.8427e-02,
1900
+ 3.1357e-02,
1901
+ -5.4897e-02,
1902
+ -3.1463e-02,
1903
+ -1.2070e-01,
1904
+ -5.7460e-02,
1905
+ -2.1424e-02,
1906
+ -1.2160e-01,
1907
+ 1.0026e-01,
1908
+ 4.7475e-02,
1909
+ -1.4578e-02,
1910
+ 1.3247e-01,
1911
+ 1.5272e-01,
1912
+ -2.9788e-02,
1913
+ 8.2947e-02,
1914
+ 7.9830e-02,
1915
+ -1.1615e-01,
1916
+ 7.2949e-02,
1917
+ -1.2386e-01,
1918
+ -5.3969e-02,
1919
+ -1.3578e-01,
1920
+ 4.0461e-02,
1921
+ 1.2467e-01,
1922
+ -9.4575e-02,
1923
+ 6.2182e-04,
1924
+ 1.3108e-01,
1925
+ 6.5035e-02,
1926
+ -5.7784e-02,
1927
+ 4.2513e-02,
1928
+ -7.8118e-02,
1929
+ 1.1274e-01,
1930
+ 8.9333e-02,
1931
+ -4.9991e-02,
1932
+ 7.4639e-02,
1933
+ 3.0876e-01,
1934
+ 7.3974e-02,
1935
+ -1.4039e-02,
1936
+ -7.4526e-02,
1937
+ 6.5200e-02,
1938
+ -3.6421e-02,
1939
+ -1.6730e-01,
1940
+ 4.1713e-02,
1941
+ -1.5316e-02,
1942
+ 1.0056e-01,
1943
+ -5.0370e-02,
1944
+ -4.4531e-02,
1945
+ -5.0233e-02,
1946
+ 3.2636e-02,
1947
+ -1.7609e-02,
1948
+ -1.1354e-01,
1949
+ 8.1922e-02,
1950
+ -2.0907e-02,
1951
+ -1.3264e-02,
1952
+ 1.5115e-01,
1953
+ 1.2777e-02,
1954
+ 3.8261e-02,
1955
+ 2.1829e-02,
1956
+ 5.5874e-02,
1957
+ 6.8772e-03,
1958
+ -2.7772e-02,
1959
+ 6.7979e-02,
1960
+ -3.9099e-02,
1961
+ -2.8619e-02,
1962
+ -5.6936e-02,
1963
+ 1.3366e-01,
1964
+ 9.4269e-02,
1965
+ 4.4852e-02,
1966
+ 7.6881e-02,
1967
+ 9.6787e-02,
1968
+ -1.2885e-01,
1969
+ 7.9722e-02,
1970
+ 6.4991e-02,
1971
+ ],
1972
+ [
1973
+ 1.0575e-01,
1974
+ 6.4565e-02,
1975
+ 1.2479e-01,
1976
+ 5.5616e-02,
1977
+ -5.5869e-02,
1978
+ 8.9005e-02,
1979
+ -1.9612e-02,
1980
+ -3.3032e-02,
1981
+ -3.9719e-02,
1982
+ 1.3884e-01,
1983
+ 1.2010e-01,
1984
+ -6.3224e-02,
1985
+ 1.0893e-01,
1986
+ 4.4172e-02,
1987
+ -1.4283e-01,
1988
+ -3.8972e-02,
1989
+ 1.1470e-01,
1990
+ 7.7830e-02,
1991
+ -4.6450e-02,
1992
+ -8.7246e-02,
1993
+ -5.7215e-03,
1994
+ 1.7476e-02,
1995
+ -9.0388e-02,
1996
+ 7.4607e-02,
1997
+ -1.3105e-01,
1998
+ 2.9371e-02,
1999
+ -1.2681e-01,
2000
+ 1.6014e-01,
2001
+ -1.3655e-02,
2002
+ 1.8492e-01,
2003
+ 1.9841e-01,
2004
+ 1.0942e-01,
2005
+ -1.1261e-01,
2006
+ 6.8326e-02,
2007
+ -5.1130e-02,
2008
+ -7.1296e-02,
2009
+ 9.3895e-02,
2010
+ -3.1097e-02,
2011
+ 8.7043e-02,
2012
+ -1.2087e-02,
2013
+ 7.3054e-02,
2014
+ -1.0602e-02,
2015
+ -3.6347e-02,
2016
+ 3.1324e-02,
2017
+ 7.5686e-02,
2018
+ 5.3221e-02,
2019
+ -3.1793e-02,
2020
+ -1.3818e-01,
2021
+ 1.4972e-01,
2022
+ 9.0217e-02,
2023
+ 2.6365e-02,
2024
+ 1.1733e-01,
2025
+ -2.6107e-02,
2026
+ -1.1933e-01,
2027
+ 6.5575e-02,
2028
+ -1.6907e-02,
2029
+ -1.0186e-01,
2030
+ 3.3719e-02,
2031
+ -3.4021e-02,
2032
+ -1.0597e-02,
2033
+ -1.0478e-01,
2034
+ -3.4765e-02,
2035
+ -3.0666e-03,
2036
+ -1.2035e-01,
2037
+ 1.1053e-01,
2038
+ 5.2212e-02,
2039
+ -1.8638e-02,
2040
+ 1.2226e-01,
2041
+ 1.3614e-01,
2042
+ -4.3806e-02,
2043
+ 4.7251e-02,
2044
+ 9.2144e-02,
2045
+ -1.2505e-01,
2046
+ 8.3020e-02,
2047
+ -1.1203e-01,
2048
+ -4.9654e-02,
2049
+ -1.1939e-01,
2050
+ 4.4126e-02,
2051
+ 1.1188e-01,
2052
+ -7.8910e-02,
2053
+ -4.1917e-03,
2054
+ 1.3279e-01,
2055
+ 5.0291e-02,
2056
+ -6.1770e-02,
2057
+ 5.1914e-02,
2058
+ -6.8796e-02,
2059
+ 1.2148e-01,
2060
+ 7.6837e-02,
2061
+ -4.0098e-02,
2062
+ 8.3547e-02,
2063
+ 3.0367e-01,
2064
+ 6.9357e-02,
2065
+ -2.5710e-02,
2066
+ -7.2644e-02,
2067
+ 5.2612e-02,
2068
+ -5.3110e-02,
2069
+ -1.7360e-01,
2070
+ 5.2791e-02,
2071
+ -1.6490e-02,
2072
+ 9.9930e-02,
2073
+ -4.8174e-02,
2074
+ -3.0230e-02,
2075
+ -6.0898e-02,
2076
+ 3.4064e-02,
2077
+ 8.2446e-03,
2078
+ -1.0747e-01,
2079
+ 6.9406e-02,
2080
+ -9.2400e-03,
2081
+ 2.1804e-02,
2082
+ 1.5491e-01,
2083
+ 2.0964e-02,
2084
+ 3.4691e-02,
2085
+ 3.5648e-02,
2086
+ 4.4363e-02,
2087
+ -3.2605e-03,
2088
+ -5.9988e-02,
2089
+ 9.3354e-02,
2090
+ -7.4183e-02,
2091
+ -3.5824e-02,
2092
+ -7.1163e-02,
2093
+ 1.4776e-01,
2094
+ 1.0179e-01,
2095
+ 4.1685e-02,
2096
+ 6.7698e-02,
2097
+ 8.4053e-02,
2098
+ -1.4797e-01,
2099
+ 9.9115e-02,
2100
+ 7.2061e-02,
2101
+ ],
2102
+ [
2103
+ 8.3987e-02,
2104
+ 6.9951e-02,
2105
+ 1.3728e-01,
2106
+ 6.4616e-02,
2107
+ -5.5591e-02,
2108
+ 8.8007e-02,
2109
+ -1.7926e-02,
2110
+ -4.8462e-02,
2111
+ -4.8522e-02,
2112
+ 1.5932e-01,
2113
+ 1.0766e-01,
2114
+ -4.9589e-02,
2115
+ 1.0511e-01,
2116
+ 4.4010e-02,
2117
+ -1.3653e-01,
2118
+ -4.1380e-02,
2119
+ 1.3629e-01,
2120
+ 9.2007e-02,
2121
+ -5.2961e-02,
2122
+ -7.1865e-02,
2123
+ -2.8329e-03,
2124
+ 1.3417e-02,
2125
+ -5.2565e-02,
2126
+ 6.2258e-02,
2127
+ -1.2135e-01,
2128
+ 2.7531e-02,
2129
+ -9.4902e-02,
2130
+ 1.6567e-01,
2131
+ -1.6613e-02,
2132
+ 1.8443e-01,
2133
+ 1.6290e-01,
2134
+ 1.1109e-01,
2135
+ -1.1476e-01,
2136
+ 6.5613e-02,
2137
+ -1.7283e-02,
2138
+ -8.7120e-02,
2139
+ 1.0862e-01,
2140
+ -2.5325e-02,
2141
+ 8.9025e-02,
2142
+ -1.8290e-02,
2143
+ 7.2979e-02,
2144
+ 1.1878e-04,
2145
+ -4.1007e-02,
2146
+ 1.9679e-02,
2147
+ 6.1887e-02,
2148
+ 4.0949e-02,
2149
+ -3.6016e-02,
2150
+ -1.5569e-01,
2151
+ 1.4839e-01,
2152
+ 1.1372e-01,
2153
+ 1.8360e-02,
2154
+ 9.3030e-02,
2155
+ -1.8070e-02,
2156
+ -1.5578e-01,
2157
+ 6.1180e-02,
2158
+ -1.9569e-02,
2159
+ -1.1221e-01,
2160
+ 4.0037e-02,
2161
+ -2.3712e-02,
2162
+ -1.0311e-02,
2163
+ -1.0530e-01,
2164
+ -1.2916e-02,
2165
+ 3.1546e-02,
2166
+ -1.1647e-01,
2167
+ 1.2109e-01,
2168
+ 5.8714e-02,
2169
+ -1.9454e-02,
2170
+ 9.9438e-02,
2171
+ 1.1088e-01,
2172
+ -7.4443e-02,
2173
+ 1.6456e-02,
2174
+ 8.7619e-02,
2175
+ -1.1460e-01,
2176
+ 9.7142e-02,
2177
+ -1.1083e-01,
2178
+ -5.2367e-02,
2179
+ -1.2157e-01,
2180
+ 4.6784e-02,
2181
+ 8.5968e-02,
2182
+ -7.2488e-02,
2183
+ -1.2658e-02,
2184
+ 1.4136e-01,
2185
+ 1.7934e-02,
2186
+ -6.4075e-02,
2187
+ 5.3809e-02,
2188
+ -5.8649e-02,
2189
+ 1.2410e-01,
2190
+ 6.7307e-02,
2191
+ -5.3157e-02,
2192
+ 8.5616e-02,
2193
+ 2.8266e-01,
2194
+ 6.4775e-02,
2195
+ -2.9330e-02,
2196
+ -8.7733e-02,
2197
+ 1.3974e-02,
2198
+ -7.9092e-02,
2199
+ -1.6691e-01,
2200
+ 5.9037e-02,
2201
+ 4.1784e-03,
2202
+ 9.8219e-02,
2203
+ -3.6970e-02,
2204
+ 8.6754e-03,
2205
+ -5.5234e-02,
2206
+ 3.4627e-02,
2207
+ 3.5126e-02,
2208
+ -9.5508e-02,
2209
+ 6.4539e-02,
2210
+ -9.1506e-04,
2211
+ 2.9144e-02,
2212
+ 1.3975e-01,
2213
+ 1.5472e-02,
2214
+ 3.9015e-02,
2215
+ 4.6270e-02,
2216
+ 5.4053e-02,
2217
+ -1.2525e-02,
2218
+ -6.6168e-02,
2219
+ 1.2463e-01,
2220
+ -1.0793e-01,
2221
+ -4.0659e-02,
2222
+ -8.1224e-02,
2223
+ 1.5702e-01,
2224
+ 1.2323e-01,
2225
+ 3.3473e-02,
2226
+ 6.1162e-02,
2227
+ 8.3375e-02,
2228
+ -1.7863e-01,
2229
+ 1.1536e-01,
2230
+ 6.9906e-02,
2231
+ ],
2232
+ [
2233
+ 6.2508e-02,
2234
+ 8.5713e-02,
2235
+ 1.3566e-01,
2236
+ 8.5148e-02,
2237
+ -2.2642e-02,
2238
+ 5.8889e-02,
2239
+ -3.3654e-02,
2240
+ -6.6948e-02,
2241
+ -5.1541e-02,
2242
+ 1.7082e-01,
2243
+ 1.1639e-01,
2244
+ -3.9750e-02,
2245
+ 8.5338e-02,
2246
+ 2.8182e-02,
2247
+ -1.1108e-01,
2248
+ -3.5316e-02,
2249
+ 1.4888e-01,
2250
+ 1.0210e-01,
2251
+ -3.4537e-02,
2252
+ -1.1950e-02,
2253
+ 5.0482e-03,
2254
+ 1.8051e-02,
2255
+ -2.2067e-02,
2256
+ 1.8682e-02,
2257
+ -9.7924e-02,
2258
+ 2.6326e-02,
2259
+ -7.6128e-02,
2260
+ 1.7675e-01,
2261
+ -1.6092e-02,
2262
+ 1.6355e-01,
2263
+ 1.1133e-01,
2264
+ 1.1036e-01,
2265
+ -1.4070e-01,
2266
+ 6.9723e-02,
2267
+ 2.0482e-02,
2268
+ -1.0421e-01,
2269
+ 1.2920e-01,
2270
+ -3.1272e-02,
2271
+ 7.6070e-02,
2272
+ -3.1384e-02,
2273
+ 6.7374e-02,
2274
+ -8.3764e-05,
2275
+ -2.1820e-02,
2276
+ 1.4192e-02,
2277
+ 2.3975e-02,
2278
+ 2.3384e-02,
2279
+ -2.3589e-02,
2280
+ -1.7196e-01,
2281
+ 1.4986e-01,
2282
+ 1.1465e-01,
2283
+ 2.0120e-02,
2284
+ 4.8034e-02,
2285
+ -3.9982e-02,
2286
+ -1.8704e-01,
2287
+ 6.2222e-02,
2288
+ -1.4799e-02,
2289
+ -1.0783e-01,
2290
+ 5.4098e-02,
2291
+ -1.2046e-02,
2292
+ -1.5444e-02,
2293
+ -1.4001e-01,
2294
+ 1.5944e-02,
2295
+ 7.9476e-02,
2296
+ -9.7107e-02,
2297
+ 1.0629e-01,
2298
+ 7.1731e-02,
2299
+ -8.1614e-03,
2300
+ 6.2800e-02,
2301
+ 5.5151e-02,
2302
+ -1.0514e-01,
2303
+ 1.7916e-02,
2304
+ 8.1229e-02,
2305
+ -7.8761e-02,
2306
+ 1.1222e-01,
2307
+ -1.1032e-01,
2308
+ -4.8039e-02,
2309
+ -1.5340e-01,
2310
+ 6.0481e-02,
2311
+ 3.2276e-02,
2312
+ -6.2797e-02,
2313
+ -3.3934e-02,
2314
+ 1.3062e-01,
2315
+ -6.3068e-03,
2316
+ -6.5936e-02,
2317
+ 4.2361e-02,
2318
+ -4.6102e-02,
2319
+ 1.2669e-01,
2320
+ 6.1909e-02,
2321
+ -5.7771e-02,
2322
+ 7.8385e-02,
2323
+ 2.4836e-01,
2324
+ 7.5433e-02,
2325
+ -2.0151e-02,
2326
+ -1.2117e-01,
2327
+ -4.1874e-02,
2328
+ -1.1751e-01,
2329
+ -1.4471e-01,
2330
+ 6.3096e-02,
2331
+ 2.9419e-02,
2332
+ 1.0021e-01,
2333
+ -1.2180e-02,
2334
+ 5.0896e-02,
2335
+ -4.0094e-02,
2336
+ 4.1059e-02,
2337
+ 8.0853e-02,
2338
+ -7.4614e-02,
2339
+ 7.5243e-02,
2340
+ 1.4928e-02,
2341
+ 1.1477e-02,
2342
+ 1.2385e-01,
2343
+ -1.4719e-02,
2344
+ 5.3751e-02,
2345
+ 5.2591e-02,
2346
+ 7.2013e-02,
2347
+ -1.6333e-02,
2348
+ -5.0256e-02,
2349
+ 1.4199e-01,
2350
+ -1.3433e-01,
2351
+ -7.1574e-02,
2352
+ -7.4449e-02,
2353
+ 1.6136e-01,
2354
+ 1.5659e-01,
2355
+ 2.1466e-02,
2356
+ 6.9775e-02,
2357
+ 1.0352e-01,
2358
+ -1.9641e-01,
2359
+ 1.3156e-01,
2360
+ 5.5903e-02,
2361
+ ],
2362
+ [
2363
+ 4.4988e-02,
2364
+ 1.0390e-01,
2365
+ 1.1266e-01,
2366
+ 1.1661e-01,
2367
+ 2.1212e-02,
2368
+ 1.4641e-02,
2369
+ -3.1484e-02,
2370
+ -8.3443e-02,
2371
+ -4.4991e-02,
2372
+ 1.5687e-01,
2373
+ 1.3158e-01,
2374
+ -3.3434e-02,
2375
+ 6.5104e-02,
2376
+ 2.0808e-02,
2377
+ -7.0680e-02,
2378
+ -4.2508e-02,
2379
+ 1.4304e-01,
2380
+ 9.8483e-02,
2381
+ -2.6904e-02,
2382
+ 3.2902e-02,
2383
+ 1.1152e-02,
2384
+ 2.1118e-02,
2385
+ -1.4842e-02,
2386
+ -2.2356e-02,
2387
+ -8.6942e-02,
2388
+ 2.6609e-02,
2389
+ -6.2533e-02,
2390
+ 1.7495e-01,
2391
+ -1.0246e-02,
2392
+ 1.2697e-01,
2393
+ 7.9345e-02,
2394
+ 1.0189e-01,
2395
+ -1.7178e-01,
2396
+ 7.5006e-02,
2397
+ 4.5051e-02,
2398
+ -1.0753e-01,
2399
+ 1.2829e-01,
2400
+ -3.3422e-02,
2401
+ 7.8522e-02,
2402
+ -4.5604e-02,
2403
+ 5.2822e-02,
2404
+ 8.3934e-03,
2405
+ -1.4676e-02,
2406
+ 1.0482e-02,
2407
+ -6.9389e-03,
2408
+ 2.2131e-03,
2409
+ -2.0089e-02,
2410
+ -1.7261e-01,
2411
+ 1.5468e-01,
2412
+ 1.1537e-01,
2413
+ 2.7489e-02,
2414
+ 2.9948e-02,
2415
+ -7.4234e-02,
2416
+ -1.9946e-01,
2417
+ 7.4632e-02,
2418
+ 2.2249e-03,
2419
+ -9.7061e-02,
2420
+ 6.4988e-02,
2421
+ -1.1239e-02,
2422
+ -2.6152e-02,
2423
+ -1.6496e-01,
2424
+ 3.0305e-02,
2425
+ 1.0491e-01,
2426
+ -6.7692e-02,
2427
+ 7.3323e-02,
2428
+ 7.0258e-02,
2429
+ 1.2020e-02,
2430
+ 4.1457e-02,
2431
+ 3.7126e-03,
2432
+ -1.0465e-01,
2433
+ 2.7035e-02,
2434
+ 8.1376e-02,
2435
+ -4.0890e-02,
2436
+ 1.0791e-01,
2437
+ -1.2141e-01,
2438
+ -1.6070e-02,
2439
+ -1.7111e-01,
2440
+ 7.4218e-02,
2441
+ -2.4698e-02,
2442
+ -3.7496e-02,
2443
+ -4.5666e-02,
2444
+ 1.0478e-01,
2445
+ -1.7289e-03,
2446
+ -7.1823e-02,
2447
+ 2.7480e-02,
2448
+ -5.5305e-02,
2449
+ 1.2323e-01,
2450
+ 5.3008e-02,
2451
+ -5.6903e-02,
2452
+ 6.6087e-02,
2453
+ 2.1523e-01,
2454
+ 8.2657e-02,
2455
+ -9.3275e-03,
2456
+ -1.3791e-01,
2457
+ -6.7856e-02,
2458
+ -1.4959e-01,
2459
+ -1.1916e-01,
2460
+ 6.2014e-02,
2461
+ 3.8735e-02,
2462
+ 9.7171e-02,
2463
+ 2.6820e-02,
2464
+ 7.4336e-02,
2465
+ -2.9922e-02,
2466
+ 5.3645e-02,
2467
+ 1.0981e-01,
2468
+ -3.7215e-02,
2469
+ 9.4254e-02,
2470
+ 4.2902e-02,
2471
+ -2.0969e-03,
2472
+ 1.1838e-01,
2473
+ -5.4015e-02,
2474
+ 5.3799e-02,
2475
+ 4.8568e-02,
2476
+ 8.0494e-02,
2477
+ -2.1410e-02,
2478
+ -2.0629e-02,
2479
+ 1.3477e-01,
2480
+ -1.4177e-01,
2481
+ -1.1144e-01,
2482
+ -5.1781e-02,
2483
+ 1.5780e-01,
2484
+ 1.9357e-01,
2485
+ 2.5627e-02,
2486
+ 7.5402e-02,
2487
+ 1.1982e-01,
2488
+ -1.8961e-01,
2489
+ 1.4646e-01,
2490
+ 4.2665e-02,
2491
+ ],
2492
+ [
2493
+ 5.1689e-02,
2494
+ 1.0737e-01,
2495
+ 1.1035e-01,
2496
+ 1.1308e-01,
2497
+ 2.0363e-02,
2498
+ 1.9703e-02,
2499
+ -3.2107e-02,
2500
+ -9.0838e-02,
2501
+ -3.7066e-02,
2502
+ 1.5839e-01,
2503
+ 1.3202e-01,
2504
+ -3.9661e-02,
2505
+ 6.3263e-02,
2506
+ 1.4509e-02,
2507
+ -6.0145e-02,
2508
+ -4.1459e-02,
2509
+ 1.4670e-01,
2510
+ 9.4701e-02,
2511
+ -2.7710e-02,
2512
+ 3.4281e-02,
2513
+ 1.2373e-02,
2514
+ 2.0087e-02,
2515
+ -2.0821e-02,
2516
+ -1.6274e-02,
2517
+ -7.9908e-02,
2518
+ 2.6209e-02,
2519
+ -7.0731e-02,
2520
+ 1.7365e-01,
2521
+ -8.5357e-03,
2522
+ 1.2784e-01,
2523
+ 7.9591e-02,
2524
+ 9.7291e-02,
2525
+ -1.6958e-01,
2526
+ 7.5774e-02,
2527
+ 4.4993e-02,
2528
+ -1.0499e-01,
2529
+ 1.2133e-01,
2530
+ -4.2564e-02,
2531
+ 7.7296e-02,
2532
+ -3.9049e-02,
2533
+ 5.4639e-02,
2534
+ 3.7831e-03,
2535
+ -1.2108e-02,
2536
+ 1.7782e-02,
2537
+ -1.8517e-03,
2538
+ 1.1160e-02,
2539
+ -1.9819e-02,
2540
+ -1.7390e-01,
2541
+ 1.5853e-01,
2542
+ 1.1225e-01,
2543
+ 2.6839e-02,
2544
+ 3.1746e-02,
2545
+ -8.1013e-02,
2546
+ -1.9950e-01,
2547
+ 7.7933e-02,
2548
+ 4.3883e-03,
2549
+ -9.1775e-02,
2550
+ 6.3761e-02,
2551
+ -2.0220e-02,
2552
+ -2.0873e-02,
2553
+ -1.6981e-01,
2554
+ 3.2587e-02,
2555
+ 1.0188e-01,
2556
+ -7.2338e-02,
2557
+ 7.9739e-02,
2558
+ 7.2336e-02,
2559
+ 1.2712e-02,
2560
+ 4.6304e-02,
2561
+ 1.1977e-02,
2562
+ -9.9069e-02,
2563
+ 3.2361e-02,
2564
+ 7.4647e-02,
2565
+ -3.2075e-02,
2566
+ 1.0834e-01,
2567
+ -1.2421e-01,
2568
+ -1.4408e-02,
2569
+ -1.7591e-01,
2570
+ 7.6520e-02,
2571
+ -2.1893e-02,
2572
+ -4.0591e-02,
2573
+ -3.9790e-02,
2574
+ 1.0072e-01,
2575
+ 2.1907e-03,
2576
+ -7.5756e-02,
2577
+ 3.4002e-02,
2578
+ -6.2571e-02,
2579
+ 1.2714e-01,
2580
+ 5.2011e-02,
2581
+ -5.6154e-02,
2582
+ 6.2876e-02,
2583
+ 2.0992e-01,
2584
+ 8.9547e-02,
2585
+ -1.2875e-02,
2586
+ -1.3528e-01,
2587
+ -6.6275e-02,
2588
+ -1.4322e-01,
2589
+ -1.0560e-01,
2590
+ 5.9555e-02,
2591
+ 4.4426e-02,
2592
+ 1.0047e-01,
2593
+ 3.3182e-02,
2594
+ 7.2001e-02,
2595
+ -3.6514e-02,
2596
+ 5.0708e-02,
2597
+ 1.0564e-01,
2598
+ -4.1174e-02,
2599
+ 1.0378e-01,
2600
+ 4.2651e-02,
2601
+ -2.6353e-03,
2602
+ 1.1964e-01,
2603
+ -6.2808e-02,
2604
+ 5.9555e-02,
2605
+ 5.4234e-02,
2606
+ 8.3802e-02,
2607
+ -1.2534e-02,
2608
+ -1.7949e-02,
2609
+ 1.3403e-01,
2610
+ -1.3682e-01,
2611
+ -1.1048e-01,
2612
+ -5.1154e-02,
2613
+ 1.5967e-01,
2614
+ 1.9743e-01,
2615
+ 3.2240e-02,
2616
+ 7.4049e-02,
2617
+ 1.2060e-01,
2618
+ -1.9087e-01,
2619
+ 1.3942e-01,
2620
+ 4.1168e-02,
2621
+ ],
2622
+ [
2623
+ 7.2669e-02,
2624
+ 1.0343e-01,
2625
+ 1.1966e-01,
2626
+ 9.3777e-02,
2627
+ -4.3358e-03,
2628
+ 5.0720e-02,
2629
+ -3.8923e-02,
2630
+ -9.1626e-02,
2631
+ -3.0056e-02,
2632
+ 1.6863e-01,
2633
+ 1.2676e-01,
2634
+ -5.3184e-02,
2635
+ 7.4056e-02,
2636
+ 1.5517e-02,
2637
+ -7.5657e-02,
2638
+ -3.7712e-02,
2639
+ 1.5280e-01,
2640
+ 9.0719e-02,
2641
+ -3.1311e-02,
2642
+ 1.7038e-02,
2643
+ 1.0062e-02,
2644
+ 1.4759e-02,
2645
+ -3.4438e-02,
2646
+ 1.5206e-02,
2647
+ -7.7897e-02,
2648
+ 2.7431e-02,
2649
+ -9.0602e-02,
2650
+ 1.7164e-01,
2651
+ -9.9721e-03,
2652
+ 1.4776e-01,
2653
+ 1.0475e-01,
2654
+ 9.0160e-02,
2655
+ -1.5270e-01,
2656
+ 7.4838e-02,
2657
+ 2.4463e-02,
2658
+ -1.0180e-01,
2659
+ 1.1501e-01,
2660
+ -5.0178e-02,
2661
+ 7.2532e-02,
2662
+ -1.8656e-02,
2663
+ 6.9202e-02,
2664
+ -9.6694e-03,
2665
+ -1.8219e-02,
2666
+ 3.5159e-02,
2667
+ 2.5262e-02,
2668
+ 3.4003e-02,
2669
+ -1.4053e-02,
2670
+ -1.7146e-01,
2671
+ 1.6299e-01,
2672
+ 1.0628e-01,
2673
+ 2.3901e-02,
2674
+ 4.2174e-02,
2675
+ -7.0690e-02,
2676
+ -1.8996e-01,
2677
+ 7.4249e-02,
2678
+ -2.3634e-03,
2679
+ -8.9371e-02,
2680
+ 5.7919e-02,
2681
+ -3.2949e-02,
2682
+ -2.4951e-03,
2683
+ -1.6144e-01,
2684
+ 3.1264e-02,
2685
+ 8.4885e-02,
2686
+ -9.7903e-02,
2687
+ 1.0806e-01,
2688
+ 7.3583e-02,
2689
+ 1.4389e-03,
2690
+ 6.4343e-02,
2691
+ 5.4305e-02,
2692
+ -9.2823e-02,
2693
+ 3.4699e-02,
2694
+ 6.5887e-02,
2695
+ -4.1142e-02,
2696
+ 1.1057e-01,
2697
+ -1.2077e-01,
2698
+ -3.4178e-02,
2699
+ -1.7040e-01,
2700
+ 7.2546e-02,
2701
+ 1.9399e-02,
2702
+ -6.3051e-02,
2703
+ -3.0156e-02,
2704
+ 1.1501e-01,
2705
+ 3.9456e-03,
2706
+ -7.8346e-02,
2707
+ 5.0548e-02,
2708
+ -6.3681e-02,
2709
+ 1.3261e-01,
2710
+ 6.1531e-02,
2711
+ -5.1743e-02,
2712
+ 6.4762e-02,
2713
+ 2.3264e-01,
2714
+ 9.4896e-02,
2715
+ -2.0536e-02,
2716
+ -1.1807e-01,
2717
+ -4.9545e-02,
2718
+ -1.1460e-01,
2719
+ -1.0079e-01,
2720
+ 5.7836e-02,
2721
+ 4.4144e-02,
2722
+ 1.1039e-01,
2723
+ 2.1311e-02,
2724
+ 4.8694e-02,
2725
+ -4.7126e-02,
2726
+ 3.7200e-02,
2727
+ 8.3108e-02,
2728
+ -7.0069e-02,
2729
+ 9.7451e-02,
2730
+ 2.0983e-02,
2731
+ -4.1628e-04,
2732
+ 1.2463e-01,
2733
+ -5.0752e-02,
2734
+ 7.0680e-02,
2735
+ 6.3588e-02,
2736
+ 8.6767e-02,
2737
+ 6.3361e-04,
2738
+ -2.5236e-02,
2739
+ 1.3658e-01,
2740
+ -1.1925e-01,
2741
+ -8.3861e-02,
2742
+ -6.3728e-02,
2743
+ 1.6767e-01,
2744
+ 1.7967e-01,
2745
+ 3.3894e-02,
2746
+ 7.2830e-02,
2747
+ 1.1590e-01,
2748
+ -1.9359e-01,
2749
+ 1.2254e-01,
2750
+ 4.7767e-02,
2751
+ ],
2752
+ [
2753
+ 8.4189e-02,
2754
+ 9.3299e-02,
2755
+ 1.1532e-01,
2756
+ 8.4670e-02,
2757
+ -9.7533e-03,
2758
+ 6.4423e-02,
2759
+ -4.3742e-02,
2760
+ -9.0334e-02,
2761
+ -1.9307e-02,
2762
+ 1.7434e-01,
2763
+ 1.2522e-01,
2764
+ -6.4064e-02,
2765
+ 7.7841e-02,
2766
+ 2.5594e-02,
2767
+ -9.4764e-02,
2768
+ -3.8088e-02,
2769
+ 1.5148e-01,
2770
+ 8.7002e-02,
2771
+ -3.8634e-02,
2772
+ 1.6073e-02,
2773
+ 1.0152e-02,
2774
+ 8.9035e-03,
2775
+ -3.9583e-02,
2776
+ 3.3614e-02,
2777
+ -7.4457e-02,
2778
+ 2.6379e-02,
2779
+ -9.8885e-02,
2780
+ 1.6510e-01,
2781
+ -1.1651e-02,
2782
+ 1.5700e-01,
2783
+ 1.3055e-01,
2784
+ 7.7680e-02,
2785
+ -1.3690e-01,
2786
+ 7.0143e-02,
2787
+ 5.4565e-03,
2788
+ -1.0524e-01,
2789
+ 1.1096e-01,
2790
+ -4.6091e-02,
2791
+ 6.8167e-02,
2792
+ -5.6453e-03,
2793
+ 7.2094e-02,
2794
+ -1.8229e-02,
2795
+ -2.5539e-02,
2796
+ 3.9328e-02,
2797
+ 4.3699e-02,
2798
+ 4.4686e-02,
2799
+ -1.2072e-02,
2800
+ -1.6738e-01,
2801
+ 1.6301e-01,
2802
+ 1.0474e-01,
2803
+ 1.4918e-02,
2804
+ 5.4164e-02,
2805
+ -7.2489e-02,
2806
+ -1.7993e-01,
2807
+ 7.7292e-02,
2808
+ -2.8187e-03,
2809
+ -8.9506e-02,
2810
+ 5.6049e-02,
2811
+ -3.1474e-02,
2812
+ 4.7455e-03,
2813
+ -1.5338e-01,
2814
+ 2.2827e-02,
2815
+ 6.7546e-02,
2816
+ -1.1578e-01,
2817
+ 1.2026e-01,
2818
+ 8.2049e-02,
2819
+ -1.1262e-02,
2820
+ 7.4682e-02,
2821
+ 7.3023e-02,
2822
+ -8.6490e-02,
2823
+ 3.1955e-02,
2824
+ 6.6658e-02,
2825
+ -4.6417e-02,
2826
+ 1.0408e-01,
2827
+ -1.1210e-01,
2828
+ -3.3793e-02,
2829
+ -1.6235e-01,
2830
+ 7.3353e-02,
2831
+ 4.5024e-02,
2832
+ -7.6526e-02,
2833
+ -3.0443e-02,
2834
+ 1.2222e-01,
2835
+ 1.1513e-02,
2836
+ -8.5835e-02,
2837
+ 5.6084e-02,
2838
+ -6.5380e-02,
2839
+ 1.3535e-01,
2840
+ 7.2861e-02,
2841
+ -4.8121e-02,
2842
+ 6.6781e-02,
2843
+ 2.5041e-01,
2844
+ 9.3076e-02,
2845
+ -2.0334e-02,
2846
+ -1.0416e-01,
2847
+ -3.9805e-02,
2848
+ -1.0051e-01,
2849
+ -9.2133e-02,
2850
+ 6.3271e-02,
2851
+ 3.4768e-02,
2852
+ 1.1286e-01,
2853
+ 1.3493e-02,
2854
+ 3.0643e-02,
2855
+ -5.5867e-02,
2856
+ 2.4630e-02,
2857
+ 7.8038e-02,
2858
+ -7.5769e-02,
2859
+ 9.0636e-02,
2860
+ 1.8840e-02,
2861
+ 1.3688e-02,
2862
+ 1.2780e-01,
2863
+ -4.3306e-02,
2864
+ 7.6513e-02,
2865
+ 5.6990e-02,
2866
+ 8.7927e-02,
2867
+ 1.6438e-03,
2868
+ -1.0833e-02,
2869
+ 1.3722e-01,
2870
+ -9.4365e-02,
2871
+ -7.4534e-02,
2872
+ -6.2697e-02,
2873
+ 1.6868e-01,
2874
+ 1.6850e-01,
2875
+ 4.3229e-02,
2876
+ 7.4762e-02,
2877
+ 1.1225e-01,
2878
+ -1.9559e-01,
2879
+ 1.1981e-01,
2880
+ 5.0025e-02,
2881
+ ],
2882
+ [
2883
+ 1.0233e-01,
2884
+ 6.7397e-02,
2885
+ 1.0132e-01,
2886
+ 1.1631e-01,
2887
+ 4.3957e-02,
2888
+ 5.7017e-02,
2889
+ -4.0769e-02,
2890
+ -1.2329e-01,
2891
+ -6.1235e-03,
2892
+ 1.3821e-01,
2893
+ 1.3135e-01,
2894
+ -7.2640e-02,
2895
+ 6.7380e-02,
2896
+ 4.7518e-02,
2897
+ -1.0261e-01,
2898
+ -8.4497e-02,
2899
+ 1.1074e-01,
2900
+ 7.9965e-02,
2901
+ 4.3208e-03,
2902
+ -7.9881e-03,
2903
+ 3.0558e-02,
2904
+ 3.8130e-02,
2905
+ -7.0863e-02,
2906
+ 2.6484e-02,
2907
+ -8.1795e-02,
2908
+ 3.9067e-02,
2909
+ -1.5670e-01,
2910
+ 1.2963e-01,
2911
+ 4.3868e-03,
2912
+ 1.3081e-01,
2913
+ 1.7308e-01,
2914
+ 5.3891e-02,
2915
+ -1.2683e-01,
2916
+ 6.0787e-02,
2917
+ -7.8336e-02,
2918
+ -1.0347e-01,
2919
+ 5.8472e-02,
2920
+ -2.7212e-02,
2921
+ 7.1385e-02,
2922
+ -6.8297e-03,
2923
+ 8.7485e-02,
2924
+ -7.1364e-02,
2925
+ -1.9182e-02,
2926
+ 8.7217e-02,
2927
+ 8.2944e-02,
2928
+ 3.4400e-02,
2929
+ -8.5778e-03,
2930
+ -1.1191e-01,
2931
+ 1.4871e-01,
2932
+ 1.0602e-01,
2933
+ 5.5060e-02,
2934
+ 4.0554e-02,
2935
+ -1.0835e-01,
2936
+ -1.4749e-01,
2937
+ 1.1336e-01,
2938
+ 8.1897e-03,
2939
+ -5.1693e-02,
2940
+ 4.9600e-02,
2941
+ -3.6377e-02,
2942
+ -1.6864e-03,
2943
+ -1.4149e-01,
2944
+ -3.8906e-02,
2945
+ 5.3481e-02,
2946
+ -1.1160e-01,
2947
+ 1.3861e-01,
2948
+ 6.3040e-02,
2949
+ -2.2296e-02,
2950
+ 8.1680e-02,
2951
+ 7.9567e-02,
2952
+ -6.1083e-02,
2953
+ 6.7573e-02,
2954
+ 9.8196e-02,
2955
+ -7.0956e-02,
2956
+ 8.7717e-02,
2957
+ -1.3439e-01,
2958
+ -4.4087e-02,
2959
+ -1.7677e-01,
2960
+ 5.1971e-02,
2961
+ 1.1013e-01,
2962
+ -1.2838e-01,
2963
+ -1.8450e-02,
2964
+ 9.7373e-02,
2965
+ 1.5415e-02,
2966
+ -6.8535e-02,
2967
+ 5.8374e-02,
2968
+ -1.0450e-01,
2969
+ 1.3363e-01,
2970
+ 1.2089e-01,
2971
+ -4.1817e-02,
2972
+ 3.1015e-02,
2973
+ 2.7185e-01,
2974
+ 6.9890e-02,
2975
+ -3.8259e-03,
2976
+ -7.7094e-02,
2977
+ 2.4651e-02,
2978
+ -1.0086e-01,
2979
+ -8.4380e-02,
2980
+ 4.9062e-02,
2981
+ 4.7470e-02,
2982
+ 1.0739e-01,
2983
+ -2.4693e-03,
2984
+ -5.9990e-02,
2985
+ -6.2990e-02,
2986
+ 1.2398e-03,
2987
+ 4.8100e-03,
2988
+ -6.0338e-02,
2989
+ 6.4579e-02,
2990
+ 4.2505e-04,
2991
+ -2.9926e-02,
2992
+ 1.3627e-01,
2993
+ -3.0724e-02,
2994
+ 4.2972e-02,
2995
+ 4.6971e-02,
2996
+ 1.2441e-01,
2997
+ -2.4336e-02,
2998
+ 6.9954e-02,
2999
+ 1.0403e-01,
3000
+ -3.2450e-02,
3001
+ -4.3154e-02,
3002
+ -4.9959e-02,
3003
+ 1.5666e-01,
3004
+ 1.3688e-01,
3005
+ 5.3450e-02,
3006
+ 7.9968e-02,
3007
+ 1.3858e-01,
3008
+ -1.6817e-01,
3009
+ 1.2637e-01,
3010
+ 7.3937e-02,
3011
+ ],
3012
+ ]
3013
+ )
src/backend/vespa_app.py ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from typing import Any, Dict, Tuple
4
+ import asyncio
5
+ import numpy as np
6
+ import torch
7
+ from dotenv import load_dotenv
8
+ from vespa.application import Vespa
9
+ from vespa.io import VespaQueryResponse
10
+ from .colpali import SimMapGenerator
11
+ import backend.stopwords
12
+ import logging
13
+
14
+
15
+ class VespaQueryClient:
16
+ MAX_QUERY_TERMS = 64
17
+ VESPA_SCHEMA_NAME = "pdf_page"
18
+ SELECT_FIELDS = "id,title,url,blur_image,page_number,snippet,text"
19
+
20
+ def __init__(self, logger: logging.Logger):
21
+ """
22
+ Initialize the VespaQueryClient by loading environment variables and establishing a connection to the Vespa application.
23
+ """
24
+ load_dotenv()
25
+ self.logger = logger
26
+
27
+ if os.environ.get("USE_MTLS") == "true":
28
+ self.logger.info("Connected using mTLS")
29
+ mtls_key = os.environ.get("VESPA_CLOUD_MTLS_KEY")
30
+ mtls_cert = os.environ.get("VESPA_CLOUD_MTLS_CERT")
31
+
32
+ self.vespa_app_url = os.environ.get("VESPA_APP_MTLS_URL")
33
+ if not self.vespa_app_url:
34
+ raise ValueError(
35
+ "Please set the VESPA_APP_MTLS_URL environment variable"
36
+ )
37
+
38
+ if not mtls_cert or not mtls_key:
39
+ raise ValueError(
40
+ "USE_MTLS was true, but VESPA_CLOUD_MTLS_KEY and VESPA_CLOUD_MTLS_CERT were not set"
41
+ )
42
+
43
+ # write the key and cert to a file
44
+ mtls_key_path = "/tmp/vespa-data-plane-private-key.pem"
45
+ with open(mtls_key_path, "w") as f:
46
+ f.write(mtls_key)
47
+
48
+ mtls_cert_path = "/tmp/vespa-data-plane-public-cert.pem"
49
+ with open(mtls_cert_path, "w") as f:
50
+ f.write(mtls_cert)
51
+
52
+ # Instantiate Vespa connection
53
+ self.app = Vespa(
54
+ url=self.vespa_app_url, key=mtls_key_path, cert=mtls_cert_path
55
+ )
56
+ else:
57
+ self.logger.info("Connected using token")
58
+ self.vespa_app_url = os.environ.get("VESPA_APP_TOKEN_URL")
59
+ if not self.vespa_app_url:
60
+ raise ValueError(
61
+ "Please set the VESPA_APP_TOKEN_URL environment variable"
62
+ )
63
+
64
+ self.vespa_cloud_secret_token = os.environ.get("VESPA_CLOUD_SECRET_TOKEN")
65
+
66
+ if not self.vespa_cloud_secret_token:
67
+ raise ValueError(
68
+ "Please set the VESPA_CLOUD_SECRET_TOKEN environment variable"
69
+ )
70
+
71
+ # Instantiate Vespa connection
72
+ self.app = Vespa(
73
+ url=self.vespa_app_url,
74
+ vespa_cloud_secret_token=self.vespa_cloud_secret_token,
75
+ )
76
+
77
+ self.app.wait_for_application_up()
78
+ self.logger.info(f"Connected to Vespa at {self.vespa_app_url}")
79
+
80
+ def get_fields(self, sim_map: bool = False):
81
+ if not sim_map:
82
+ return self.SELECT_FIELDS
83
+ else:
84
+ return "summaryfeatures"
85
+
86
+ def format_query_results(
87
+ self, query: str, response: VespaQueryResponse, hits: int = 5
88
+ ) -> dict:
89
+ """
90
+ Format the Vespa query results.
91
+
92
+ Args:
93
+ query (str): The query text.
94
+ response (VespaQueryResponse): The response from Vespa.
95
+ hits (int, optional): Number of hits to display. Defaults to 5.
96
+
97
+ Returns:
98
+ dict: The JSON content of the response.
99
+ """
100
+ query_time = response.json.get("timing", {}).get("searchtime", -1)
101
+ query_time = round(query_time, 2)
102
+ count = response.json.get("root", {}).get("fields", {}).get("totalCount", 0)
103
+ result_text = f"Query text: '{query}', query time {query_time}s, count={count}, top results:\n"
104
+ self.logger.debug(result_text)
105
+ return response.json
106
+
107
+ async def query_vespa_bm25(
108
+ self,
109
+ query: str,
110
+ q_emb: torch.Tensor,
111
+ hits: int = 3,
112
+ timeout: str = "10s",
113
+ sim_map: bool = False,
114
+ **kwargs,
115
+ ) -> dict:
116
+ """
117
+ Query Vespa using the BM25 ranking profile.
118
+ This corresponds to the "BM25" radio button in the UI.
119
+
120
+ Args:
121
+ query (str): The query text.
122
+ q_emb (torch.Tensor): Query embeddings.
123
+ hits (int, optional): Number of hits to retrieve. Defaults to 3.
124
+ timeout (str, optional): Query timeout. Defaults to "10s".
125
+
126
+ Returns:
127
+ dict: The formatted query results.
128
+ """
129
+ async with self.app.asyncio(connections=1) as session:
130
+ query_embedding = self.format_q_embs(q_emb)
131
+
132
+ start = time.perf_counter()
133
+ response: VespaQueryResponse = await session.query(
134
+ body={
135
+ "yql": (
136
+ f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where userQuery();"
137
+ ),
138
+ "ranking": self.get_rank_profile("bm25", sim_map),
139
+ "query": query,
140
+ "timeout": timeout,
141
+ "hits": hits,
142
+ "input.query(qt)": query_embedding,
143
+ "presentation.timing": True,
144
+ **kwargs,
145
+ },
146
+ )
147
+ assert response.is_successful(), response.json
148
+ stop = time.perf_counter()
149
+ self.logger.debug(
150
+ f"Query time + data transfer took: {stop - start} s, Vespa reported searchtime was "
151
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
152
+ )
153
+ return self.format_query_results(query, response)
154
+
155
+ def float_to_binary_embedding(self, float_query_embedding: dict) -> dict:
156
+ """
157
+ Convert float query embeddings to binary embeddings.
158
+
159
+ Args:
160
+ float_query_embedding (dict): Dictionary of float embeddings.
161
+
162
+ Returns:
163
+ dict: Dictionary of binary embeddings.
164
+ """
165
+ binary_query_embeddings = {}
166
+ for key, vector in float_query_embedding.items():
167
+ binary_vector = (
168
+ np.packbits(np.where(np.array(vector) > 0, 1, 0))
169
+ .astype(np.int8)
170
+ .tolist()
171
+ )
172
+ binary_query_embeddings[key] = binary_vector
173
+ if len(binary_query_embeddings) >= self.MAX_QUERY_TERMS:
174
+ self.logger.warning(
175
+ f"Warning: Query has more than {self.MAX_QUERY_TERMS} terms. Truncating."
176
+ )
177
+ break
178
+ return binary_query_embeddings
179
+
180
+ def create_nn_query_strings(
181
+ self, binary_query_embeddings: dict, target_hits_per_query_tensor: int = 20
182
+ ) -> Tuple[str, dict]:
183
+ """
184
+ Create nearest neighbor query strings for Vespa.
185
+
186
+ Args:
187
+ binary_query_embeddings (dict): Binary query embeddings.
188
+ target_hits_per_query_tensor (int, optional): Target hits per query tensor. Defaults to 20.
189
+
190
+ Returns:
191
+ Tuple[str, dict]: Nearest neighbor query string and query tensor dictionary.
192
+ """
193
+ nn_query_dict = {}
194
+ for i in range(len(binary_query_embeddings)):
195
+ nn_query_dict[f"input.query(rq{i})"] = binary_query_embeddings[i]
196
+ nn = " OR ".join(
197
+ [
198
+ f"({{targetHits:{target_hits_per_query_tensor}}}nearestNeighbor(embedding,rq{i}))"
199
+ for i in range(len(binary_query_embeddings))
200
+ ]
201
+ )
202
+ return nn, nn_query_dict
203
+
204
+ def format_q_embs(self, q_embs: torch.Tensor) -> dict:
205
+ """
206
+ Convert query embeddings to a dictionary of lists.
207
+
208
+ Args:
209
+ q_embs (torch.Tensor): Query embeddings tensor.
210
+
211
+ Returns:
212
+ dict: Dictionary where each key is an index and value is the embedding list.
213
+ """
214
+ return {idx: emb.tolist() for idx, emb in enumerate(q_embs)}
215
+
216
+ async def get_result_from_query(
217
+ self,
218
+ query: str,
219
+ q_embs: torch.Tensor,
220
+ ranking: str,
221
+ idx_to_token: dict,
222
+ ) -> Dict[str, Any]:
223
+ """
224
+ Get query results from Vespa based on the ranking method.
225
+
226
+ Args:
227
+ query (str): The query text.
228
+ q_embs (torch.Tensor): Query embeddings.
229
+ ranking (str): The ranking method to use.
230
+ idx_to_token (dict): Index to token mapping.
231
+
232
+ Returns:
233
+ Dict[str, Any]: The query results.
234
+ """
235
+
236
+ # Remove stopwords from the query to avoid visual emphasis on irrelevant words (e.g., "the", "and", "of")
237
+ query = backend.stopwords.filter(query)
238
+
239
+ rank_method = ranking.split("_")[0]
240
+ sim_map: bool = len(ranking.split("_")) > 1 and ranking.split("_")[1] == "sim"
241
+ if rank_method == "colpali": # ColPali
242
+ result = await self.query_vespa_colpali(
243
+ query=query, ranking=rank_method, q_emb=q_embs, sim_map=sim_map
244
+ )
245
+ elif rank_method == "hybrid": # Hybrid ColPali+BM25
246
+ result = await self.query_vespa_colpali(
247
+ query=query, ranking=rank_method, q_emb=q_embs, sim_map=sim_map
248
+ )
249
+ elif rank_method == "bm25":
250
+ result = await self.query_vespa_bm25(query, q_embs, sim_map=sim_map)
251
+ else:
252
+ raise ValueError(f"Unsupported ranking: {rank_method}")
253
+ if "root" not in result or "children" not in result["root"]:
254
+ result["root"] = {"children": []}
255
+ return result
256
+ for single_result in result["root"]["children"]:
257
+ self.logger.debug(single_result["fields"].keys())
258
+ return result
259
+
260
+ def get_sim_maps_from_query(
261
+ self, query: str, q_embs: torch.Tensor, ranking: str, idx_to_token: dict
262
+ ):
263
+ """
264
+ Get similarity maps from Vespa based on the ranking method.
265
+
266
+ Args:
267
+ query (str): The query text.
268
+ q_embs (torch.Tensor): Query embeddings.
269
+ ranking (str): The ranking method to use.
270
+ idx_to_token (dict): Index to token mapping.
271
+
272
+ Returns:
273
+ Dict[str, Any]: The query results.
274
+ """
275
+ # Get the result by calling asyncio.run
276
+ result = asyncio.run(
277
+ self.get_result_from_query(query, q_embs, ranking, idx_to_token)
278
+ )
279
+ vespa_sim_maps = []
280
+ for single_result in result["root"]["children"]:
281
+ vespa_sim_map = single_result["fields"].get("summaryfeatures", None)
282
+ if vespa_sim_map is not None:
283
+ vespa_sim_maps.append(vespa_sim_map)
284
+ else:
285
+ raise ValueError("No sim_map found in Vespa response")
286
+ return vespa_sim_maps
287
+
288
+ async def get_full_image_from_vespa(self, doc_id: str) -> str:
289
+ """
290
+ Retrieve the full image from Vespa for a given document ID.
291
+
292
+ Args:
293
+ doc_id (str): The document ID.
294
+
295
+ Returns:
296
+ str: The full image data.
297
+ """
298
+ async with self.app.asyncio(connections=1) as session:
299
+ start = time.perf_counter()
300
+ response: VespaQueryResponse = await session.query(
301
+ body={
302
+ "yql": f'select full_image from {self.VESPA_SCHEMA_NAME} where id contains "{doc_id}"',
303
+ "ranking": "unranked",
304
+ "presentation.timing": True,
305
+ "ranking.matching.numThreadsPerSearch": 1,
306
+ },
307
+ )
308
+ assert response.is_successful(), response.json
309
+ stop = time.perf_counter()
310
+ self.logger.debug(
311
+ f"Getting image from Vespa took: {stop - start} s, Vespa reported searchtime was "
312
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
313
+ )
314
+ return response.json["root"]["children"][0]["fields"]["full_image"]
315
+
316
+ def get_results_children(self, result: VespaQueryResponse) -> list:
317
+ return result["root"]["children"]
318
+
319
+ def results_to_search_results(
320
+ self, result: VespaQueryResponse, idx_to_token: dict
321
+ ) -> list:
322
+ # Initialize sim_map_ fields in the result
323
+ fields_to_add = [
324
+ f"sim_map_{token}_{idx}"
325
+ for idx, token in idx_to_token.items()
326
+ if not SimMapGenerator.should_filter_token(token)
327
+ ]
328
+ for child in result["root"]["children"]:
329
+ for sim_map_key in fields_to_add:
330
+ child["fields"][sim_map_key] = None
331
+ return self.get_results_children(result)
332
+
333
+ async def get_suggestions(self, query: str) -> list:
334
+ async with self.app.asyncio(connections=1) as session:
335
+ start = time.perf_counter()
336
+ yql = f'select questions from {self.VESPA_SCHEMA_NAME} where questions matches (".*{query}.*")'
337
+ response: VespaQueryResponse = await session.query(
338
+ body={
339
+ "yql": yql,
340
+ "query": query,
341
+ "ranking": "unranked",
342
+ "presentation.timing": True,
343
+ "presentation.summary": "suggestions",
344
+ "ranking.matching.numThreadsPerSearch": 1,
345
+ },
346
+ )
347
+ assert response.is_successful(), response.json
348
+ stop = time.perf_counter()
349
+ self.logger.debug(
350
+ f"Getting suggestions from Vespa took: {stop - start} s, Vespa reported searchtime was "
351
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
352
+ )
353
+ search_results = (
354
+ response.json["root"]["children"]
355
+ if "root" in response.json and "children" in response.json["root"]
356
+ else []
357
+ )
358
+ questions = [
359
+ result["fields"]["questions"]
360
+ for result in search_results
361
+ if "questions" in result["fields"]
362
+ ]
363
+
364
+ unique_questions = set([item for sublist in questions for item in sublist])
365
+
366
+ # remove an artifact from our data generation
367
+ if "string" in unique_questions:
368
+ unique_questions.remove("string")
369
+
370
+ return list(unique_questions)
371
+
372
+ def get_rank_profile(self, ranking: str, sim_map: bool) -> str:
373
+ if sim_map:
374
+ return f"{ranking}_sim"
375
+ else:
376
+ return ranking
377
+
378
+ async def query_vespa_colpali(
379
+ self,
380
+ query: str,
381
+ ranking: str,
382
+ q_emb: torch.Tensor,
383
+ target_hits_per_query_tensor: int = 100,
384
+ hnsw_explore_additional_hits: int = 300,
385
+ hits: int = 3,
386
+ timeout: str = "10s",
387
+ sim_map: bool = False,
388
+ **kwargs,
389
+ ) -> dict:
390
+ """
391
+ Query Vespa using nearest neighbor search with mixed tensors for MaxSim calculations.
392
+ This corresponds to the "ColPali" radio button in the UI.
393
+
394
+ Args:
395
+ query (str): The query text.
396
+ q_emb (torch.Tensor): Query embeddings.
397
+ target_hits_per_query_tensor (int, optional): Target hits per query tensor. Defaults to 20.
398
+ hits (int, optional): Number of hits to retrieve. Defaults to 3.
399
+ timeout (str, optional): Query timeout. Defaults to "10s".
400
+
401
+ Returns:
402
+ dict: The formatted query results.
403
+ """
404
+ async with self.app.asyncio(connections=1) as session:
405
+ float_query_embedding = self.format_q_embs(q_emb)
406
+ binary_query_embeddings = self.float_to_binary_embedding(
407
+ float_query_embedding
408
+ )
409
+
410
+ # Mixed tensors for MaxSim calculations
411
+ query_tensors = {
412
+ "input.query(qtb)": binary_query_embeddings,
413
+ "input.query(qt)": float_query_embedding,
414
+ }
415
+ nn_string, nn_query_dict = self.create_nn_query_strings(
416
+ binary_query_embeddings, target_hits_per_query_tensor
417
+ )
418
+ query_tensors.update(nn_query_dict)
419
+ response: VespaQueryResponse = await session.query(
420
+ body={
421
+ **query_tensors,
422
+ "presentation.timing": True,
423
+ "yql": (
424
+ f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where {nn_string} or userQuery()"
425
+ ),
426
+ "ranking.profile": self.get_rank_profile(
427
+ ranking=ranking, sim_map=sim_map
428
+ ),
429
+ "timeout": timeout,
430
+ "hits": hits,
431
+ "query": query,
432
+ "hnsw.exploreAdditionalHits": hnsw_explore_additional_hits,
433
+ "ranking.rerankCount": 100,
434
+ **kwargs,
435
+ },
436
+ )
437
+ assert response.is_successful(), response.json
438
+ return self.format_query_results(query, response)
439
+
440
+ async def keepalive(self) -> bool:
441
+ """
442
+ Query Vespa to keep the connection alive.
443
+
444
+ Returns:
445
+ bool: True if the connection is alive.
446
+ """
447
+ async with self.app.asyncio(connections=1) as session:
448
+ response: VespaQueryResponse = await session.query(
449
+ body={
450
+ "yql": f"select title from {self.VESPA_SCHEMA_NAME} where true limit 1;",
451
+ "ranking": "unranked",
452
+ "query": "keepalive",
453
+ "timeout": "3s",
454
+ "hits": 1,
455
+ },
456
+ )
457
+ assert response.is_successful(), response.json
458
+ return True
src/frontend/__init__.py ADDED
File without changes
src/frontend/app.py ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from urllib.parse import quote_plus
3
+
4
+ from fasthtml.components import (
5
+ H1,
6
+ H2,
7
+ H3,
8
+ Br,
9
+ Div,
10
+ Form,
11
+ Img,
12
+ NotStr,
13
+ P,
14
+ Hr,
15
+ Span,
16
+ A,
17
+ Script,
18
+ Button,
19
+ Label,
20
+ RadioGroup,
21
+ RadioGroupItem,
22
+ Separator,
23
+ Ul,
24
+ Li,
25
+ Strong,
26
+ Iframe,
27
+ )
28
+ from fasthtml.xtend import A, Script
29
+ from lucide_fasthtml import Lucide
30
+ from shad4fast import Badge, Button, Input, Label, RadioGroup, RadioGroupItem, Separator
31
+
32
+ # JavaScript to check the input value and enable/disable the search button and radio buttons
33
+ check_input_script = Script(
34
+ """
35
+ window.onload = function() {
36
+ const input = document.getElementById('search-input');
37
+ const button = document.querySelector('[data-button="search-button"]');
38
+ const radioGroupItems = document.querySelectorAll('button[data-ref="radio-item"]'); // Get all radio buttons
39
+
40
+ function checkInputValue() {
41
+ const isInputEmpty = input.value.trim() === "";
42
+ button.disabled = isInputEmpty; // Disable the submit button
43
+ radioGroupItems.forEach(item => {
44
+ item.disabled = isInputEmpty; // Disable/enable the radio buttons
45
+ });
46
+ }
47
+
48
+ input.addEventListener('input', checkInputValue); // Listen for input changes
49
+ checkInputValue(); // Initial check when the page loads
50
+ };
51
+ """
52
+ )
53
+
54
+ # JavaScript to handle the image swapping, reset button, and active class toggling
55
+ image_swapping = Script(
56
+ """
57
+ document.addEventListener('click', function (e) {
58
+ if (e.target.classList.contains('sim-map-button') || e.target.classList.contains('reset-button')) {
59
+ const imgContainer = e.target.closest('.relative');
60
+ const overlayContainer = imgContainer.querySelector('.overlay-container');
61
+ const newSrc = e.target.getAttribute('data-image-src');
62
+
63
+ // If it's a reset button, remove the overlay image
64
+ if (e.target.classList.contains('reset-button')) {
65
+ overlayContainer.innerHTML = ''; // Clear the overlay container, showing only the full image
66
+ } else {
67
+ // Create a new overlay image
68
+ const img = document.createElement('img');
69
+ img.src = newSrc;
70
+ img.classList.add('overlay-image', 'absolute', 'top-0', 'left-0', 'w-full', 'h-full');
71
+ overlayContainer.innerHTML = ''; // Clear any previous overlay
72
+ overlayContainer.appendChild(img); // Add the new overlay image
73
+ }
74
+
75
+ // Toggle active class on buttons
76
+ const activeButton = document.querySelector('.sim-map-button.active');
77
+ if (activeButton) {
78
+ activeButton.classList.remove('active');
79
+ }
80
+ if (e.target.classList.contains('sim-map-button')) {
81
+ e.target.classList.add('active');
82
+ }
83
+ }
84
+ });
85
+ """
86
+ )
87
+
88
+ toggle_text_content = Script(
89
+ """
90
+ function toggleTextContent(idx) {
91
+ const textColumn = document.getElementById(`text-column-${idx}`);
92
+ const imageTextColumns = document.getElementById(`image-text-columns-${idx}`);
93
+ const toggleButton = document.getElementById(`toggle-button-${idx}`);
94
+
95
+ if (textColumn.classList.contains('md-grid-text-column')) {
96
+ // Hide the text column
97
+ textColumn.classList.remove('md-grid-text-column');
98
+ imageTextColumns.classList.remove('grid-image-text-columns');
99
+ toggleButton.innerText = `Show Text`;
100
+ } else {
101
+ // Show the text column
102
+ textColumn.classList.add('md-grid-text-column');
103
+ imageTextColumns.classList.add('grid-image-text-columns');
104
+ toggleButton.innerText = `Hide Text`;
105
+ }
106
+ }
107
+ """
108
+ )
109
+
110
+ autocomplete_script = Script(
111
+ """
112
+ document.addEventListener('DOMContentLoaded', function() {
113
+ const input = document.querySelector('#search-input');
114
+ const awesomplete = new Awesomplete(input, { minChars: 1, maxItems: 5 });
115
+
116
+ input.addEventListener('input', function() {
117
+ if (this.value.length >= 1) {
118
+ // Use template literals to insert the input value dynamically in the query parameter
119
+ fetch(`/suggestions?query=${encodeURIComponent(this.value)}`)
120
+ .then(response => response.json())
121
+ .then(data => {
122
+ // Update the Awesomplete list dynamically with fetched suggestions
123
+ awesomplete.list = data.suggestions;
124
+ })
125
+ .catch(err => console.error('Error fetching suggestions:', err));
126
+ }
127
+ });
128
+ });
129
+ """
130
+ )
131
+
132
+ dynamic_elements_scrollbars = Script(
133
+ """
134
+ (function () {
135
+ const { applyOverlayScrollbars, getScrollbarTheme } = OverlayScrollbarsManager;
136
+
137
+ function applyScrollbarsToDynamicElements() {
138
+ const scrollbarTheme = getScrollbarTheme();
139
+
140
+ // Apply scrollbars to dynamically loaded result-text-full and result-text-snippet elements
141
+ const resultTextFullElements = document.querySelectorAll('[id^="result-text-full"]');
142
+ const resultTextSnippetElements = document.querySelectorAll('[id^="result-text-snippet"]');
143
+
144
+ resultTextFullElements.forEach(element => {
145
+ applyOverlayScrollbars(element, scrollbarTheme);
146
+ });
147
+
148
+ resultTextSnippetElements.forEach(element => {
149
+ applyOverlayScrollbars(element, scrollbarTheme);
150
+ });
151
+ }
152
+
153
+ // Apply scrollbars after dynamic content is loaded (e.g., after search results)
154
+ applyScrollbarsToDynamicElements();
155
+
156
+ // Observe changes in the 'dark' class to adjust the theme dynamically if needed
157
+ const observer = new MutationObserver(applyScrollbarsToDynamicElements);
158
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
159
+ })();
160
+ """
161
+ )
162
+
163
+ submit_form_on_radio_change = Script(
164
+ """
165
+ document.addEventListener('click', function (e) {
166
+ // if target has data-ref="radio-item" and type is button
167
+ if (e.target.getAttribute('data-ref') === 'radio-item' && e.target.type === 'button') {
168
+ console.log('Radio button clicked');
169
+ const form = e.target.closest('form');
170
+ form.submit();
171
+ }
172
+ });
173
+ """
174
+ )
175
+
176
+
177
+ def ShareButtons():
178
+ title = "Visual RAG over PDFs with Vespa and ColPali"
179
+ url = "https://huggingface.co/spaces/vespa-engine/colpali-vespa-visual-retrieval"
180
+ return Div(
181
+ A(
182
+ Img(src="/static/img/linkedin.svg", aria_hidden="true", cls="h-[21px]"),
183
+ "Share on LinkedIn",
184
+ href=f"https://www.linkedin.com/sharing/share-offsite/?url={quote_plus(url)}",
185
+ rel="noopener noreferrer",
186
+ target="_blank",
187
+ cls="bg-[#0A66C2] text-white inline-flex items-center gap-x-1.5 px-2.5 py-1.5 border rounded-md text-sm font-semibold",
188
+ ),
189
+ A(
190
+ Img(src="/static/img/x.svg", aria_hidden="true", cls="h-[21px]"),
191
+ "Share on X",
192
+ href=f"https://twitter.com/intent/tweet?text={quote_plus(title)}&url={quote_plus(url)}",
193
+ rel="noopener noreferrer",
194
+ target="_blank",
195
+ cls="bg-black text-white inline-flex items-center gap-x-1.5 px-2.5 py-1.5 border rounded-md text-sm font-semibold",
196
+ ),
197
+ cls="flex items-center justify-center space-x-8 mt-5",
198
+ )
199
+
200
+
201
+ def SearchBox(with_border=False, query_value="", ranking_value="hybrid"):
202
+ grid_cls = "grid gap-2 items-center p-3 bg-muted w-full"
203
+
204
+ if with_border:
205
+ grid_cls = "grid gap-2 p-3 rounded-md border border-input bg-muted w-full ring-offset-background focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:border-input"
206
+
207
+ return Form(
208
+ Div(
209
+ Lucide(
210
+ icon="search", cls="absolute left-2 top-2 text-muted-foreground z-10"
211
+ ),
212
+ Input(
213
+ placeholder="Enter your search query...",
214
+ name="query",
215
+ value=query_value,
216
+ id="search-input",
217
+ cls="text-base pl-10 border-transparent ring-offset-transparent ring-0 focus-visible:ring-transparent bg-white dark:bg-background awesomplete",
218
+ data_list="#suggestions",
219
+ style="font-size: 1rem",
220
+ autofocus=True,
221
+ ),
222
+ cls="relative",
223
+ ),
224
+ Div(
225
+ Div(
226
+ Span("Ranking by:", cls="text-muted-foreground text-xs font-semibold"),
227
+ RadioGroup(
228
+ Div(
229
+ RadioGroupItem(value="colpali", id="colpali"),
230
+ Label("ColPali", htmlFor="ColPali"),
231
+ cls="flex items-center space-x-2",
232
+ ),
233
+ Div(
234
+ RadioGroupItem(value="bm25", id="bm25"),
235
+ Label("BM25", htmlFor="BM25"),
236
+ cls="flex items-center space-x-2",
237
+ ),
238
+ Div(
239
+ RadioGroupItem(value="hybrid", id="hybrid"),
240
+ Label("Hybrid ColPali + BM25", htmlFor="Hybrid ColPali + BM25"),
241
+ cls="flex items-center space-x-2",
242
+ ),
243
+ name="ranking",
244
+ default_value=ranking_value,
245
+ cls="grid-flow-col gap-x-5 text-muted-foreground",
246
+ # Submit form when radio button is clicked
247
+ ),
248
+ cls="grid grid-flow-col items-center gap-x-3 border border-input px-3 rounded-sm",
249
+ ),
250
+ Button(
251
+ Lucide(icon="arrow-right", size="21"),
252
+ size="sm",
253
+ type="submit",
254
+ data_button="search-button",
255
+ disabled=True,
256
+ ),
257
+ cls="flex justify-between",
258
+ ),
259
+ check_input_script,
260
+ autocomplete_script,
261
+ submit_form_on_radio_change,
262
+ action=f"/search?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
263
+ method="GET",
264
+ hx_get="/fetch_results", # As the component is a form, input components query and ranking are sent as query parameters automatically, see https://htmx.org/docs/#parameters
265
+ hx_trigger="load",
266
+ hx_target="#search-results",
267
+ hx_swap="outerHTML",
268
+ hx_indicator="#loading-indicator",
269
+ cls=grid_cls,
270
+ )
271
+
272
+
273
+ def SampleQueries():
274
+ sample_queries = [
275
+ "What percentage of the funds unlisted real estate investments were in Switzerland 2023?",
276
+ "Gender balance at level 4 or above in NY office 2023?",
277
+ "Number of graduate applications trend 2021-2023",
278
+ "Total amount of fixed salaries paid in 2023?",
279
+ "Proportion of female new hires 2021-2023?",
280
+ "child jumping over puddle",
281
+ "hula hoop kid",
282
+ ]
283
+
284
+ query_badges = []
285
+ for query in sample_queries:
286
+ query_badges.append(
287
+ A(
288
+ Badge(
289
+ Div(
290
+ Lucide(
291
+ icon="text-search", size="18", cls="text-muted-foreground"
292
+ ),
293
+ Span(query, cls="text-base font-normal"),
294
+ cls="flex gap-2 items-center",
295
+ ),
296
+ variant="outline",
297
+ cls="text-base font-normal text-muted-foreground hover:border-black dark:hover:border-white",
298
+ ),
299
+ href=f"/search?query={quote_plus(query)}",
300
+ cls="no-underline",
301
+ )
302
+ )
303
+
304
+ return Div(*query_badges, cls="grid gap-2 justify-items-center")
305
+
306
+
307
+ def Hero():
308
+ return Div(
309
+ H1(
310
+ "Visual RAG over PDFs",
311
+ cls="text-5xl md:text-6xl font-bold tracking-wide md:tracking-wider bg-clip-text text-transparent bg-gradient-to-r from-black to-slate-700 dark:from-white dark:to-slate-300 animate-fade-in",
312
+ ),
313
+ P(
314
+ "See how Vespa and ColPali can be used for Visual RAG in this demo",
315
+ cls="text-base md:text-2xl text-muted-foreground md:tracking-wide",
316
+ ),
317
+ cls="grid gap-5 text-center",
318
+ )
319
+
320
+
321
+ def Home():
322
+ return Div(
323
+ Div(
324
+ Hero(),
325
+ SearchBox(with_border=True),
326
+ SampleQueries(),
327
+ ShareButtons(),
328
+ cls="grid gap-8 content-start mt-[13vh]",
329
+ ),
330
+ cls="grid w-full h-full max-w-screen-md gap-4 mx-auto",
331
+ )
332
+
333
+
334
+ def LinkResource(text, href):
335
+ return Li(
336
+ A(
337
+ Lucide(icon="external-link", size="18"),
338
+ text,
339
+ href=href,
340
+ target="_blank",
341
+ cls="flex items-center gap-1.5 hover:underline bold text-md",
342
+ ),
343
+ )
344
+
345
+
346
+ def AboutThisDemo():
347
+ resources = [
348
+ {
349
+ "text": "Vespa Blog: How we built this demo",
350
+ "href": "https://blog.vespa.ai/visual-rag-in-practice",
351
+ },
352
+ {
353
+ "text": "Notebook to set up Vespa application and feed dataset",
354
+ "href": "https://pyvespa.readthedocs.io/en/latest/examples/visual_pdf_rag_with_vespa_colpali_cloud.html",
355
+ },
356
+ {
357
+ "text": "Web App (FastHTML) Code",
358
+ "href": "https://github.com/vespa-engine/sample-apps/tree/master/visual-retrieval-colpali",
359
+ },
360
+ {
361
+ "text": "Vespa Blog: Scaling ColPali to Billions",
362
+ "href": "https://blog.vespa.ai/scaling-colpali-to-billions/",
363
+ },
364
+ {
365
+ "text": "Vespa Blog: Retrieval with Vision Language Models",
366
+ "href": "https://blog.vespa.ai/retrieval-with-vision-language-models-colpali/",
367
+ },
368
+ ]
369
+ return Div(
370
+ H1(
371
+ "About This Demo",
372
+ cls="text-3xl md:text-5xl font-bold tracking-wide md:tracking-wider",
373
+ ),
374
+ P(
375
+ "This demo showcases a Visual Retrieval-Augmented Generation (RAG) application over PDFs using ColPali embeddings in Vespa, built entirely in Python, using FastHTML. The code is fully open source.",
376
+ cls="text-base",
377
+ ),
378
+ Img(
379
+ src="/static/img/colpali_child.png",
380
+ alt="Example of token level similarity map",
381
+ cls="w-full",
382
+ ),
383
+ H2("Resources", cls="text-2xl font-semibold"),
384
+ Ul(
385
+ *[
386
+ LinkResource(resource["text"], resource["href"])
387
+ for resource in resources
388
+ ],
389
+ cls="space-y-2 list-disc pl-5",
390
+ ),
391
+ H2("Architecture Overview", cls="text-2xl font-semibold"),
392
+ Img(
393
+ src="/static/img/visual-retrieval-demoapp-arch.png",
394
+ alt="Architecture Overview",
395
+ cls="w-full",
396
+ ),
397
+ Ul(
398
+ Li(
399
+ Strong("Vespa Application: "),
400
+ "Vespa Application that handles indexing, search, ranking and queries, leveraging features like phased ranking and multivector MaxSim calculations.",
401
+ ),
402
+ Li(
403
+ Strong("Frontend: "),
404
+ "Built with FastHTML, offering a professional and responsive user interface without the complexity of separate frontend frameworks.",
405
+ ),
406
+ Li(
407
+ Strong("Backend: "),
408
+ "Also built with FastHTML. Handles query embedding inference using ColPali, serves static files, and is responsible for orchestrating interactions between Vespa and the frontend.",
409
+ ),
410
+ Li(
411
+ Strong("Gemini API: "),
412
+ "VLM for the AI response, providing responses based on the top results from Vespa.",
413
+ cls="list-disc list-inside",
414
+ ),
415
+ H2("User Experience Highlights", cls="text-2xl font-semibold"),
416
+ Ul(
417
+ Li(
418
+ Strong("Fast and Responsive: "),
419
+ "Optimized for quick loading times, with phased content delivery to display essential information immediately while loading detailed data in the background.",
420
+ ),
421
+ Li(
422
+ Strong("Similarity Maps: "),
423
+ "Provides visual highlights of the most relevant parts of a page in response to a query, enhancing interpretability.",
424
+ ),
425
+ Li(
426
+ Strong("Type-Ahead Suggestions: "),
427
+ "Offers query suggestions to assist users in formulating effective searches.",
428
+ ),
429
+ cls="list-disc list-inside",
430
+ ),
431
+ cls="grid gap-5",
432
+ ),
433
+ H2("Dataset", cls="text-2xl font-semibold"),
434
+ P(
435
+ "The dataset used in this demo is retrieved from reports published by the Norwegian Government Pension Fund Global. It contains 6,992 pages from 116 PDF reports (2000–2024). The information is often presented in visual formats, making it an ideal dataset for visual retrieval applications.",
436
+ cls="text-base",
437
+ ),
438
+ Iframe(
439
+ src="https://huggingface.co/datasets/vespa-engine/gpfg-QA/embed/viewer",
440
+ frameborder="0",
441
+ width="100%",
442
+ height="500",
443
+ ),
444
+ Hr(), # To add some margin to bottom. Probably a much better way to do this, but the mb-[16vh] class doesn't seem to be applied
445
+ cls="w-full h-full max-w-screen-md gap-4 mx-auto mt-[8vh] mb-[16vh] grid gap-8 content-start",
446
+ )
447
+
448
+
449
+ def Search(request, search_results=[]):
450
+ query_value = request.query_params.get("query", "").strip()
451
+ ranking_value = request.query_params.get("ranking", "hybrid")
452
+ return Div(
453
+ Div(
454
+ Div(
455
+ SearchBox(query_value=query_value, ranking_value=ranking_value),
456
+ Div(
457
+ LoadingMessage(),
458
+ id="search-results", # This will be replaced by the search results
459
+ ),
460
+ cls="grid",
461
+ ),
462
+ cls="grid",
463
+ ),
464
+ )
465
+
466
+
467
+ def LoadingMessage(display_text="Retrieving search results"):
468
+ return Div(
469
+ Lucide(icon="loader-circle", cls="size-5 mr-1.5 animate-spin"),
470
+ Span(display_text, cls="text-base text-center"),
471
+ cls="p-10 text-muted-foreground flex items-center justify-center",
472
+ id="loading-indicator",
473
+ )
474
+
475
+
476
+ def LoadingSkeleton():
477
+ return Div(
478
+ Div(cls="h-5 bg-muted"),
479
+ Div(cls="h-5 bg-muted"),
480
+ Div(cls="h-5 bg-muted"),
481
+ cls="grid gap-2 animate-pulse",
482
+ )
483
+
484
+
485
+ def SimMapButtonReady(query_id, idx, token, token_idx, img_src):
486
+ return Button(
487
+ token.replace("\u2581", ""),
488
+ size="sm",
489
+ data_image_src=img_src,
490
+ id=f"sim-map-button-{query_id}-{idx}-{token_idx}-{token}",
491
+ cls="sim-map-button pointer-events-auto font-mono text-xs h-5 rounded-none px-2",
492
+ )
493
+
494
+
495
+ def SimMapButtonPoll(query_id, idx, token, token_idx):
496
+ return Button(
497
+ Lucide(icon="loader-circle", size="15", cls="animate-spin"),
498
+ size="sm",
499
+ disabled=True,
500
+ hx_get=f"/get_sim_map?query_id={query_id}&idx={idx}&token={token}&token_idx={token_idx}",
501
+ hx_trigger="every 0.5s",
502
+ hx_swap="outerHTML",
503
+ cls="pointer-events-auto text-xs h-5 rounded-none px-2",
504
+ )
505
+
506
+
507
+ def SearchInfo(search_time, total_count):
508
+ return (
509
+ Div(
510
+ Span(
511
+ "Retrieved ",
512
+ Strong(total_count),
513
+ Span(" results"),
514
+ Span(" in "),
515
+ Strong(f"{search_time:.3f}"), # 3 significant digits
516
+ Span(" seconds."),
517
+ ),
518
+ cls="grid bg-background border-t text-sm text-center p-3",
519
+ ),
520
+ )
521
+
522
+
523
+ def SearchResult(
524
+ results: list,
525
+ query: str,
526
+ query_id: Optional[str] = None,
527
+ search_time: float = 0,
528
+ total_count: int = 0,
529
+ ):
530
+ if not results:
531
+ return Div(
532
+ P(
533
+ "No results found for your query.",
534
+ cls="text-muted-foreground text-base text-center",
535
+ ),
536
+ cls="grid p-10",
537
+ )
538
+
539
+ doc_ids = []
540
+ # Otherwise, display the search results
541
+ result_items = []
542
+ for idx, result in enumerate(results):
543
+ fields = result["fields"] # Extract the 'fields' part of each result
544
+ doc_id = fields["id"]
545
+ doc_ids.append(doc_id)
546
+ blur_image_base64 = f"data:image/jpeg;base64,{fields['blur_image']}"
547
+
548
+ sim_map_fields = {
549
+ key: value
550
+ for key, value in fields.items()
551
+ if key.startswith(
552
+ "sim_map_"
553
+ ) # filtering is done before creating with 'should_filter_token'-function
554
+ }
555
+
556
+ # Generate buttons for the sim_map fields
557
+ sim_map_buttons = []
558
+ for key, value in sim_map_fields.items():
559
+ token = key.split("_")[-2]
560
+ token_idx = int(key.split("_")[-1])
561
+ if value is not None:
562
+ sim_map_base64 = f"data:image/jpeg;base64,{value}"
563
+ sim_map_buttons.append(
564
+ SimMapButtonReady(
565
+ query_id=query_id,
566
+ idx=idx,
567
+ token=token,
568
+ token_idx=token_idx,
569
+ img_src=sim_map_base64,
570
+ )
571
+ )
572
+ else:
573
+ sim_map_buttons.append(
574
+ SimMapButtonPoll(
575
+ query_id=query_id,
576
+ idx=idx,
577
+ token=token,
578
+ token_idx=token_idx,
579
+ )
580
+ )
581
+
582
+ # Add "Reset Image" button to restore the full image
583
+ reset_button = Button(
584
+ "Reset",
585
+ variant="outline",
586
+ size="sm",
587
+ data_image_src=blur_image_base64,
588
+ cls="reset-button pointer-events-auto font-mono text-xs h-5 rounded-none px-2",
589
+ )
590
+
591
+ tokens_icon = Lucide(icon="images", size="15")
592
+
593
+ # Add "Tokens" button - this has no action, just a placeholder
594
+ tokens_button = Button(
595
+ tokens_icon,
596
+ "Tokens",
597
+ size="sm",
598
+ cls="tokens-button flex gap-[3px] font-bold pointer-events-none font-mono text-xs h-5 rounded-none px-2",
599
+ )
600
+
601
+ result_items.append(
602
+ Div(
603
+ Div(
604
+ Div(
605
+ Lucide(icon="file-text"),
606
+ H2(fields["title"], cls="text-xl md:text-2xl font-semibold"),
607
+ Separator(orientation="vertical"),
608
+ Badge(
609
+ f"Relevance score: {result['relevance']:.4f}",
610
+ cls="flex gap-1.5 items-center justify-center",
611
+ ),
612
+ cls="flex items-center gap-2",
613
+ ),
614
+ Div(
615
+ Button(
616
+ "Hide Text",
617
+ size="sm",
618
+ id=f"toggle-button-{idx}",
619
+ onclick=f"toggleTextContent({idx})",
620
+ cls="hidden md:block",
621
+ ),
622
+ ),
623
+ cls="flex flex-wrap items-center justify-between bg-background px-3 py-4",
624
+ ),
625
+ Div(
626
+ Div(
627
+ Div(
628
+ tokens_button,
629
+ *sim_map_buttons,
630
+ reset_button,
631
+ cls="flex flex-wrap gap-px w-full pointer-events-none",
632
+ ),
633
+ Div(
634
+ Div(
635
+ Div(
636
+ Img(
637
+ src=blur_image_base64,
638
+ hx_get=f"/full_image?doc_id={doc_id}",
639
+ style="backdrop-filter: blur(5px);",
640
+ hx_trigger="load",
641
+ hx_swap="outerHTML",
642
+ alt=fields["title"],
643
+ cls="result-image w-full h-full object-contain",
644
+ ),
645
+ Div(
646
+ cls="overlay-container absolute top-0 left-0 w-full h-full pointer-events-none"
647
+ ),
648
+ cls="relative w-full h-full",
649
+ ),
650
+ cls="grid bg-muted p-2",
651
+ ),
652
+ cls="block",
653
+ ),
654
+ id=f"image-column-{idx}",
655
+ cls="image-column relative bg-background px-3 py-5 grid-image-column",
656
+ ),
657
+ Div(
658
+ Div(
659
+ A(
660
+ Lucide(icon="external-link", size="18"),
661
+ f"PDF Source (Page {fields['page_number'] + 1})",
662
+ href=f"{fields['url']}#page={fields['page_number'] + 1}",
663
+ target="_blank",
664
+ cls="flex items-center gap-1.5 font-mono bold text-sm",
665
+ ),
666
+ cls="flex items-center justify-end",
667
+ ),
668
+ Div(
669
+ Div(
670
+ Div(
671
+ Div(
672
+ Div(
673
+ H3(
674
+ "Dynamic summary",
675
+ cls="text-base font-semibold",
676
+ ),
677
+ P(
678
+ NotStr(fields.get("snippet", "")),
679
+ cls="text-highlight text-muted-foreground",
680
+ ),
681
+ cls="grid grid-rows-[auto_0px] content-start gap-y-3",
682
+ ),
683
+ id=f"result-text-snippet-{idx}",
684
+ cls="grid gap-y-3 p-8 border border-dashed",
685
+ ),
686
+ Div(
687
+ Div(
688
+ Div(
689
+ H3(
690
+ "Full text",
691
+ cls="text-base font-semibold",
692
+ ),
693
+ Div(
694
+ P(
695
+ NotStr(fields.get("text", "")),
696
+ cls="text-highlight text-muted-foreground",
697
+ ),
698
+ Br(),
699
+ ),
700
+ cls="grid grid-rows-[auto_0px] content-start gap-y-3",
701
+ ),
702
+ id=f"result-text-full-{idx}",
703
+ cls="grid gap-y-3 p-8 border border-dashed",
704
+ ),
705
+ Div(
706
+ cls="absolute inset-x-0 bottom-0 bg-gradient-to-t from-[#fcfcfd] dark:from-[#1c2024] pt-[7%]"
707
+ ),
708
+ cls="relative grid",
709
+ ),
710
+ cls="grid grid-rows-[1fr_1fr] xl:grid-rows-[1fr_2fr] gap-y-8 p-8 text-sm",
711
+ ),
712
+ cls="grid bg-background",
713
+ ),
714
+ cls="grid bg-muted p-2",
715
+ ),
716
+ id=f"text-column-{idx}",
717
+ cls="text-column relative bg-background px-3 py-5 hidden md-grid-text-column",
718
+ ),
719
+ id=f"image-text-columns-{idx}",
720
+ cls="relative grid grid-cols-1 border-t grid-image-text-columns",
721
+ ),
722
+ cls="grid grid-cols-1 grid-rows-[auto_auto_1fr]",
723
+ ),
724
+ )
725
+
726
+ return [
727
+ Div(
728
+ SearchInfo(search_time, total_count),
729
+ *result_items,
730
+ image_swapping,
731
+ toggle_text_content,
732
+ dynamic_elements_scrollbars,
733
+ id="search-results",
734
+ cls="grid grid-cols-1 gap-px bg-border min-h-0",
735
+ ),
736
+ Div(
737
+ ChatResult(query_id=query_id, query=query, doc_ids=doc_ids),
738
+ hx_swap_oob="true",
739
+ id="chat_messages",
740
+ ),
741
+ ]
742
+
743
+
744
+ def ChatResult(query_id: str, query: str, doc_ids: Optional[list] = None):
745
+ messages = Div(LoadingSkeleton())
746
+
747
+ if doc_ids:
748
+ messages = Div(
749
+ LoadingSkeleton(),
750
+ hx_ext="sse",
751
+ sse_connect=f"/get-message?query_id={query_id}&doc_ids={','.join(doc_ids)}&query={quote_plus(query)}",
752
+ sse_swap="message",
753
+ sse_close="close",
754
+ hx_swap="innerHTML",
755
+ )
756
+
757
+ return Div(
758
+ Div("AI-response (Gemini-2.0)", cls="text-xl font-semibold p-5"),
759
+ Div(
760
+ Div(
761
+ messages,
762
+ ),
763
+ id="chat-messages",
764
+ cls="overflow-auto min-h-0 grid items-end px-5",
765
+ ),
766
+ id="chat_messages",
767
+ cls="h-full grid grid-rows-[auto_1fr_auto] min-h-0 gap-3",
768
+ )
src/frontend/layout.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fasthtml.components import Body, Div, Header, Img, Nav, Title
2
+ from fasthtml.xtend import A, Script
3
+ from lucide_fasthtml import Lucide
4
+ from shad4fast import Button, Separator
5
+
6
+ layout_script = Script(
7
+ """
8
+ document.addEventListener("DOMContentLoaded", function () {
9
+ const main = document.querySelector('main');
10
+ const aside = document.querySelector('aside');
11
+ const body = document.body;
12
+
13
+ if (main && aside && main.nextElementSibling === aside) {
14
+ // If we have both main and aside, adjust the layout for larger screens
15
+ body.classList.remove('grid-cols-1'); // Remove single-column layout
16
+ body.classList.add('md:grid-cols-[minmax(0,_45fr)_minmax(0,_15fr)]'); // Two-column layout on larger screens
17
+ } else if (main) {
18
+ // If only main, keep it full width
19
+ body.classList.add('grid-cols-1');
20
+ }
21
+ });
22
+ """
23
+ )
24
+
25
+ overlay_scrollbars_manager = Script(
26
+ """
27
+ (function () {
28
+ const { OverlayScrollbars } = OverlayScrollbarsGlobal;
29
+
30
+ function getPreferredTheme() {
31
+ return localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
32
+ ? 'dark'
33
+ : 'light';
34
+ }
35
+
36
+ function applyOverlayScrollbars(element, scrollbarTheme) {
37
+ // Destroy existing OverlayScrollbars instance if it exists
38
+ const instance = OverlayScrollbars(element);
39
+ if (instance) {
40
+ instance.destroy();
41
+ }
42
+
43
+ // Reinitialize OverlayScrollbars with the correct theme and settings
44
+ OverlayScrollbars(element, {
45
+ overflow: {
46
+ x: 'hidden',
47
+ y: 'scroll'
48
+ },
49
+ scrollbars: {
50
+ theme: scrollbarTheme,
51
+ visibility: 'auto',
52
+ autoHide: 'leave',
53
+ autoHideDelay: 800
54
+ }
55
+ });
56
+ }
57
+
58
+ // Function to get the current scrollbar theme (light or dark)
59
+ function getScrollbarTheme() {
60
+ const isDarkMode = getPreferredTheme() === 'dark';
61
+ return isDarkMode ? 'os-theme-light' : 'os-theme-dark'; // Light theme in dark mode, dark theme in light mode
62
+ }
63
+
64
+ // Expose the common functions globally for reuse
65
+ window.OverlayScrollbarsManager = {
66
+ applyOverlayScrollbars: applyOverlayScrollbars,
67
+ getScrollbarTheme: getScrollbarTheme
68
+ };
69
+ })();
70
+ """
71
+ )
72
+
73
+ static_elements_scrollbars = Script(
74
+ """
75
+ (function () {
76
+ const { applyOverlayScrollbars, getScrollbarTheme } = OverlayScrollbarsManager;
77
+
78
+ function applyScrollbarsToStaticElements() {
79
+ const mainElement = document.querySelector('main');
80
+ const chatMessagesElement = document.querySelector('#chat-messages');
81
+
82
+ const scrollbarTheme = getScrollbarTheme();
83
+
84
+ if (mainElement) {
85
+ applyOverlayScrollbars(mainElement, scrollbarTheme);
86
+ }
87
+
88
+ if (chatMessagesElement) {
89
+ applyOverlayScrollbars(chatMessagesElement, scrollbarTheme);
90
+ }
91
+ }
92
+
93
+ // Apply the scrollbars on page load
94
+ applyScrollbarsToStaticElements();
95
+
96
+ // Observe changes in the 'dark' class on the <html> element to adjust the theme dynamically
97
+ const observer = new MutationObserver(applyScrollbarsToStaticElements);
98
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
99
+ })();
100
+ """
101
+ )
102
+
103
+
104
+ def Logo():
105
+ return Div(
106
+ Img(
107
+ src="https://assets.vespa.ai/logos/vespa-logo-black.svg",
108
+ alt="Vespa Logo",
109
+ cls="h-full dark:hidden",
110
+ ),
111
+ Img(
112
+ src="https://assets.vespa.ai/logos/vespa-logo-white.svg",
113
+ alt="Vespa Logo Dark Mode",
114
+ cls="h-full hidden dark:block",
115
+ ),
116
+ cls="h-[27px]",
117
+ )
118
+
119
+
120
+ def ThemeToggle(variant="ghost", cls=None, **kwargs):
121
+ return Button(
122
+ Lucide("sun", cls="dark:flex hidden"),
123
+ Lucide("moon", cls="dark:hidden"),
124
+ variant=variant,
125
+ size="icon",
126
+ cls=f"theme-toggle {cls}",
127
+ **kwargs,
128
+ )
129
+
130
+
131
+ def Links():
132
+ return Nav(
133
+ A(
134
+ Button("About this demo?", variant="link"),
135
+ href="/about-this-demo",
136
+ ),
137
+ Separator(orientation="vertical"),
138
+ A(
139
+ Button(Lucide(icon="github"), size="icon", variant="ghost"),
140
+ href="https://github.com/vespa-engine/vespa",
141
+ target="_blank",
142
+ ),
143
+ A(
144
+ Button(Lucide(icon="slack"), size="icon", variant="ghost"),
145
+ href="https://slack.vespa.ai",
146
+ target="_blank",
147
+ ),
148
+ Separator(orientation="vertical"),
149
+ ThemeToggle(),
150
+ cls="flex items-center space-x-2",
151
+ )
152
+
153
+
154
+ def Layout(*c, is_home=False, **kwargs):
155
+ return (
156
+ Title("Visual Retrieval ColPali"),
157
+ Body(
158
+ Header(
159
+ A(Logo(), href="/"),
160
+ Links(),
161
+ cls="min-h-[55px] h-[55px] w-full flex items-center justify-between px-4",
162
+ ),
163
+ *c,
164
+ **kwargs,
165
+ data_is_home=str(is_home).lower(),
166
+ cls="grid grid-rows-[minmax(0,55px)_minmax(0,1fr)] min-h-0",
167
+ ),
168
+ layout_script,
169
+ overlay_scrollbars_manager,
170
+ static_elements_scrollbars,
171
+ )
src/globals.css ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+
6
+ @layer base {
7
+ :root {
8
+ --background: 240 20% 99%; /* 1 */
9
+ --foreground: 210 13% 13%; /* 12 */
10
+ --card: 240 20% 99%; /* 1 */
11
+ --card-foreground: 210 13% 13%; /* 12 */
12
+ --popover: 240 20% 99%; /* 1 */
13
+ --popover-foreground: 210 13% 13%; /* 12 */
14
+ --primary: 210 13% 13%; /* 12 */
15
+ --primary-foreground: 240 20% 98%; /* 2 */
16
+ --secondary: 240 11% 95%; /* 3 */
17
+ --secondary-foreground: 210 13% 13%; /* 12 */
18
+ --muted: 240 11% 95%; /* 3 */
19
+ --muted-foreground: 220 6% 40%; /* 11 */
20
+ --accent: 240 11% 95%; /* 3 */
21
+ --accent-foreground: 210 13% 13%; /* 12 */
22
+ --destructive: 358 75% 59%; /* 9 - red */
23
+ --destructive-foreground: 240 20% 98%; /* 2 */
24
+ --border: 240 10% 86%; /* 6 */
25
+ --input: 240 10% 86%; /* 6 */
26
+ --ring: 210 13% 13%; /* 12 */
27
+ --chart-1: 10 78% 54%; /* 9 - tomato */
28
+ --chart-2: 173 80% 36%; /* 9 - teal */
29
+ --chart-3: 206 100% 50%; /* 9 - blue */
30
+ --chart-4: 42 100% 62%; /* 9 - amber */
31
+ --chart-5: 23 93% 53%; /* 9 - orange */
32
+ }
33
+
34
+ .dark {
35
+ --background: 240 6% 7%; /* 1 */
36
+ --foreground: 220 9% 94%; /* 12 */
37
+ --card: 240 6% 7%; /* 1 */
38
+ --card-foreground: 220 9% 94%; /* 12 */
39
+ --popover: 240 6% 7%; /* 1 */
40
+ --popover-foreground: 220 9% 94%; /* 12 */
41
+ --primary: 220 9% 94%; /* 12 */
42
+ --primary-foreground: 220 6% 10%; /* 2 */
43
+ --secondary: 225 6% 14%; /* 3 */
44
+ --secondary-foreground: 220 9% 94%; /* 12 */
45
+ --muted: 225 6% 14%; /* 3 */
46
+ --muted-foreground: 216 7% 71%; /* 11 */
47
+ --accent: 225 6% 14%; /* 3 */
48
+ --accent-foreground: 220 9% 94%; /* 12 */
49
+ --destructive: 358 75% 59%; /* 9 - red */
50
+ --destructive-foreground: 220 9% 94%; /* 12 */
51
+ --border: 213 8% 23%; /* 6 */
52
+ --input: 213 8% 23%; /* 6 */
53
+ --ring: 220 9% 94%; /* 12 */
54
+ --chart-1: 10 78% 54%; /* 9 - tomato */
55
+ --chart-2: 173 80% 36%; /* 9 - teal */
56
+ --chart-3: 206 100% 50%; /* 9 - blue */
57
+ --chart-4: 42 100% 62%; /* 9 - amber */
58
+ --chart-5: 23 93% 53%; /* 9 - orange */
59
+ }
60
+ }
61
+
62
+ @layer base {
63
+ :root:has(.no-bg-scroll) {
64
+ overflow: hidden;
65
+ }
66
+
67
+ * {
68
+ @apply border-border;
69
+ }
70
+
71
+ body {
72
+ @apply bg-background text-foreground antialiased min-h-screen;
73
+ font-feature-settings: "rlig" 1, "calt" 1;
74
+ }
75
+ }
76
+
77
+ @layer utilities {
78
+
79
+ /* Hide scrollbar for Chrome, Safari and Opera */
80
+ .no-scrollbar::-webkit-scrollbar {
81
+ display: none;
82
+ }
83
+
84
+ /* Hide scrollbar for IE, Edge and Firefox */
85
+ .no-scrollbar {
86
+ -webkit-overflow-scrolling: touch;
87
+ -ms-overflow-style: none;
88
+ /* IE and Edge */
89
+ scrollbar-width: none;
90
+ /* Firefox */
91
+ }
92
+ }
93
+
94
+ @keyframes slideInFromTop {
95
+ from {
96
+ transform: translateY(-100%);
97
+ }
98
+
99
+ to {
100
+ transform: translateY(0);
101
+ }
102
+ }
103
+
104
+ @keyframes slideInFromBottom {
105
+ from {
106
+ transform: translateY(100%);
107
+ }
108
+
109
+ to {
110
+ transform: translateY(0);
111
+ }
112
+ }
113
+
114
+ .toast {
115
+ animation-duration: 0.2s;
116
+ animation-fill-mode: forwards;
117
+ }
118
+
119
+ @media (max-width: 640px) {
120
+ .toast {
121
+ animation-name: slideInFromTop;
122
+ }
123
+ }
124
+
125
+ @media (min-width: 641px) {
126
+ .toast {
127
+ animation-name: slideInFromBottom;
128
+ }
129
+ }
130
+
131
+ @keyframes fade-in {
132
+ from {
133
+ opacity: 0;
134
+ }
135
+ to {
136
+ opacity: 1;
137
+ }
138
+ }
139
+
140
+ @keyframes slide-up {
141
+ from {
142
+ transform: translateY(20px);
143
+ opacity: 0;
144
+ }
145
+ to {
146
+ transform: translateY(0);
147
+ opacity: 1;
148
+ }
149
+ }
150
+
151
+ .animate-fade-in {
152
+ animation: fade-in 1s ease-out forwards;
153
+ }
154
+
155
+ .animate-slide-up {
156
+ animation: slide-up 1s ease-out forwards;
157
+ }
158
+
159
+ .sim-map-button.active {
160
+ background-color: #61D790;
161
+ color: #2E2F27;
162
+
163
+ &:hover {
164
+ background-color: #61D790;
165
+ }
166
+ }
167
+
168
+ .text-highlight strong {
169
+ color: black;
170
+
171
+ .dark & {
172
+ color: white;
173
+ }
174
+ }
175
+
176
+ .tokens-button {
177
+ background-color: #B7E2F1;
178
+ color: #2E2F27;
179
+ }
180
+
181
+ .overlay-image {
182
+ opacity: 0.5;
183
+ position: absolute;
184
+ top: 0;
185
+ left: 0;
186
+ width: 100%;
187
+ height: 100%;
188
+ z-index: 10;
189
+ }
190
+
191
+ header {
192
+ grid-column: 1/-1;
193
+ }
194
+
195
+ body {
196
+ &[data-is-home="true"] {
197
+ background: radial-gradient(circle at 50% 100%, #fcfcfd, #fcfcfd, #fdfdfe, #fdfdfe, #fefefe, #fefefe, #ffffff, #ffffff);
198
+
199
+ .dark & {
200
+ background: radial-gradient(circle at 50% 50%, #272a2d, #242629, #212326, #1e1f22, #1b1c1e, #18181b, #151517, #111113);
201
+ }
202
+ }
203
+ }
204
+
205
+ main {
206
+ overflow: auto;
207
+ }
208
+
209
+ aside {
210
+ overflow: auto;
211
+ }
212
+
213
+ .scroll-container {
214
+ padding-right: 10px;
215
+ }
216
+
217
+ .question-message {
218
+ background-color: #61D790;
219
+ color: #2E2F27;
220
+ }
221
+
222
+ .grid-image-text-columns {
223
+ @apply md:grid-cols-2 md:col-span-2
224
+ }
225
+
226
+ .grid-image-column {
227
+ @apply grid grid-rows-subgrid row-span-2 content-start;
228
+ }
229
+
230
+ .md-grid-text-column {
231
+ @apply md:grid md:grid-rows-subgrid md:row-span-2 md:content-start;
232
+ }
233
+
234
+ #search-input[aria-expanded="true"] {
235
+ border-top: 1px solid hsl(var(--input));
236
+ border-left: 1px solid hsl(var(--input));
237
+ border-right: 1px solid hsl(var(--input));
238
+ border-bottom: none;
239
+ border-bottom-left-radius: 0;
240
+ border-bottom-right-radius: 0;
241
+ }
242
+
243
+ .awesomplete {
244
+ width: 100%;
245
+ }
246
+
247
+ .awesomplete > ul {
248
+ @apply text-sm space-y-1;
249
+ margin: 0;
250
+ border-top: none;
251
+ border-left: 1px solid hsl(var(--input));
252
+ border-right: 1px solid hsl(var(--input));
253
+ border-bottom: 1px solid hsl(var(--input));
254
+ border-radius: 0 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px);
255
+ background: white;
256
+
257
+ .dark & {
258
+ background: hsl(var(--background));
259
+ }
260
+
261
+ box-shadow: none;
262
+ text-shadow: none;
263
+ }
264
+
265
+ .awesomplete > ul:before {
266
+ display: none;
267
+ }
268
+
269
+ .awesomplete > ul > li:hover {
270
+ background-color: #B7E2F1;
271
+ color: #2E2F27;
272
+ }
273
+
274
+ .awesomplete > ul > li[aria-selected="true"] {
275
+ background-color: #B7E2F1;
276
+ color: #2E2F27;
277
+ }
278
+
279
+ .awesomplete mark {
280
+ background-color: #61D790;
281
+ color: #2E2F27;
282
+ }
283
+
284
+ .awesomplete li:hover mark {
285
+ background-color: #61D790;
286
+ color: #2E2F27;
287
+ }
288
+
289
+ .awesomplete li[aria-selected="true"] mark {
290
+ background-color: #61D790;
291
+ color: #2E2F27;
292
+ }
293
+
src/icons.py ADDED
@@ -0,0 +1 @@
 
 
1
+ ICONS = {"chevrons-right": "<path d=\"m6 17 5-5-5-5\"></path><path d=\"m13 17 5-5-5-5\"></path>", "moon": "<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"></path>", "sun": "<circle cx=\"12\" cy=\"12\" r=\"4\"></circle><path d=\"M12 2v2\"></path><path d=\"M12 20v2\"></path><path d=\"m4.93 4.93 1.41 1.41\"></path><path d=\"m17.66 17.66 1.41 1.41\"></path><path d=\"M2 12h2\"></path><path d=\"M20 12h2\"></path><path d=\"m6.34 17.66-1.41 1.41\"></path><path d=\"m19.07 4.93-1.41 1.41\"></path>", "github": "<path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"></path><path d=\"M9 18c-4.51 2-5-2-7-2\"></path>", "slack": "<rect height=\"8\" rx=\"1.5\" width=\"3\" x=\"13\" y=\"2\"></rect><path d=\"M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5\"></path><rect height=\"8\" rx=\"1.5\" width=\"3\" x=\"8\" y=\"14\"></rect><path d=\"M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5\"></path><rect height=\"3\" rx=\"1.5\" width=\"8\" x=\"14\" y=\"13\"></rect><path d=\"M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5\"></path><rect height=\"3\" rx=\"1.5\" width=\"8\" x=\"2\" y=\"8\"></rect><path d=\"M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5\"></path>", "settings": "<path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path><circle cx=\"12\" cy=\"12\" r=\"3\"></circle>", "arrow-right": "<path d=\"M5 12h14\"></path><path d=\"m12 5 7 7-7 7\"></path>", "search": "<circle cx=\"11\" cy=\"11\" r=\"8\"></circle><path d=\"m21 21-4.3-4.3\"></path>", "file-search": "<path d=\"M14 2v4a2 2 0 0 0 2 2h4\"></path><path d=\"M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3\"></path><path d=\"m9 18-1.5-1.5\"></path><circle cx=\"5\" cy=\"14\" r=\"3\"></circle>", "message-circle-question": "<path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\"></path><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path><path d=\"M12 17h.01\"></path>", "text-search": "<path d=\"M21 6H3\"></path><path d=\"M10 12H3\"></path><path d=\"M10 18H3\"></path><circle cx=\"17\" cy=\"15\" r=\"3\"></circle><path d=\"m21 19-1.9-1.9\"></path>", "maximize": "<path d=\"M8 3H5a2 2 0 0 0-2 2v3\"></path><path d=\"M21 8V5a2 2 0 0 0-2-2h-3\"></path><path d=\"M3 16v3a2 2 0 0 0 2 2h3\"></path><path d=\"M16 21h3a2 2 0 0 0 2-2v-3\"></path>", "expand": "<path d=\"m21 21-6-6m6 6v-4.8m0 4.8h-4.8\"></path><path d=\"M3 16.2V21m0 0h4.8M3 21l6-6\"></path><path d=\"M21 7.8V3m0 0h-4.8M21 3l-6 6\"></path><path d=\"M3 7.8V3m0 0h4.8M3 3l6 6\"></path>", "fullscreen": "<path d=\"M3 7V5a2 2 0 0 1 2-2h2\"></path><path d=\"M17 3h2a2 2 0 0 1 2 2v2\"></path><path d=\"M21 17v2a2 2 0 0 1-2 2h-2\"></path><path d=\"M7 21H5a2 2 0 0 1-2-2v-2\"></path><rect height=\"8\" rx=\"1\" width=\"10\" x=\"7\" y=\"8\"></rect>", "images": "<path d=\"M18 22H4a2 2 0 0 1-2-2V6\"></path><path d=\"m22 13-1.296-1.296a2.41 2.41 0 0 0-3.408 0L11 18\"></path><circle cx=\"12\" cy=\"8\" r=\"2\"></circle><rect height=\"16\" rx=\"2\" width=\"16\" x=\"6\" y=\"2\"></rect>", "circle": "<circle cx=\"12\" cy=\"12\" r=\"10\"></circle>", "loader-circle": "<path d=\"M21 12a9 9 0 1 1-6.219-8.56\"></path>", "file-text": "<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"></path><path d=\"M14 2v4a2 2 0 0 0 2 2h4\"></path><path d=\"M10 9H8\"></path><path d=\"M16 13H8\"></path><path d=\"M16 17H8\"></path>", "file-question": "<path d=\"M12 17h.01\"></path><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z\"></path><path d=\"M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3\"></path>", "external-link": "<path d=\"M15 3h6v6\"></path><path d=\"M10 14 21 3\"></path><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>", "linkedin": "<path d=\"M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z\"></path><rect height=\"12\" width=\"4\" x=\"2\" y=\"9\"></rect><circle cx=\"4\" cy=\"4\" r=\"2\"></circle>"}
src/main.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import base64
3
+ import os
4
+ import time
5
+ import uuid
6
+ import logging
7
+ import sys
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from pathlib import Path
10
+
11
+ import google.generativeai as genai
12
+ from fastcore.parallel import threaded
13
+ from fasthtml.common import (
14
+ Aside,
15
+ Div,
16
+ FileResponse,
17
+ HighlightJS,
18
+ Img,
19
+ JSONResponse,
20
+ Link,
21
+ Main,
22
+ P,
23
+ RedirectResponse,
24
+ Script,
25
+ StreamingResponse,
26
+ fast_app,
27
+ serve,
28
+ )
29
+ from PIL import Image
30
+ from shad4fast import ShadHead
31
+ from vespa.application import Vespa
32
+
33
+ from backend.colpali import SimMapGenerator
34
+ from backend.vespa_app import VespaQueryClient
35
+ from frontend.app import (
36
+ AboutThisDemo,
37
+ ChatResult,
38
+ Home,
39
+ Search,
40
+ SearchBox,
41
+ SearchResult,
42
+ SimMapButtonPoll,
43
+ SimMapButtonReady,
44
+ )
45
+ from frontend.layout import Layout
46
+
47
+ highlight_js_theme_link = Link(id="highlight-theme", rel="stylesheet", href="")
48
+ highlight_js_theme = Script(src="/static/js/highlightjs-theme.js")
49
+ highlight_js = HighlightJS(
50
+ langs=["python", "javascript", "java", "json", "xml"],
51
+ dark="github-dark",
52
+ light="github",
53
+ )
54
+
55
+ overlayscrollbars_link = Link(
56
+ rel="stylesheet",
57
+ href="https://cdnjs.cloudflare.com/ajax/libs/overlayscrollbars/2.10.0/styles/overlayscrollbars.min.css",
58
+ type="text/css",
59
+ )
60
+ overlayscrollbars_js = Script(
61
+ src="https://cdnjs.cloudflare.com/ajax/libs/overlayscrollbars/2.10.0/browser/overlayscrollbars.browser.es5.min.js"
62
+ )
63
+ awesomplete_link = Link(
64
+ rel="stylesheet",
65
+ href="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.7/awesomplete.min.css",
66
+ type="text/css",
67
+ )
68
+ awesomplete_js = Script(
69
+ src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.7/awesomplete.min.js"
70
+ )
71
+ sselink = Script(src="https://unpkg.com/htmx-ext-sse@2.2.1/sse.js")
72
+
73
+ # Get log level from environment variable, default to INFO
74
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
75
+ # Configure logger
76
+ logger = logging.getLogger("vespa_app")
77
+ handler = logging.StreamHandler(sys.stdout)
78
+ handler.setFormatter(
79
+ logging.Formatter(
80
+ "%(levelname)s: \t %(asctime)s \t %(message)s",
81
+ datefmt="%Y-%m-%d %H:%M:%S",
82
+ )
83
+ )
84
+ logger.addHandler(handler)
85
+ logger.setLevel(getattr(logging, LOG_LEVEL))
86
+
87
+ app, rt = fast_app(
88
+ htmlkw={"cls": "grid h-full"},
89
+ pico=False,
90
+ hdrs=(
91
+ highlight_js,
92
+ highlight_js_theme_link,
93
+ highlight_js_theme,
94
+ overlayscrollbars_link,
95
+ overlayscrollbars_js,
96
+ awesomplete_link,
97
+ awesomplete_js,
98
+ sselink,
99
+ ShadHead(tw_cdn=False, theme_handle=True),
100
+ ),
101
+ )
102
+ vespa_app: Vespa = VespaQueryClient(logger=logger)
103
+ thread_pool = ThreadPoolExecutor()
104
+ # Gemini config
105
+
106
+ genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
107
+ GEMINI_SYSTEM_PROMPT = """If the user query is a question, try your best to answer it based on the provided images.
108
+ If the user query can not be interpreted as a question, or if the answer to the query can not be inferred from the images,
109
+ answer with the exact phrase "I am sorry, I can't find enough relevant information on these pages to answer your question.".
110
+ Your response should be HTML formatted, but only simple tags, such as <b>. <p>, <i>, <br> <ul> and <li> are allowed. No HTML tables.
111
+ This means that newlines will be replaced with <br> tags, bold text will be enclosed in <b> tags, and so on.
112
+ Do NOT include backticks (`) in your response. Only simple HTML tags and text.
113
+ """
114
+ gemini_model = genai.GenerativeModel(
115
+ "gemini-2.0-flash", system_instruction=GEMINI_SYSTEM_PROMPT
116
+ )
117
+ STATIC_DIR = Path("static")
118
+ IMG_DIR = STATIC_DIR / "full_images"
119
+ SIM_MAP_DIR = STATIC_DIR / "sim_maps"
120
+ os.makedirs(IMG_DIR, exist_ok=True)
121
+ os.makedirs(SIM_MAP_DIR, exist_ok=True)
122
+
123
+
124
+ @app.on_event("startup")
125
+ def load_model_on_startup():
126
+ app.sim_map_generator = SimMapGenerator(logger=logger)
127
+ return
128
+
129
+
130
+ @app.on_event("startup")
131
+ async def keepalive():
132
+ asyncio.create_task(poll_vespa_keepalive())
133
+ return
134
+
135
+
136
+ def generate_query_id(query, ranking_value):
137
+ hash_input = (query + ranking_value).encode("utf-8")
138
+ return hash(hash_input)
139
+
140
+
141
+ @rt("/static/{filepath:path}")
142
+ def serve_static(filepath: str):
143
+ return FileResponse(STATIC_DIR / filepath)
144
+
145
+
146
+ @rt("/")
147
+ def get(session):
148
+ if "session_id" not in session:
149
+ session["session_id"] = str(uuid.uuid4())
150
+ return Layout(Main(Home()), is_home=True)
151
+
152
+
153
+ @rt("/about-this-demo")
154
+ def get():
155
+ return Layout(Main(AboutThisDemo()))
156
+
157
+
158
+ @rt("/search")
159
+ def get(request, query: str = "", ranking: str = "hybrid"):
160
+ logger.info(f"/search: Fetching results for query: {query}, ranking: {ranking}")
161
+
162
+ # Always render the SearchBox first
163
+ if not query:
164
+ # Show SearchBox and a message for missing query
165
+ return Layout(
166
+ Main(
167
+ Div(
168
+ SearchBox(query_value=query, ranking_value=ranking),
169
+ Div(
170
+ P(
171
+ "No query provided. Please enter a query.",
172
+ cls="text-center text-muted-foreground",
173
+ ),
174
+ cls="p-10",
175
+ ),
176
+ cls="grid",
177
+ )
178
+ )
179
+ )
180
+ # Generate a unique query_id based on the query and ranking value
181
+ query_id = generate_query_id(query, ranking)
182
+ # Show the loading message if a query is provided
183
+ return Layout(
184
+ Main(Search(request), data_overlayscrollbars_initialize=True, cls="border-t"),
185
+ Aside(
186
+ ChatResult(query_id=query_id, query=query),
187
+ cls="border-t border-l hidden md:block",
188
+ ),
189
+ ) # Show SearchBox and Loading message initially
190
+
191
+
192
+ @rt("/fetch_results")
193
+ async def get(session, request, query: str, ranking: str):
194
+ if "hx-request" not in request.headers:
195
+ return RedirectResponse("/search")
196
+
197
+ # Get the hash of the query and ranking value
198
+ query_id = generate_query_id(query, ranking)
199
+ logger.info(f"Query id in /fetch_results: {query_id}")
200
+ # Run the embedding and query against Vespa app
201
+ start_inference = time.perf_counter()
202
+ q_embs, idx_to_token = app.sim_map_generator.get_query_embeddings_and_token_map(
203
+ query
204
+ )
205
+ end_inference = time.perf_counter()
206
+ logger.info(
207
+ f"Inference time for query_id: {query_id} \t {end_inference - start_inference:.2f} seconds"
208
+ )
209
+
210
+ start = time.perf_counter()
211
+ # Fetch real search results from Vespa
212
+ result = await vespa_app.get_result_from_query(
213
+ query=query,
214
+ q_embs=q_embs,
215
+ ranking=ranking,
216
+ idx_to_token=idx_to_token,
217
+ )
218
+ end = time.perf_counter()
219
+ logger.info(
220
+ f"Search results fetched in {end - start:.2f} seconds. Vespa search time: {result['timing']['searchtime']}"
221
+ )
222
+ search_time = result["timing"]["searchtime"]
223
+ # Safely get total_count with a default of 0
224
+ total_count = result.get("root", {}).get("fields", {}).get("totalCount", 0)
225
+
226
+ search_results = vespa_app.results_to_search_results(result, idx_to_token)
227
+
228
+ get_and_store_sim_maps(
229
+ query_id=query_id,
230
+ query=query,
231
+ q_embs=q_embs,
232
+ ranking=ranking,
233
+ idx_to_token=idx_to_token,
234
+ doc_ids=[result["fields"]["id"] for result in search_results],
235
+ )
236
+ return SearchResult(search_results, query, query_id, search_time, total_count)
237
+
238
+
239
+ def get_results_children(result):
240
+ search_results = (
241
+ result["root"]["children"]
242
+ if "root" in result and "children" in result["root"]
243
+ else []
244
+ )
245
+ return search_results
246
+
247
+
248
+ async def poll_vespa_keepalive():
249
+ while True:
250
+ await asyncio.sleep(5)
251
+ await vespa_app.keepalive()
252
+ logger.debug(f"Vespa keepalive: {time.time()}")
253
+
254
+
255
+ @threaded
256
+ def get_and_store_sim_maps(
257
+ query_id, query: str, q_embs, ranking, idx_to_token, doc_ids
258
+ ):
259
+ ranking_sim = ranking + "_sim"
260
+ vespa_sim_maps = vespa_app.get_sim_maps_from_query(
261
+ query=query,
262
+ q_embs=q_embs,
263
+ ranking=ranking_sim,
264
+ idx_to_token=idx_to_token,
265
+ )
266
+ img_paths = [IMG_DIR / f"{doc_id}.jpg" for doc_id in doc_ids]
267
+ # All images should be downloaded, but best to wait 5 secs
268
+ max_wait = 5
269
+ start_time = time.time()
270
+ while (
271
+ not all([os.path.exists(img_path) for img_path in img_paths])
272
+ and time.time() - start_time < max_wait
273
+ ):
274
+ time.sleep(0.2)
275
+ if not all([os.path.exists(img_path) for img_path in img_paths]):
276
+ logger.warning(f"Images not ready in 5 seconds for query_id: {query_id}")
277
+ return False
278
+ sim_map_generator = app.sim_map_generator.gen_similarity_maps(
279
+ query=query,
280
+ query_embs=q_embs,
281
+ token_idx_map=idx_to_token,
282
+ images=img_paths,
283
+ vespa_sim_maps=vespa_sim_maps,
284
+ )
285
+ for idx, token, token_idx, blended_img_base64 in sim_map_generator:
286
+ with open(SIM_MAP_DIR / f"{query_id}_{idx}_{token_idx}.png", "wb") as f:
287
+ f.write(base64.b64decode(blended_img_base64))
288
+ logger.debug(
289
+ f"Sim map saved to disk for query_id: {query_id}, idx: {idx}, token: {token}"
290
+ )
291
+ return True
292
+
293
+
294
+ @app.get("/get_sim_map")
295
+ async def get_sim_map(query_id: str, idx: int, token: str, token_idx: int):
296
+ """
297
+ Endpoint that each of the sim map button polls to get the sim map image
298
+ when it is ready. If it is not ready, returns a SimMapButtonPoll, that
299
+ continues to poll every 1 second.
300
+ """
301
+ sim_map_path = SIM_MAP_DIR / f"{query_id}_{idx}_{token_idx}.png"
302
+ if not os.path.exists(sim_map_path):
303
+ logger.debug(
304
+ f"Sim map not ready for query_id: {query_id}, idx: {idx}, token: {token}"
305
+ )
306
+ return SimMapButtonPoll(
307
+ query_id=query_id, idx=idx, token=token, token_idx=token_idx
308
+ )
309
+ else:
310
+ return SimMapButtonReady(
311
+ query_id=query_id,
312
+ idx=idx,
313
+ token=token,
314
+ token_idx=token_idx,
315
+ img_src=sim_map_path,
316
+ )
317
+
318
+
319
+ @app.get("/full_image")
320
+ async def full_image(doc_id: str):
321
+ """
322
+ Endpoint to get the full quality image for a given result id.
323
+ """
324
+ img_path = IMG_DIR / f"{doc_id}.jpg"
325
+ if not os.path.exists(img_path):
326
+ image_data = await vespa_app.get_full_image_from_vespa(doc_id)
327
+ # image data is base 64 encoded string. Save it to disk as jpg.
328
+ with open(img_path, "wb") as f:
329
+ f.write(base64.b64decode(image_data))
330
+ logger.debug(f"Full image saved to disk for doc_id: {doc_id}")
331
+ else:
332
+ with open(img_path, "rb") as f:
333
+ image_data = base64.b64encode(f.read()).decode("utf-8")
334
+ return Img(
335
+ src=f"data:image/jpeg;base64,{image_data}",
336
+ alt="something",
337
+ cls="result-image w-full h-full object-contain",
338
+ )
339
+
340
+
341
+ @rt("/suggestions")
342
+ async def get_suggestions(query: str = ""):
343
+ """Endpoint to get suggestions as user types in the search box"""
344
+ query = query.lower().strip()
345
+
346
+ if query:
347
+ suggestions = await vespa_app.get_suggestions(query)
348
+ if len(suggestions) > 0:
349
+ return JSONResponse({"suggestions": suggestions})
350
+
351
+ return JSONResponse({"suggestions": []})
352
+
353
+
354
+ async def message_generator(query_id: str, query: str, doc_ids: list):
355
+ """Generator function to yield SSE messages for chat response"""
356
+ images = []
357
+ num_images = 3 # Number of images before firing chat request
358
+ max_wait = 10 # seconds
359
+ start_time = time.time()
360
+ # Check if full images are ready on disk
361
+ while (
362
+ len(images) < min(num_images, len(doc_ids))
363
+ and time.time() - start_time < max_wait
364
+ ):
365
+ images = []
366
+ for idx in range(num_images):
367
+ image_filename = IMG_DIR / f"{doc_ids[idx]}.jpg"
368
+ if not os.path.exists(image_filename):
369
+ logger.debug(
370
+ f"Message generator: Full image not ready for query_id: {query_id}, idx: {idx}"
371
+ )
372
+ continue
373
+ else:
374
+ logger.debug(
375
+ f"Message generator: image ready for query_id: {query_id}, idx: {idx}"
376
+ )
377
+ images.append(Image.open(image_filename))
378
+ if len(images) < num_images:
379
+ await asyncio.sleep(0.2)
380
+
381
+ # yield message with number of images ready
382
+ yield f"event: message\ndata: Generating response based on {len(images)} images...\n\n"
383
+ if not images:
384
+ yield "event: message\ndata: Failed to send images to Gemini 2.0!\n\n"
385
+ yield "event: close\ndata: \n\n"
386
+ return
387
+
388
+ # If newlines are present in the response, the connection will be closed.
389
+ def replace_newline_with_br(text):
390
+ return text.replace("\n", "<br>")
391
+
392
+ response_text = ""
393
+ async for chunk in await gemini_model.generate_content_async(
394
+ images + ["\n\n Query: ", query], stream=True
395
+ ):
396
+ if chunk.text:
397
+ response_text += chunk.text
398
+ response_text = replace_newline_with_br(response_text)
399
+ yield f"event: message\ndata: {response_text}\n\n"
400
+ await asyncio.sleep(0.1)
401
+ yield "event: close\ndata: \n\n"
402
+
403
+
404
+ @app.get("/get-message")
405
+ async def get_message(query_id: str, query: str, doc_ids: str):
406
+ return StreamingResponse(
407
+ message_generator(query_id=query_id, query=query, doc_ids=doc_ids.split(",")),
408
+ media_type="text/event-stream",
409
+ )
410
+
411
+
412
+ @rt("/app")
413
+ def get():
414
+ return Layout(Main(Div(P(f"Connected to Vespa at {vespa_app.url}"), cls="p-4")))
415
+
416
+
417
+ if __name__ == "__main__":
418
+ HOT_RELOAD = os.getenv("HOT_RELOAD", "False").lower() == "true"
419
+ logger.info(f"Starting app with hot reload: {HOT_RELOAD}")
420
+ serve(port=7860, reload=HOT_RELOAD)
src/output.css ADDED
@@ -0,0 +1,2973 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *, ::before, ::after {
2
+ --tw-border-spacing-x: 0;
3
+ --tw-border-spacing-y: 0;
4
+ --tw-translate-x: 0;
5
+ --tw-translate-y: 0;
6
+ --tw-rotate: 0;
7
+ --tw-skew-x: 0;
8
+ --tw-skew-y: 0;
9
+ --tw-scale-x: 1;
10
+ --tw-scale-y: 1;
11
+ --tw-pan-x: ;
12
+ --tw-pan-y: ;
13
+ --tw-pinch-zoom: ;
14
+ --tw-scroll-snap-strictness: proximity;
15
+ --tw-gradient-from-position: ;
16
+ --tw-gradient-via-position: ;
17
+ --tw-gradient-to-position: ;
18
+ --tw-ordinal: ;
19
+ --tw-slashed-zero: ;
20
+ --tw-numeric-figure: ;
21
+ --tw-numeric-spacing: ;
22
+ --tw-numeric-fraction: ;
23
+ --tw-ring-inset: ;
24
+ --tw-ring-offset-width: 0px;
25
+ --tw-ring-offset-color: #fff;
26
+ --tw-ring-color: rgb(59 130 246 / 0.5);
27
+ --tw-ring-offset-shadow: 0 0 #0000;
28
+ --tw-ring-shadow: 0 0 #0000;
29
+ --tw-shadow: 0 0 #0000;
30
+ --tw-shadow-colored: 0 0 #0000;
31
+ --tw-blur: ;
32
+ --tw-brightness: ;
33
+ --tw-contrast: ;
34
+ --tw-grayscale: ;
35
+ --tw-hue-rotate: ;
36
+ --tw-invert: ;
37
+ --tw-saturate: ;
38
+ --tw-sepia: ;
39
+ --tw-drop-shadow: ;
40
+ --tw-backdrop-blur: ;
41
+ --tw-backdrop-brightness: ;
42
+ --tw-backdrop-contrast: ;
43
+ --tw-backdrop-grayscale: ;
44
+ --tw-backdrop-hue-rotate: ;
45
+ --tw-backdrop-invert: ;
46
+ --tw-backdrop-opacity: ;
47
+ --tw-backdrop-saturate: ;
48
+ --tw-backdrop-sepia: ;
49
+ --tw-contain-size: ;
50
+ --tw-contain-layout: ;
51
+ --tw-contain-paint: ;
52
+ --tw-contain-style: ;
53
+ }
54
+
55
+ ::backdrop {
56
+ --tw-border-spacing-x: 0;
57
+ --tw-border-spacing-y: 0;
58
+ --tw-translate-x: 0;
59
+ --tw-translate-y: 0;
60
+ --tw-rotate: 0;
61
+ --tw-skew-x: 0;
62
+ --tw-skew-y: 0;
63
+ --tw-scale-x: 1;
64
+ --tw-scale-y: 1;
65
+ --tw-pan-x: ;
66
+ --tw-pan-y: ;
67
+ --tw-pinch-zoom: ;
68
+ --tw-scroll-snap-strictness: proximity;
69
+ --tw-gradient-from-position: ;
70
+ --tw-gradient-via-position: ;
71
+ --tw-gradient-to-position: ;
72
+ --tw-ordinal: ;
73
+ --tw-slashed-zero: ;
74
+ --tw-numeric-figure: ;
75
+ --tw-numeric-spacing: ;
76
+ --tw-numeric-fraction: ;
77
+ --tw-ring-inset: ;
78
+ --tw-ring-offset-width: 0px;
79
+ --tw-ring-offset-color: #fff;
80
+ --tw-ring-color: rgb(59 130 246 / 0.5);
81
+ --tw-ring-offset-shadow: 0 0 #0000;
82
+ --tw-ring-shadow: 0 0 #0000;
83
+ --tw-shadow: 0 0 #0000;
84
+ --tw-shadow-colored: 0 0 #0000;
85
+ --tw-blur: ;
86
+ --tw-brightness: ;
87
+ --tw-contrast: ;
88
+ --tw-grayscale: ;
89
+ --tw-hue-rotate: ;
90
+ --tw-invert: ;
91
+ --tw-saturate: ;
92
+ --tw-sepia: ;
93
+ --tw-drop-shadow: ;
94
+ --tw-backdrop-blur: ;
95
+ --tw-backdrop-brightness: ;
96
+ --tw-backdrop-contrast: ;
97
+ --tw-backdrop-grayscale: ;
98
+ --tw-backdrop-hue-rotate: ;
99
+ --tw-backdrop-invert: ;
100
+ --tw-backdrop-opacity: ;
101
+ --tw-backdrop-saturate: ;
102
+ --tw-backdrop-sepia: ;
103
+ --tw-contain-size: ;
104
+ --tw-contain-layout: ;
105
+ --tw-contain-paint: ;
106
+ --tw-contain-style: ;
107
+ }
108
+
109
+ /*
110
+ ! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com
111
+ */
112
+
113
+ /*
114
+ 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
115
+ 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
116
+ */
117
+
118
+ *,
119
+ ::before,
120
+ ::after {
121
+ box-sizing: border-box;
122
+ /* 1 */
123
+ border-width: 0;
124
+ /* 2 */
125
+ border-style: solid;
126
+ /* 2 */
127
+ border-color: #e5e7eb;
128
+ /* 2 */
129
+ }
130
+
131
+ ::before,
132
+ ::after {
133
+ --tw-content: '';
134
+ }
135
+
136
+ /*
137
+ 1. Use a consistent sensible line-height in all browsers.
138
+ 2. Prevent adjustments of font size after orientation changes in iOS.
139
+ 3. Use a more readable tab size.
140
+ 4. Use the user's configured `sans` font-family by default.
141
+ 5. Use the user's configured `sans` font-feature-settings by default.
142
+ 6. Use the user's configured `sans` font-variation-settings by default.
143
+ 7. Disable tap highlights on iOS
144
+ */
145
+
146
+ html,
147
+ :host {
148
+ line-height: 1.5;
149
+ /* 1 */
150
+ -webkit-text-size-adjust: 100%;
151
+ /* 2 */
152
+ -moz-tab-size: 4;
153
+ /* 3 */
154
+ -o-tab-size: 4;
155
+ tab-size: 4;
156
+ /* 3 */
157
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
158
+ /* 4 */
159
+ font-feature-settings: normal;
160
+ /* 5 */
161
+ font-variation-settings: normal;
162
+ /* 6 */
163
+ -webkit-tap-highlight-color: transparent;
164
+ /* 7 */
165
+ }
166
+
167
+ /*
168
+ 1. Remove the margin in all browsers.
169
+ 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
170
+ */
171
+
172
+ body {
173
+ margin: 0;
174
+ /* 1 */
175
+ line-height: inherit;
176
+ /* 2 */
177
+ }
178
+
179
+ /*
180
+ 1. Add the correct height in Firefox.
181
+ 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
182
+ 3. Ensure horizontal rules are visible by default.
183
+ */
184
+
185
+ hr {
186
+ height: 0;
187
+ /* 1 */
188
+ color: inherit;
189
+ /* 2 */
190
+ border-top-width: 1px;
191
+ /* 3 */
192
+ }
193
+
194
+ /*
195
+ Add the correct text decoration in Chrome, Edge, and Safari.
196
+ */
197
+
198
+ abbr:where([title]) {
199
+ -webkit-text-decoration: underline dotted;
200
+ text-decoration: underline dotted;
201
+ }
202
+
203
+ /*
204
+ Remove the default font size and weight for headings.
205
+ */
206
+
207
+ h1,
208
+ h2,
209
+ h3,
210
+ h4,
211
+ h5,
212
+ h6 {
213
+ font-size: inherit;
214
+ font-weight: inherit;
215
+ }
216
+
217
+ /*
218
+ Reset links to optimize for opt-in styling instead of opt-out.
219
+ */
220
+
221
+ a {
222
+ color: inherit;
223
+ text-decoration: inherit;
224
+ }
225
+
226
+ /*
227
+ Add the correct font weight in Edge and Safari.
228
+ */
229
+
230
+ b,
231
+ strong {
232
+ font-weight: bolder;
233
+ }
234
+
235
+ /*
236
+ 1. Use the user's configured `mono` font-family by default.
237
+ 2. Use the user's configured `mono` font-feature-settings by default.
238
+ 3. Use the user's configured `mono` font-variation-settings by default.
239
+ 4. Correct the odd `em` font sizing in all browsers.
240
+ */
241
+
242
+ code,
243
+ kbd,
244
+ samp,
245
+ pre {
246
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
247
+ /* 1 */
248
+ font-feature-settings: normal;
249
+ /* 2 */
250
+ font-variation-settings: normal;
251
+ /* 3 */
252
+ font-size: 1em;
253
+ /* 4 */
254
+ }
255
+
256
+ /*
257
+ Add the correct font size in all browsers.
258
+ */
259
+
260
+ small {
261
+ font-size: 80%;
262
+ }
263
+
264
+ /*
265
+ Prevent `sub` and `sup` elements from affecting the line height in all browsers.
266
+ */
267
+
268
+ sub,
269
+ sup {
270
+ font-size: 75%;
271
+ line-height: 0;
272
+ position: relative;
273
+ vertical-align: baseline;
274
+ }
275
+
276
+ sub {
277
+ bottom: -0.25em;
278
+ }
279
+
280
+ sup {
281
+ top: -0.5em;
282
+ }
283
+
284
+ /*
285
+ 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)
286
+ 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)
287
+ 3. Remove gaps between table borders by default.
288
+ */
289
+
290
+ table {
291
+ text-indent: 0;
292
+ /* 1 */
293
+ border-color: inherit;
294
+ /* 2 */
295
+ border-collapse: collapse;
296
+ /* 3 */
297
+ }
298
+
299
+ /*
300
+ 1. Change the font styles in all browsers.
301
+ 2. Remove the margin in Firefox and Safari.
302
+ 3. Remove default padding in all browsers.
303
+ */
304
+
305
+ button,
306
+ input,
307
+ optgroup,
308
+ select,
309
+ textarea {
310
+ font-family: inherit;
311
+ /* 1 */
312
+ font-feature-settings: inherit;
313
+ /* 1 */
314
+ font-variation-settings: inherit;
315
+ /* 1 */
316
+ font-size: 100%;
317
+ /* 1 */
318
+ font-weight: inherit;
319
+ /* 1 */
320
+ line-height: inherit;
321
+ /* 1 */
322
+ letter-spacing: inherit;
323
+ /* 1 */
324
+ color: inherit;
325
+ /* 1 */
326
+ margin: 0;
327
+ /* 2 */
328
+ padding: 0;
329
+ /* 3 */
330
+ }
331
+
332
+ /*
333
+ Remove the inheritance of text transform in Edge and Firefox.
334
+ */
335
+
336
+ button,
337
+ select {
338
+ text-transform: none;
339
+ }
340
+
341
+ /*
342
+ 1. Correct the inability to style clickable types in iOS and Safari.
343
+ 2. Remove default button styles.
344
+ */
345
+
346
+ button,
347
+ input:where([type='button']),
348
+ input:where([type='reset']),
349
+ input:where([type='submit']) {
350
+ -webkit-appearance: button;
351
+ /* 1 */
352
+ background-color: transparent;
353
+ /* 2 */
354
+ background-image: none;
355
+ /* 2 */
356
+ }
357
+
358
+ /*
359
+ Use the modern Firefox focus style for all focusable elements.
360
+ */
361
+
362
+ :-moz-focusring {
363
+ outline: auto;
364
+ }
365
+
366
+ /*
367
+ Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
368
+ */
369
+
370
+ :-moz-ui-invalid {
371
+ box-shadow: none;
372
+ }
373
+
374
+ /*
375
+ Add the correct vertical alignment in Chrome and Firefox.
376
+ */
377
+
378
+ progress {
379
+ vertical-align: baseline;
380
+ }
381
+
382
+ /*
383
+ Correct the cursor style of increment and decrement buttons in Safari.
384
+ */
385
+
386
+ ::-webkit-inner-spin-button,
387
+ ::-webkit-outer-spin-button {
388
+ height: auto;
389
+ }
390
+
391
+ /*
392
+ 1. Correct the odd appearance in Chrome and Safari.
393
+ 2. Correct the outline style in Safari.
394
+ */
395
+
396
+ [type='search'] {
397
+ -webkit-appearance: textfield;
398
+ /* 1 */
399
+ outline-offset: -2px;
400
+ /* 2 */
401
+ }
402
+
403
+ /*
404
+ Remove the inner padding in Chrome and Safari on macOS.
405
+ */
406
+
407
+ ::-webkit-search-decoration {
408
+ -webkit-appearance: none;
409
+ }
410
+
411
+ /*
412
+ 1. Correct the inability to style clickable types in iOS and Safari.
413
+ 2. Change font properties to `inherit` in Safari.
414
+ */
415
+
416
+ ::-webkit-file-upload-button {
417
+ -webkit-appearance: button;
418
+ /* 1 */
419
+ font: inherit;
420
+ /* 2 */
421
+ }
422
+
423
+ /*
424
+ Add the correct display in Chrome and Safari.
425
+ */
426
+
427
+ summary {
428
+ display: list-item;
429
+ }
430
+
431
+ /*
432
+ Removes the default spacing and border for appropriate elements.
433
+ */
434
+
435
+ blockquote,
436
+ dl,
437
+ dd,
438
+ h1,
439
+ h2,
440
+ h3,
441
+ h4,
442
+ h5,
443
+ h6,
444
+ hr,
445
+ figure,
446
+ p,
447
+ pre {
448
+ margin: 0;
449
+ }
450
+
451
+ fieldset {
452
+ margin: 0;
453
+ padding: 0;
454
+ }
455
+
456
+ legend {
457
+ padding: 0;
458
+ }
459
+
460
+ ol,
461
+ ul,
462
+ menu {
463
+ list-style: none;
464
+ margin: 0;
465
+ padding: 0;
466
+ }
467
+
468
+ /*
469
+ Reset default styling for dialogs.
470
+ */
471
+
472
+ dialog {
473
+ padding: 0;
474
+ }
475
+
476
+ /*
477
+ Prevent resizing textareas horizontally by default.
478
+ */
479
+
480
+ textarea {
481
+ resize: vertical;
482
+ }
483
+
484
+ /*
485
+ 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
486
+ 2. Set the default placeholder color to the user's configured gray 400 color.
487
+ */
488
+
489
+ input::-moz-placeholder, textarea::-moz-placeholder {
490
+ opacity: 1;
491
+ /* 1 */
492
+ color: #9ca3af;
493
+ /* 2 */
494
+ }
495
+
496
+ input::placeholder,
497
+ textarea::placeholder {
498
+ opacity: 1;
499
+ /* 1 */
500
+ color: #9ca3af;
501
+ /* 2 */
502
+ }
503
+
504
+ /*
505
+ Set the default cursor for buttons.
506
+ */
507
+
508
+ button,
509
+ [role="button"] {
510
+ cursor: pointer;
511
+ }
512
+
513
+ /*
514
+ Make sure disabled buttons don't get the pointer cursor.
515
+ */
516
+
517
+ :disabled {
518
+ cursor: default;
519
+ }
520
+
521
+ /*
522
+ 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
523
+ 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
524
+ This can trigger a poorly considered lint error in some tools but is included by design.
525
+ */
526
+
527
+ img,
528
+ svg,
529
+ video,
530
+ canvas,
531
+ audio,
532
+ iframe,
533
+ embed,
534
+ object {
535
+ display: block;
536
+ /* 1 */
537
+ vertical-align: middle;
538
+ /* 2 */
539
+ }
540
+
541
+ /*
542
+ Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
543
+ */
544
+
545
+ img,
546
+ video {
547
+ max-width: 100%;
548
+ height: auto;
549
+ }
550
+
551
+ /* Make elements with the HTML hidden attribute stay hidden by default */
552
+
553
+ [hidden] {
554
+ display: none;
555
+ }
556
+
557
+ :root {
558
+ --background: 240 20% 99%;
559
+ /* 1 */
560
+ --foreground: 210 13% 13%;
561
+ /* 12 */
562
+ --card: 240 20% 99%;
563
+ /* 1 */
564
+ --card-foreground: 210 13% 13%;
565
+ /* 12 */
566
+ --popover: 240 20% 99%;
567
+ /* 1 */
568
+ --popover-foreground: 210 13% 13%;
569
+ /* 12 */
570
+ --primary: 210 13% 13%;
571
+ /* 12 */
572
+ --primary-foreground: 240 20% 98%;
573
+ /* 2 */
574
+ --secondary: 240 11% 95%;
575
+ /* 3 */
576
+ --secondary-foreground: 210 13% 13%;
577
+ /* 12 */
578
+ --muted: 240 11% 95%;
579
+ /* 3 */
580
+ --muted-foreground: 220 6% 40%;
581
+ /* 11 */
582
+ --accent: 240 11% 95%;
583
+ /* 3 */
584
+ --accent-foreground: 210 13% 13%;
585
+ /* 12 */
586
+ --destructive: 358 75% 59%;
587
+ /* 9 - red */
588
+ --destructive-foreground: 240 20% 98%;
589
+ /* 2 */
590
+ --border: 240 10% 86%;
591
+ /* 6 */
592
+ --input: 240 10% 86%;
593
+ /* 6 */
594
+ --ring: 210 13% 13%;
595
+ /* 12 */
596
+ --chart-1: 10 78% 54%;
597
+ /* 9 - tomato */
598
+ --chart-2: 173 80% 36%;
599
+ /* 9 - teal */
600
+ --chart-3: 206 100% 50%;
601
+ /* 9 - blue */
602
+ --chart-4: 42 100% 62%;
603
+ /* 9 - amber */
604
+ --chart-5: 23 93% 53%;
605
+ /* 9 - orange */
606
+ }
607
+
608
+ .dark {
609
+ --background: 240 6% 7%;
610
+ /* 1 */
611
+ --foreground: 220 9% 94%;
612
+ /* 12 */
613
+ --card: 240 6% 7%;
614
+ /* 1 */
615
+ --card-foreground: 220 9% 94%;
616
+ /* 12 */
617
+ --popover: 240 6% 7%;
618
+ /* 1 */
619
+ --popover-foreground: 220 9% 94%;
620
+ /* 12 */
621
+ --primary: 220 9% 94%;
622
+ /* 12 */
623
+ --primary-foreground: 220 6% 10%;
624
+ /* 2 */
625
+ --secondary: 225 6% 14%;
626
+ /* 3 */
627
+ --secondary-foreground: 220 9% 94%;
628
+ /* 12 */
629
+ --muted: 225 6% 14%;
630
+ /* 3 */
631
+ --muted-foreground: 216 7% 71%;
632
+ /* 11 */
633
+ --accent: 225 6% 14%;
634
+ /* 3 */
635
+ --accent-foreground: 220 9% 94%;
636
+ /* 12 */
637
+ --destructive: 358 75% 59%;
638
+ /* 9 - red */
639
+ --destructive-foreground: 220 9% 94%;
640
+ /* 12 */
641
+ --border: 213 8% 23%;
642
+ /* 6 */
643
+ --input: 213 8% 23%;
644
+ /* 6 */
645
+ --ring: 220 9% 94%;
646
+ /* 12 */
647
+ --chart-1: 10 78% 54%;
648
+ /* 9 - tomato */
649
+ --chart-2: 173 80% 36%;
650
+ /* 9 - teal */
651
+ --chart-3: 206 100% 50%;
652
+ /* 9 - blue */
653
+ --chart-4: 42 100% 62%;
654
+ /* 9 - amber */
655
+ --chart-5: 23 93% 53%;
656
+ /* 9 - orange */
657
+ }
658
+
659
+ :root:has(.no-bg-scroll) {
660
+ overflow: hidden;
661
+ }
662
+
663
+ * {
664
+ border-color: hsl(var(--border));
665
+ }
666
+
667
+ body {
668
+ min-height: 100vh;
669
+ background-color: hsl(var(--background));
670
+ color: hsl(var(--foreground));
671
+ -webkit-font-smoothing: antialiased;
672
+ -moz-osx-font-smoothing: grayscale;
673
+ font-feature-settings: "rlig" 1, "calt" 1;
674
+ }
675
+
676
+ .container {
677
+ width: 100%;
678
+ margin-right: auto;
679
+ margin-left: auto;
680
+ padding-right: 2rem;
681
+ padding-left: 2rem;
682
+ }
683
+
684
+ @media (min-width: 1400px) {
685
+ .container {
686
+ max-width: 1400px;
687
+ }
688
+ }
689
+
690
+ .sr-only {
691
+ position: absolute;
692
+ width: 1px;
693
+ height: 1px;
694
+ padding: 0;
695
+ margin: -1px;
696
+ overflow: hidden;
697
+ clip: rect(0, 0, 0, 0);
698
+ white-space: nowrap;
699
+ border-width: 0;
700
+ }
701
+
702
+ .pointer-events-none {
703
+ pointer-events: none;
704
+ }
705
+
706
+ .pointer-events-auto {
707
+ pointer-events: auto;
708
+ }
709
+
710
+ .invisible {
711
+ visibility: hidden;
712
+ }
713
+
714
+ .static {
715
+ position: static;
716
+ }
717
+
718
+ .fixed {
719
+ position: fixed;
720
+ }
721
+
722
+ .absolute {
723
+ position: absolute;
724
+ }
725
+
726
+ .relative {
727
+ position: relative;
728
+ }
729
+
730
+ .inset-0 {
731
+ inset: 0px;
732
+ }
733
+
734
+ .inset-x-0 {
735
+ left: 0px;
736
+ right: 0px;
737
+ }
738
+
739
+ .inset-y-0 {
740
+ top: 0px;
741
+ bottom: 0px;
742
+ }
743
+
744
+ .-bottom-12 {
745
+ bottom: -3rem;
746
+ }
747
+
748
+ .-left-12 {
749
+ left: -3rem;
750
+ }
751
+
752
+ .-right-12 {
753
+ right: -3rem;
754
+ }
755
+
756
+ .-top-12 {
757
+ top: -3rem;
758
+ }
759
+
760
+ .bottom-0 {
761
+ bottom: 0px;
762
+ }
763
+
764
+ .left-0 {
765
+ left: 0px;
766
+ }
767
+
768
+ .left-1\/2 {
769
+ left: 50%;
770
+ }
771
+
772
+ .left-2 {
773
+ left: 0.5rem;
774
+ }
775
+
776
+ .left-\[50\%\] {
777
+ left: 50%;
778
+ }
779
+
780
+ .right-0 {
781
+ right: 0px;
782
+ }
783
+
784
+ .right-2 {
785
+ right: 0.5rem;
786
+ }
787
+
788
+ .right-4 {
789
+ right: 1rem;
790
+ }
791
+
792
+ .top-0 {
793
+ top: 0px;
794
+ }
795
+
796
+ .top-1\/2 {
797
+ top: 50%;
798
+ }
799
+
800
+ .top-2 {
801
+ top: 0.5rem;
802
+ }
803
+
804
+ .top-4 {
805
+ top: 1rem;
806
+ }
807
+
808
+ .top-\[50\%\] {
809
+ top: 50%;
810
+ }
811
+
812
+ .top-full {
813
+ top: 100%;
814
+ }
815
+
816
+ .z-10 {
817
+ z-index: 10;
818
+ }
819
+
820
+ .z-40 {
821
+ z-index: 40;
822
+ }
823
+
824
+ .z-50 {
825
+ z-index: 50;
826
+ }
827
+
828
+ .z-\[100\] {
829
+ z-index: 100;
830
+ }
831
+
832
+ .-mx-1 {
833
+ margin-left: -0.25rem;
834
+ margin-right: -0.25rem;
835
+ }
836
+
837
+ .mx-auto {
838
+ margin-left: auto;
839
+ margin-right: auto;
840
+ }
841
+
842
+ .my-1 {
843
+ margin-top: 0.25rem;
844
+ margin-bottom: 0.25rem;
845
+ }
846
+
847
+ .-ml-4 {
848
+ margin-left: -1rem;
849
+ }
850
+
851
+ .-mt-4 {
852
+ margin-top: -1rem;
853
+ }
854
+
855
+ .mb-1 {
856
+ margin-bottom: 0.25rem;
857
+ }
858
+
859
+ .mr-1\.5 {
860
+ margin-right: 0.375rem;
861
+ }
862
+
863
+ .mt-2 {
864
+ margin-top: 0.5rem;
865
+ }
866
+
867
+ .mt-\[13vh\] {
868
+ margin-top: 13vh;
869
+ }
870
+
871
+ .mt-\[8vh\] {
872
+ margin-top: 8vh;
873
+ }
874
+
875
+ .mt-8 {
876
+ margin-top: 2rem;
877
+ }
878
+
879
+ .mt-5 {
880
+ margin-top: 1.25rem;
881
+ }
882
+
883
+ .block {
884
+ display: block;
885
+ }
886
+
887
+ .flex {
888
+ display: flex;
889
+ }
890
+
891
+ .inline-flex {
892
+ display: inline-flex;
893
+ }
894
+
895
+ .table {
896
+ display: table;
897
+ }
898
+
899
+ .grid {
900
+ display: grid;
901
+ }
902
+
903
+ .contents {
904
+ display: contents;
905
+ }
906
+
907
+ .hidden {
908
+ display: none;
909
+ }
910
+
911
+ .aspect-square {
912
+ aspect-ratio: 1 / 1;
913
+ }
914
+
915
+ .size-4 {
916
+ width: 1rem;
917
+ height: 1rem;
918
+ }
919
+
920
+ .size-5 {
921
+ width: 1.25rem;
922
+ height: 1.25rem;
923
+ }
924
+
925
+ .h-10 {
926
+ height: 2.5rem;
927
+ }
928
+
929
+ .h-11 {
930
+ height: 2.75rem;
931
+ }
932
+
933
+ .h-12 {
934
+ height: 3rem;
935
+ }
936
+
937
+ .h-2 {
938
+ height: 0.5rem;
939
+ }
940
+
941
+ .h-2\.5 {
942
+ height: 0.625rem;
943
+ }
944
+
945
+ .h-3\.5 {
946
+ height: 0.875rem;
947
+ }
948
+
949
+ .h-4 {
950
+ height: 1rem;
951
+ }
952
+
953
+ .h-5 {
954
+ height: 1.25rem;
955
+ }
956
+
957
+ .h-6 {
958
+ height: 1.5rem;
959
+ }
960
+
961
+ .h-8 {
962
+ height: 2rem;
963
+ }
964
+
965
+ .h-9 {
966
+ height: 2.25rem;
967
+ }
968
+
969
+ .h-\[1\.5px\] {
970
+ height: 1.5px;
971
+ }
972
+
973
+ .h-\[27px\] {
974
+ height: 27px;
975
+ }
976
+
977
+ .h-\[377px\] {
978
+ height: 377px;
979
+ }
980
+
981
+ .h-\[55px\] {
982
+ height: 55px;
983
+ }
984
+
985
+ .h-full {
986
+ height: 100%;
987
+ }
988
+
989
+ .h-px {
990
+ height: 1px;
991
+ }
992
+
993
+ .h-\[21px\] {
994
+ height: 21px;
995
+ }
996
+
997
+ .max-h-96 {
998
+ max-height: 24rem;
999
+ }
1000
+
1001
+ .max-h-screen {
1002
+ max-height: 100vh;
1003
+ }
1004
+
1005
+ .min-h-0 {
1006
+ min-height: 0px;
1007
+ }
1008
+
1009
+ .min-h-\[55px\] {
1010
+ min-height: 55px;
1011
+ }
1012
+
1013
+ .min-h-\[80px\] {
1014
+ min-height: 80px;
1015
+ }
1016
+
1017
+ .min-h-screen {
1018
+ min-height: 100vh;
1019
+ }
1020
+
1021
+ .w-10 {
1022
+ width: 2.5rem;
1023
+ }
1024
+
1025
+ .w-11 {
1026
+ width: 2.75rem;
1027
+ }
1028
+
1029
+ .w-2\.5 {
1030
+ width: 0.625rem;
1031
+ }
1032
+
1033
+ .w-3\.5 {
1034
+ width: 0.875rem;
1035
+ }
1036
+
1037
+ .w-3\/4 {
1038
+ width: 75%;
1039
+ }
1040
+
1041
+ .w-4 {
1042
+ width: 1rem;
1043
+ }
1044
+
1045
+ .w-48 {
1046
+ width: 12rem;
1047
+ }
1048
+
1049
+ .w-5 {
1050
+ width: 1.25rem;
1051
+ }
1052
+
1053
+ .w-8 {
1054
+ width: 2rem;
1055
+ }
1056
+
1057
+ .w-9 {
1058
+ width: 2.25rem;
1059
+ }
1060
+
1061
+ .w-\[1\.5px\] {
1062
+ width: 1.5px;
1063
+ }
1064
+
1065
+ .w-full {
1066
+ width: 100%;
1067
+ }
1068
+
1069
+ .min-w-0 {
1070
+ min-width: 0px;
1071
+ }
1072
+
1073
+ .min-w-\[8rem\] {
1074
+ min-width: 8rem;
1075
+ }
1076
+
1077
+ .max-w-lg {
1078
+ max-width: 32rem;
1079
+ }
1080
+
1081
+ .max-w-screen-md {
1082
+ max-width: 768px;
1083
+ }
1084
+
1085
+ .flex-1 {
1086
+ flex: 1 1 0%;
1087
+ }
1088
+
1089
+ .shrink-0 {
1090
+ flex-shrink: 0;
1091
+ }
1092
+
1093
+ .grow {
1094
+ flex-grow: 1;
1095
+ }
1096
+
1097
+ .grow-0 {
1098
+ flex-grow: 0;
1099
+ }
1100
+
1101
+ .basis-full {
1102
+ flex-basis: 100%;
1103
+ }
1104
+
1105
+ .caption-bottom {
1106
+ caption-side: bottom;
1107
+ }
1108
+
1109
+ .-translate-x-1\/2 {
1110
+ --tw-translate-x: -50%;
1111
+ 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));
1112
+ }
1113
+
1114
+ .-translate-y-1\/2 {
1115
+ --tw-translate-y: -50%;
1116
+ 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));
1117
+ }
1118
+
1119
+ .translate-x-\[-50\%\] {
1120
+ --tw-translate-x: -50%;
1121
+ 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));
1122
+ }
1123
+
1124
+ .translate-y-\[-50\%\] {
1125
+ --tw-translate-y: -50%;
1126
+ 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));
1127
+ }
1128
+
1129
+ .rotate-90 {
1130
+ --tw-rotate: 90deg;
1131
+ 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));
1132
+ }
1133
+
1134
+ .transform {
1135
+ 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));
1136
+ }
1137
+
1138
+ @keyframes pulse {
1139
+ 50% {
1140
+ opacity: .5;
1141
+ }
1142
+ }
1143
+
1144
+ .animate-pulse {
1145
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1146
+ }
1147
+
1148
+ @keyframes spin {
1149
+ to {
1150
+ transform: rotate(360deg);
1151
+ }
1152
+ }
1153
+
1154
+ .animate-spin {
1155
+ animation: spin 1s linear infinite;
1156
+ }
1157
+
1158
+ .cursor-default {
1159
+ cursor: default;
1160
+ }
1161
+
1162
+ .cursor-pointer {
1163
+ cursor: pointer;
1164
+ }
1165
+
1166
+ .touch-none {
1167
+ touch-action: none;
1168
+ }
1169
+
1170
+ .select-none {
1171
+ -webkit-user-select: none;
1172
+ -moz-user-select: none;
1173
+ user-select: none;
1174
+ }
1175
+
1176
+ .resize {
1177
+ resize: both;
1178
+ }
1179
+
1180
+ .grid-flow-col {
1181
+ grid-auto-flow: column;
1182
+ }
1183
+
1184
+ .grid-cols-1 {
1185
+ grid-template-columns: repeat(1, minmax(0, 1fr));
1186
+ }
1187
+
1188
+ .grid-rows-\[1fr_1fr\] {
1189
+ grid-template-rows: 1fr 1fr;
1190
+ }
1191
+
1192
+ .grid-rows-\[auto_0px\] {
1193
+ grid-template-rows: auto 0px;
1194
+ }
1195
+
1196
+ .grid-rows-\[auto_1fr_auto\] {
1197
+ grid-template-rows: auto 1fr auto;
1198
+ }
1199
+
1200
+ .grid-rows-\[auto_auto_1fr\] {
1201
+ grid-template-rows: auto auto 1fr;
1202
+ }
1203
+
1204
+ .grid-rows-\[minmax\(0\2c 55px\)_minmax\(0\2c 1fr\)\] {
1205
+ grid-template-rows: minmax(0,55px) minmax(0,1fr);
1206
+ }
1207
+
1208
+ .flex-col {
1209
+ flex-direction: column;
1210
+ }
1211
+
1212
+ .flex-col-reverse {
1213
+ flex-direction: column-reverse;
1214
+ }
1215
+
1216
+ .flex-wrap {
1217
+ flex-wrap: wrap;
1218
+ }
1219
+
1220
+ .content-center {
1221
+ align-content: center;
1222
+ }
1223
+
1224
+ .content-start {
1225
+ align-content: flex-start;
1226
+ }
1227
+
1228
+ .items-end {
1229
+ align-items: flex-end;
1230
+ }
1231
+
1232
+ .items-center {
1233
+ align-items: center;
1234
+ }
1235
+
1236
+ .justify-end {
1237
+ justify-content: flex-end;
1238
+ }
1239
+
1240
+ .justify-center {
1241
+ justify-content: center;
1242
+ }
1243
+
1244
+ .justify-between {
1245
+ justify-content: space-between;
1246
+ }
1247
+
1248
+ .justify-items-center {
1249
+ justify-items: center;
1250
+ }
1251
+
1252
+ .gap-1\.5 {
1253
+ gap: 0.375rem;
1254
+ }
1255
+
1256
+ .gap-2 {
1257
+ gap: 0.5rem;
1258
+ }
1259
+
1260
+ .gap-3 {
1261
+ gap: 0.75rem;
1262
+ }
1263
+
1264
+ .gap-4 {
1265
+ gap: 1rem;
1266
+ }
1267
+
1268
+ .gap-5 {
1269
+ gap: 1.25rem;
1270
+ }
1271
+
1272
+ .gap-8 {
1273
+ gap: 2rem;
1274
+ }
1275
+
1276
+ .gap-\[3px\] {
1277
+ gap: 3px;
1278
+ }
1279
+
1280
+ .gap-px {
1281
+ gap: 1px;
1282
+ }
1283
+
1284
+ .gap-x-1\.5 {
1285
+ -moz-column-gap: 0.375rem;
1286
+ column-gap: 0.375rem;
1287
+ }
1288
+
1289
+ .gap-x-3 {
1290
+ -moz-column-gap: 0.75rem;
1291
+ column-gap: 0.75rem;
1292
+ }
1293
+
1294
+ .gap-x-5 {
1295
+ -moz-column-gap: 1.25rem;
1296
+ column-gap: 1.25rem;
1297
+ }
1298
+
1299
+ .gap-y-3 {
1300
+ row-gap: 0.75rem;
1301
+ }
1302
+
1303
+ .gap-y-8 {
1304
+ row-gap: 2rem;
1305
+ }
1306
+
1307
+ .space-x-1 > :not([hidden]) ~ :not([hidden]) {
1308
+ --tw-space-x-reverse: 0;
1309
+ margin-right: calc(0.25rem * var(--tw-space-x-reverse));
1310
+ margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
1311
+ }
1312
+
1313
+ .space-x-2 > :not([hidden]) ~ :not([hidden]) {
1314
+ --tw-space-x-reverse: 0;
1315
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
1316
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
1317
+ }
1318
+
1319
+ .space-x-4 > :not([hidden]) ~ :not([hidden]) {
1320
+ --tw-space-x-reverse: 0;
1321
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
1322
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
1323
+ }
1324
+
1325
+ .space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
1326
+ --tw-space-y-reverse: 0;
1327
+ margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
1328
+ margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
1329
+ }
1330
+
1331
+ .space-y-2 > :not([hidden]) ~ :not([hidden]) {
1332
+ --tw-space-y-reverse: 0;
1333
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
1334
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
1335
+ }
1336
+
1337
+ .space-x-8 > :not([hidden]) ~ :not([hidden]) {
1338
+ --tw-space-x-reverse: 0;
1339
+ margin-right: calc(2rem * var(--tw-space-x-reverse));
1340
+ margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
1341
+ }
1342
+
1343
+ .self-stretch {
1344
+ align-self: stretch;
1345
+ }
1346
+
1347
+ .overflow-auto {
1348
+ overflow: auto;
1349
+ }
1350
+
1351
+ .overflow-hidden {
1352
+ overflow: hidden;
1353
+ }
1354
+
1355
+ .whitespace-nowrap {
1356
+ white-space: nowrap;
1357
+ }
1358
+
1359
+ .break-words {
1360
+ overflow-wrap: break-word;
1361
+ }
1362
+
1363
+ .\!rounded-full {
1364
+ border-radius: 9999px !important;
1365
+ }
1366
+
1367
+ .rounded-\[inherit\] {
1368
+ border-radius: inherit;
1369
+ }
1370
+
1371
+ .rounded-full {
1372
+ border-radius: 9999px;
1373
+ }
1374
+
1375
+ .rounded-lg {
1376
+ border-radius: var(--radius);
1377
+ }
1378
+
1379
+ .rounded-md {
1380
+ border-radius: calc(var(--radius) - 2px);
1381
+ }
1382
+
1383
+ .rounded-none {
1384
+ border-radius: 0px;
1385
+ }
1386
+
1387
+ .rounded-sm {
1388
+ border-radius: calc(var(--radius) - 4px);
1389
+ }
1390
+
1391
+ .border {
1392
+ border-width: 1px;
1393
+ }
1394
+
1395
+ .border-2 {
1396
+ border-width: 2px;
1397
+ }
1398
+
1399
+ .border-b {
1400
+ border-bottom-width: 1px;
1401
+ }
1402
+
1403
+ .border-l {
1404
+ border-left-width: 1px;
1405
+ }
1406
+
1407
+ .border-r {
1408
+ border-right-width: 1px;
1409
+ }
1410
+
1411
+ .border-t {
1412
+ border-top-width: 1px;
1413
+ }
1414
+
1415
+ .border-dashed {
1416
+ border-style: dashed;
1417
+ }
1418
+
1419
+ .border-destructive {
1420
+ border-color: hsl(var(--destructive));
1421
+ }
1422
+
1423
+ .border-destructive\/50 {
1424
+ border-color: hsl(var(--destructive) / 0.5);
1425
+ }
1426
+
1427
+ .border-input {
1428
+ border-color: hsl(var(--input));
1429
+ }
1430
+
1431
+ .border-primary {
1432
+ border-color: hsl(var(--primary));
1433
+ }
1434
+
1435
+ .border-transparent {
1436
+ border-color: transparent;
1437
+ }
1438
+
1439
+ .border-l-transparent {
1440
+ border-left-color: transparent;
1441
+ }
1442
+
1443
+ .border-t-transparent {
1444
+ border-top-color: transparent;
1445
+ }
1446
+
1447
+ .bg-background {
1448
+ background-color: hsl(var(--background));
1449
+ }
1450
+
1451
+ .bg-black {
1452
+ --tw-bg-opacity: 1;
1453
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1454
+ }
1455
+
1456
+ .bg-black\/80 {
1457
+ background-color: rgb(0 0 0 / 0.8);
1458
+ }
1459
+
1460
+ .bg-blue-500 {
1461
+ --tw-bg-opacity: 1;
1462
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
1463
+ }
1464
+
1465
+ .bg-border {
1466
+ background-color: hsl(var(--border));
1467
+ }
1468
+
1469
+ .bg-card {
1470
+ background-color: hsl(var(--card));
1471
+ }
1472
+
1473
+ .bg-destructive {
1474
+ background-color: hsl(var(--destructive));
1475
+ }
1476
+
1477
+ .bg-input {
1478
+ background-color: hsl(var(--input));
1479
+ }
1480
+
1481
+ .bg-muted {
1482
+ background-color: hsl(var(--muted));
1483
+ }
1484
+
1485
+ .bg-muted\/50 {
1486
+ background-color: hsl(var(--muted) / 0.5);
1487
+ }
1488
+
1489
+ .bg-popover {
1490
+ background-color: hsl(var(--popover));
1491
+ }
1492
+
1493
+ .bg-primary {
1494
+ background-color: hsl(var(--primary));
1495
+ }
1496
+
1497
+ .bg-red-300 {
1498
+ --tw-bg-opacity: 1;
1499
+ background-color: rgb(252 165 165 / var(--tw-bg-opacity));
1500
+ }
1501
+
1502
+ .bg-red-500 {
1503
+ --tw-bg-opacity: 1;
1504
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
1505
+ }
1506
+
1507
+ .bg-secondary {
1508
+ background-color: hsl(var(--secondary));
1509
+ }
1510
+
1511
+ .bg-white {
1512
+ --tw-bg-opacity: 1;
1513
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1514
+ }
1515
+
1516
+ .bg-\[\#0A66C2\] {
1517
+ --tw-bg-opacity: 1;
1518
+ background-color: rgb(10 102 194 / var(--tw-bg-opacity));
1519
+ }
1520
+
1521
+ .bg-gradient-to-r {
1522
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
1523
+ }
1524
+
1525
+ .bg-gradient-to-t {
1526
+ background-image: linear-gradient(to top, var(--tw-gradient-stops));
1527
+ }
1528
+
1529
+ .from-\[\#fcfcfd\] {
1530
+ --tw-gradient-from: #fcfcfd var(--tw-gradient-from-position);
1531
+ --tw-gradient-to: rgb(252 252 253 / 0) var(--tw-gradient-to-position);
1532
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1533
+ }
1534
+
1535
+ .from-black {
1536
+ --tw-gradient-from: #000 var(--tw-gradient-from-position);
1537
+ --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
1538
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1539
+ }
1540
+
1541
+ .to-slate-700 {
1542
+ --tw-gradient-to: #334155 var(--tw-gradient-to-position);
1543
+ }
1544
+
1545
+ .bg-clip-text {
1546
+ -webkit-background-clip: text;
1547
+ background-clip: text;
1548
+ }
1549
+
1550
+ .fill-current {
1551
+ fill: currentColor;
1552
+ }
1553
+
1554
+ .object-contain {
1555
+ -o-object-fit: contain;
1556
+ object-fit: contain;
1557
+ }
1558
+
1559
+ .p-1 {
1560
+ padding: 0.25rem;
1561
+ }
1562
+
1563
+ .p-10 {
1564
+ padding: 2.5rem;
1565
+ }
1566
+
1567
+ .p-2 {
1568
+ padding: 0.5rem;
1569
+ }
1570
+
1571
+ .p-3 {
1572
+ padding: 0.75rem;
1573
+ }
1574
+
1575
+ .p-4 {
1576
+ padding: 1rem;
1577
+ }
1578
+
1579
+ .p-5 {
1580
+ padding: 1.25rem;
1581
+ }
1582
+
1583
+ .p-6 {
1584
+ padding: 1.5rem;
1585
+ }
1586
+
1587
+ .p-8 {
1588
+ padding: 2rem;
1589
+ }
1590
+
1591
+ .p-\[1px\] {
1592
+ padding: 1px;
1593
+ }
1594
+
1595
+ .px-2 {
1596
+ padding-left: 0.5rem;
1597
+ padding-right: 0.5rem;
1598
+ }
1599
+
1600
+ .px-2\.5 {
1601
+ padding-left: 0.625rem;
1602
+ padding-right: 0.625rem;
1603
+ }
1604
+
1605
+ .px-3 {
1606
+ padding-left: 0.75rem;
1607
+ padding-right: 0.75rem;
1608
+ }
1609
+
1610
+ .px-4 {
1611
+ padding-left: 1rem;
1612
+ padding-right: 1rem;
1613
+ }
1614
+
1615
+ .px-5 {
1616
+ padding-left: 1.25rem;
1617
+ padding-right: 1.25rem;
1618
+ }
1619
+
1620
+ .px-8 {
1621
+ padding-left: 2rem;
1622
+ padding-right: 2rem;
1623
+ }
1624
+
1625
+ .py-0\.5 {
1626
+ padding-top: 0.125rem;
1627
+ padding-bottom: 0.125rem;
1628
+ }
1629
+
1630
+ .py-1 {
1631
+ padding-top: 0.25rem;
1632
+ padding-bottom: 0.25rem;
1633
+ }
1634
+
1635
+ .py-1\.5 {
1636
+ padding-top: 0.375rem;
1637
+ padding-bottom: 0.375rem;
1638
+ }
1639
+
1640
+ .py-2 {
1641
+ padding-top: 0.5rem;
1642
+ padding-bottom: 0.5rem;
1643
+ }
1644
+
1645
+ .py-4 {
1646
+ padding-top: 1rem;
1647
+ padding-bottom: 1rem;
1648
+ }
1649
+
1650
+ .py-5 {
1651
+ padding-top: 1.25rem;
1652
+ padding-bottom: 1.25rem;
1653
+ }
1654
+
1655
+ .pb-4 {
1656
+ padding-bottom: 1rem;
1657
+ }
1658
+
1659
+ .pl-10 {
1660
+ padding-left: 2.5rem;
1661
+ }
1662
+
1663
+ .pl-4 {
1664
+ padding-left: 1rem;
1665
+ }
1666
+
1667
+ .pl-8 {
1668
+ padding-left: 2rem;
1669
+ }
1670
+
1671
+ .pr-2 {
1672
+ padding-right: 0.5rem;
1673
+ }
1674
+
1675
+ .pr-8 {
1676
+ padding-right: 2rem;
1677
+ }
1678
+
1679
+ .pt-0 {
1680
+ padding-top: 0px;
1681
+ }
1682
+
1683
+ .pt-4 {
1684
+ padding-top: 1rem;
1685
+ }
1686
+
1687
+ .pt-\[7\%\] {
1688
+ padding-top: 7%;
1689
+ }
1690
+
1691
+ .text-left {
1692
+ text-align: left;
1693
+ }
1694
+
1695
+ .text-center {
1696
+ text-align: center;
1697
+ }
1698
+
1699
+ .align-middle {
1700
+ vertical-align: middle;
1701
+ }
1702
+
1703
+ .font-mono {
1704
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1705
+ }
1706
+
1707
+ .text-2xl {
1708
+ font-size: 1.5rem;
1709
+ line-height: 2rem;
1710
+ }
1711
+
1712
+ .text-3xl {
1713
+ font-size: 1.875rem;
1714
+ line-height: 2.25rem;
1715
+ }
1716
+
1717
+ .text-5xl {
1718
+ font-size: 3rem;
1719
+ line-height: 1;
1720
+ }
1721
+
1722
+ .text-base {
1723
+ font-size: 1rem;
1724
+ line-height: 1.5rem;
1725
+ }
1726
+
1727
+ .text-lg {
1728
+ font-size: 1.125rem;
1729
+ line-height: 1.75rem;
1730
+ }
1731
+
1732
+ .text-sm {
1733
+ font-size: 0.875rem;
1734
+ line-height: 1.25rem;
1735
+ }
1736
+
1737
+ .text-xl {
1738
+ font-size: 1.25rem;
1739
+ line-height: 1.75rem;
1740
+ }
1741
+
1742
+ .text-xs {
1743
+ font-size: 0.75rem;
1744
+ line-height: 1rem;
1745
+ }
1746
+
1747
+ .font-bold {
1748
+ font-weight: 700;
1749
+ }
1750
+
1751
+ .font-medium {
1752
+ font-weight: 500;
1753
+ }
1754
+
1755
+ .font-normal {
1756
+ font-weight: 400;
1757
+ }
1758
+
1759
+ .font-semibold {
1760
+ font-weight: 600;
1761
+ }
1762
+
1763
+ .capitalize {
1764
+ text-transform: capitalize;
1765
+ }
1766
+
1767
+ .leading-none {
1768
+ line-height: 1;
1769
+ }
1770
+
1771
+ .tracking-tight {
1772
+ letter-spacing: -0.025em;
1773
+ }
1774
+
1775
+ .tracking-wide {
1776
+ letter-spacing: 0.025em;
1777
+ }
1778
+
1779
+ .text-card-foreground {
1780
+ color: hsl(var(--card-foreground));
1781
+ }
1782
+
1783
+ .text-current {
1784
+ color: currentColor;
1785
+ }
1786
+
1787
+ .text-destructive {
1788
+ color: hsl(var(--destructive));
1789
+ }
1790
+
1791
+ .text-destructive-foreground {
1792
+ color: hsl(var(--destructive-foreground));
1793
+ }
1794
+
1795
+ .text-foreground {
1796
+ color: hsl(var(--foreground));
1797
+ }
1798
+
1799
+ .text-foreground\/50 {
1800
+ color: hsl(var(--foreground) / 0.5);
1801
+ }
1802
+
1803
+ .text-gray-800 {
1804
+ --tw-text-opacity: 1;
1805
+ color: rgb(31 41 55 / var(--tw-text-opacity));
1806
+ }
1807
+
1808
+ .text-gray-900 {
1809
+ --tw-text-opacity: 1;
1810
+ color: rgb(17 24 39 / var(--tw-text-opacity));
1811
+ }
1812
+
1813
+ .text-muted-foreground {
1814
+ color: hsl(var(--muted-foreground));
1815
+ }
1816
+
1817
+ .text-popover-foreground {
1818
+ color: hsl(var(--popover-foreground));
1819
+ }
1820
+
1821
+ .text-primary {
1822
+ color: hsl(var(--primary));
1823
+ }
1824
+
1825
+ .text-primary-foreground {
1826
+ color: hsl(var(--primary-foreground));
1827
+ }
1828
+
1829
+ .text-secondary-foreground {
1830
+ color: hsl(var(--secondary-foreground));
1831
+ }
1832
+
1833
+ .text-transparent {
1834
+ color: transparent;
1835
+ }
1836
+
1837
+ .text-white {
1838
+ --tw-text-opacity: 1;
1839
+ color: rgb(255 255 255 / var(--tw-text-opacity));
1840
+ }
1841
+
1842
+ .no-underline {
1843
+ text-decoration-line: none;
1844
+ }
1845
+
1846
+ .underline-offset-4 {
1847
+ text-underline-offset: 4px;
1848
+ }
1849
+
1850
+ .antialiased {
1851
+ -webkit-font-smoothing: antialiased;
1852
+ -moz-osx-font-smoothing: grayscale;
1853
+ }
1854
+
1855
+ .opacity-0 {
1856
+ opacity: 0;
1857
+ }
1858
+
1859
+ .opacity-50 {
1860
+ opacity: 0.5;
1861
+ }
1862
+
1863
+ .opacity-70 {
1864
+ opacity: 0.7;
1865
+ }
1866
+
1867
+ .opacity-90 {
1868
+ opacity: 0.9;
1869
+ }
1870
+
1871
+ .shadow-lg {
1872
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
1873
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1874
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1875
+ }
1876
+
1877
+ .shadow-md {
1878
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
1879
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
1880
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1881
+ }
1882
+
1883
+ .shadow-sm {
1884
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
1885
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
1886
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1887
+ }
1888
+
1889
+ .outline-none {
1890
+ outline: 2px solid transparent;
1891
+ outline-offset: 2px;
1892
+ }
1893
+
1894
+ .outline {
1895
+ outline-style: solid;
1896
+ }
1897
+
1898
+ .ring {
1899
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1900
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1901
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1902
+ }
1903
+
1904
+ .ring-0 {
1905
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1906
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1907
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1908
+ }
1909
+
1910
+ .ring-1 {
1911
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1912
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1913
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1914
+ }
1915
+
1916
+ .ring-black {
1917
+ --tw-ring-opacity: 1;
1918
+ --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity));
1919
+ }
1920
+
1921
+ .ring-opacity-5 {
1922
+ --tw-ring-opacity: 0.05;
1923
+ }
1924
+
1925
+ .ring-offset-background {
1926
+ --tw-ring-offset-color: hsl(var(--background));
1927
+ }
1928
+
1929
+ .ring-offset-transparent {
1930
+ --tw-ring-offset-color: transparent;
1931
+ }
1932
+
1933
+ .blur {
1934
+ --tw-blur: blur(8px);
1935
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1936
+ }
1937
+
1938
+ .filter {
1939
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1940
+ }
1941
+
1942
+ .backdrop-filter {
1943
+ -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);
1944
+ 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);
1945
+ }
1946
+
1947
+ .transition {
1948
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
1949
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1950
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
1951
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1952
+ transition-duration: 150ms;
1953
+ }
1954
+
1955
+ .transition-all {
1956
+ transition-property: all;
1957
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1958
+ transition-duration: 150ms;
1959
+ }
1960
+
1961
+ .transition-colors {
1962
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
1963
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1964
+ transition-duration: 150ms;
1965
+ }
1966
+
1967
+ .transition-opacity {
1968
+ transition-property: opacity;
1969
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1970
+ transition-duration: 150ms;
1971
+ }
1972
+
1973
+ .transition-transform {
1974
+ transition-property: transform;
1975
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1976
+ transition-duration: 150ms;
1977
+ }
1978
+
1979
+ .duration-200 {
1980
+ transition-duration: 200ms;
1981
+ }
1982
+
1983
+ .duration-300 {
1984
+ transition-duration: 300ms;
1985
+ }
1986
+
1987
+ .ease-in-out {
1988
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1989
+ }
1990
+
1991
+ .ease-out {
1992
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1993
+ }
1994
+
1995
+ @keyframes enter {
1996
+ from {
1997
+ opacity: var(--tw-enter-opacity, 1);
1998
+ transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0));
1999
+ }
2000
+ }
2001
+
2002
+ @keyframes exit {
2003
+ to {
2004
+ opacity: var(--tw-exit-opacity, 1);
2005
+ transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0));
2006
+ }
2007
+ }
2008
+
2009
+ .animate-in {
2010
+ animation-name: enter;
2011
+ animation-duration: 150ms;
2012
+ --tw-enter-opacity: initial;
2013
+ --tw-enter-scale: initial;
2014
+ --tw-enter-rotate: initial;
2015
+ --tw-enter-translate-x: initial;
2016
+ --tw-enter-translate-y: initial;
2017
+ }
2018
+
2019
+ .animate-out {
2020
+ animation-name: exit;
2021
+ animation-duration: 150ms;
2022
+ --tw-exit-opacity: initial;
2023
+ --tw-exit-scale: initial;
2024
+ --tw-exit-rotate: initial;
2025
+ --tw-exit-translate-x: initial;
2026
+ --tw-exit-translate-y: initial;
2027
+ }
2028
+
2029
+ .fade-in {
2030
+ --tw-enter-opacity: 0;
2031
+ }
2032
+
2033
+ .fade-out {
2034
+ --tw-exit-opacity: 0;
2035
+ }
2036
+
2037
+ .zoom-in {
2038
+ --tw-enter-scale: 0;
2039
+ }
2040
+
2041
+ .zoom-out {
2042
+ --tw-exit-scale: 0;
2043
+ }
2044
+
2045
+ .spin-in {
2046
+ --tw-enter-rotate: 30deg;
2047
+ }
2048
+
2049
+ .spin-out {
2050
+ --tw-exit-rotate: 30deg;
2051
+ }
2052
+
2053
+ .slide-in-from-bottom {
2054
+ --tw-enter-translate-y: 100%;
2055
+ }
2056
+
2057
+ .slide-in-from-bottom-2 {
2058
+ --tw-enter-translate-y: 0.5rem;
2059
+ }
2060
+
2061
+ .slide-in-from-left {
2062
+ --tw-enter-translate-x: -100%;
2063
+ }
2064
+
2065
+ .slide-in-from-right {
2066
+ --tw-enter-translate-x: 100%;
2067
+ }
2068
+
2069
+ .slide-in-from-top {
2070
+ --tw-enter-translate-y: -100%;
2071
+ }
2072
+
2073
+ .slide-in-from-top-2 {
2074
+ --tw-enter-translate-y: -0.5rem;
2075
+ }
2076
+
2077
+ .slide-out-to-bottom {
2078
+ --tw-exit-translate-y: 100%;
2079
+ }
2080
+
2081
+ .slide-out-to-left {
2082
+ --tw-exit-translate-x: -100%;
2083
+ }
2084
+
2085
+ .slide-out-to-right {
2086
+ --tw-exit-translate-x: 100%;
2087
+ }
2088
+
2089
+ .slide-out-to-top {
2090
+ --tw-exit-translate-y: -100%;
2091
+ }
2092
+
2093
+ .duration-200 {
2094
+ animation-duration: 200ms;
2095
+ }
2096
+
2097
+ .duration-300 {
2098
+ animation-duration: 300ms;
2099
+ }
2100
+
2101
+ .ease-in-out {
2102
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2103
+ }
2104
+
2105
+ .ease-out {
2106
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
2107
+ }
2108
+
2109
+ .running {
2110
+ animation-play-state: running;
2111
+ }
2112
+
2113
+ .paused {
2114
+ animation-play-state: paused;
2115
+ }
2116
+
2117
+ /* Hide scrollbar for Chrome, Safari and Opera */
2118
+
2119
+ .no-scrollbar::-webkit-scrollbar {
2120
+ display: none;
2121
+ }
2122
+
2123
+ /* Hide scrollbar for IE, Edge and Firefox */
2124
+
2125
+ .no-scrollbar {
2126
+ -webkit-overflow-scrolling: touch;
2127
+ -ms-overflow-style: none;
2128
+ /* IE and Edge */
2129
+ scrollbar-width: none;
2130
+ /* Firefox */
2131
+ }
2132
+
2133
+ @keyframes slideInFromTop {
2134
+ from {
2135
+ transform: translateY(-100%);
2136
+ }
2137
+
2138
+ to {
2139
+ transform: translateY(0);
2140
+ }
2141
+ }
2142
+
2143
+ @keyframes slideInFromBottom {
2144
+ from {
2145
+ transform: translateY(100%);
2146
+ }
2147
+
2148
+ to {
2149
+ transform: translateY(0);
2150
+ }
2151
+ }
2152
+
2153
+ .toast {
2154
+ animation-duration: 0.2s;
2155
+ animation-fill-mode: forwards;
2156
+ }
2157
+
2158
+ @media (max-width: 640px) {
2159
+ .toast {
2160
+ animation-name: slideInFromTop;
2161
+ }
2162
+ }
2163
+
2164
+ @media (min-width: 641px) {
2165
+ .toast {
2166
+ animation-name: slideInFromBottom;
2167
+ }
2168
+ }
2169
+
2170
+ @keyframes fade-in {
2171
+ from {
2172
+ opacity: 0;
2173
+ }
2174
+
2175
+ to {
2176
+ opacity: 1;
2177
+ }
2178
+ }
2179
+
2180
+ @keyframes slide-up {
2181
+ from {
2182
+ transform: translateY(20px);
2183
+ opacity: 0;
2184
+ }
2185
+
2186
+ to {
2187
+ transform: translateY(0);
2188
+ opacity: 1;
2189
+ }
2190
+ }
2191
+
2192
+ .animate-fade-in {
2193
+ animation: fade-in 1s ease-out forwards;
2194
+ }
2195
+
2196
+ .animate-slide-up {
2197
+ animation: slide-up 1s ease-out forwards;
2198
+ }
2199
+
2200
+ .sim-map-button.active {
2201
+ background-color: #61D790;
2202
+ color: #2E2F27;
2203
+ &:hover {
2204
+ background-color: #61D790;
2205
+ }
2206
+ }
2207
+
2208
+ .text-highlight strong {
2209
+ color: black;
2210
+ .dark & {
2211
+ color: white;
2212
+ }
2213
+ }
2214
+
2215
+ .tokens-button {
2216
+ background-color: #B7E2F1;
2217
+ color: #2E2F27;
2218
+ }
2219
+
2220
+ .overlay-image {
2221
+ opacity: 0.5;
2222
+ position: absolute;
2223
+ top: 0;
2224
+ left: 0;
2225
+ width: 100%;
2226
+ height: 100%;
2227
+ z-index: 10;
2228
+ }
2229
+
2230
+ header {
2231
+ grid-column: 1/-1;
2232
+ }
2233
+
2234
+ body {
2235
+ &[data-is-home="true"] {
2236
+ background: radial-gradient(circle at 50% 100%, #fcfcfd, #fcfcfd, #fdfdfe, #fdfdfe, #fefefe, #fefefe, #ffffff, #ffffff);
2237
+ .dark & {
2238
+ background: radial-gradient(circle at 50% 50%, #272a2d, #242629, #212326, #1e1f22, #1b1c1e, #18181b, #151517, #111113);
2239
+ }
2240
+ }
2241
+ }
2242
+
2243
+ main {
2244
+ overflow: auto;
2245
+ }
2246
+
2247
+ aside {
2248
+ overflow: auto;
2249
+ }
2250
+
2251
+ .scroll-container {
2252
+ padding-right: 10px;
2253
+ }
2254
+
2255
+ .question-message {
2256
+ background-color: #61D790;
2257
+ color: #2E2F27;
2258
+ }
2259
+
2260
+ @media (min-width: 768px) {
2261
+ .grid-image-text-columns {
2262
+ grid-column: span 2 / span 2;
2263
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2264
+ }
2265
+ }
2266
+
2267
+ .grid-image-column {
2268
+ grid-row: span 2 / span 2;
2269
+ display: grid;
2270
+ grid-template-rows: subgrid;
2271
+ align-content: flex-start;
2272
+ }
2273
+
2274
+ @media (min-width: 768px) {
2275
+ .md-grid-text-column {
2276
+ grid-row: span 2 / span 2;
2277
+ display: grid;
2278
+ grid-template-rows: subgrid;
2279
+ align-content: flex-start;
2280
+ }
2281
+ }
2282
+
2283
+ #search-input[aria-expanded="true"] {
2284
+ border-top: 1px solid hsl(var(--input));
2285
+ border-left: 1px solid hsl(var(--input));
2286
+ border-right: 1px solid hsl(var(--input));
2287
+ border-bottom: none;
2288
+ border-bottom-left-radius: 0;
2289
+ border-bottom-right-radius: 0;
2290
+ }
2291
+
2292
+ .awesomplete {
2293
+ width: 100%;
2294
+ }
2295
+
2296
+ .awesomplete > ul > :not([hidden]) ~ :not([hidden]) {
2297
+ --tw-space-y-reverse: 0;
2298
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
2299
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
2300
+ }
2301
+
2302
+ .awesomplete > ul {
2303
+ font-size: 0.875rem;
2304
+ line-height: 1.25rem;
2305
+ margin: 0;
2306
+ border-top: none;
2307
+ border-left: 1px solid hsl(var(--input));
2308
+ border-right: 1px solid hsl(var(--input));
2309
+ border-bottom: 1px solid hsl(var(--input));
2310
+ border-radius: 0 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px);
2311
+ background: white;
2312
+ .dark & {
2313
+ background: hsl(var(--background));
2314
+ }
2315
+ box-shadow: none;
2316
+ text-shadow: none;
2317
+ }
2318
+
2319
+ .awesomplete > ul:before {
2320
+ display: none;
2321
+ }
2322
+
2323
+ .awesomplete > ul > li:hover {
2324
+ background-color: #B7E2F1;
2325
+ color: #2E2F27;
2326
+ }
2327
+
2328
+ .awesomplete > ul > li[aria-selected="true"] {
2329
+ background-color: #B7E2F1;
2330
+ color: #2E2F27;
2331
+ }
2332
+
2333
+ .awesomplete mark {
2334
+ background-color: #61D790;
2335
+ color: #2E2F27;
2336
+ }
2337
+
2338
+ .awesomplete li:hover mark {
2339
+ background-color: #61D790;
2340
+ color: #2E2F27;
2341
+ }
2342
+
2343
+ .awesomplete li[aria-selected="true"] mark {
2344
+ background-color: #61D790;
2345
+ color: #2E2F27;
2346
+ }
2347
+
2348
+ :root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
2349
+ overflow: hidden;
2350
+ }
2351
+
2352
+ :root:has(.group[data-state="open"] .group-data-\[state\=open\]\:no-bg-scroll) {
2353
+ overflow: hidden;
2354
+ }
2355
+
2356
+ .file\:border-0::file-selector-button {
2357
+ border-width: 0px;
2358
+ }
2359
+
2360
+ .file\:bg-transparent::file-selector-button {
2361
+ background-color: transparent;
2362
+ }
2363
+
2364
+ .file\:text-sm::file-selector-button {
2365
+ font-size: 0.875rem;
2366
+ line-height: 1.25rem;
2367
+ }
2368
+
2369
+ .file\:font-medium::file-selector-button {
2370
+ font-weight: 500;
2371
+ }
2372
+
2373
+ .placeholder\:text-muted-foreground::-moz-placeholder {
2374
+ color: hsl(var(--muted-foreground));
2375
+ }
2376
+
2377
+ .placeholder\:text-muted-foreground::placeholder {
2378
+ color: hsl(var(--muted-foreground));
2379
+ }
2380
+
2381
+ .focus-within\:border-input:focus-within {
2382
+ border-color: hsl(var(--input));
2383
+ }
2384
+
2385
+ .focus-within\:outline-none:focus-within {
2386
+ outline: 2px solid transparent;
2387
+ outline-offset: 2px;
2388
+ }
2389
+
2390
+ .focus-within\:ring-2:focus-within {
2391
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2392
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2393
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2394
+ }
2395
+
2396
+ .focus-within\:ring-ring:focus-within {
2397
+ --tw-ring-color: hsl(var(--ring));
2398
+ }
2399
+
2400
+ .focus-within\:ring-offset-2:focus-within {
2401
+ --tw-ring-offset-width: 2px;
2402
+ }
2403
+
2404
+ .hover\:border-black:hover {
2405
+ --tw-border-opacity: 1;
2406
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
2407
+ }
2408
+
2409
+ .hover\:bg-accent:hover {
2410
+ background-color: hsl(var(--accent));
2411
+ }
2412
+
2413
+ .hover\:bg-destructive\/80:hover {
2414
+ background-color: hsl(var(--destructive) / 0.8);
2415
+ }
2416
+
2417
+ .hover\:bg-destructive\/90:hover {
2418
+ background-color: hsl(var(--destructive) / 0.9);
2419
+ }
2420
+
2421
+ .hover\:bg-muted\/50:hover {
2422
+ background-color: hsl(var(--muted) / 0.5);
2423
+ }
2424
+
2425
+ .hover\:bg-primary\/80:hover {
2426
+ background-color: hsl(var(--primary) / 0.8);
2427
+ }
2428
+
2429
+ .hover\:bg-primary\/90:hover {
2430
+ background-color: hsl(var(--primary) / 0.9);
2431
+ }
2432
+
2433
+ .hover\:bg-secondary\/80:hover {
2434
+ background-color: hsl(var(--secondary) / 0.8);
2435
+ }
2436
+
2437
+ .hover\:text-accent-foreground:hover {
2438
+ color: hsl(var(--accent-foreground));
2439
+ }
2440
+
2441
+ .hover\:text-foreground:hover {
2442
+ color: hsl(var(--foreground));
2443
+ }
2444
+
2445
+ .hover\:text-primary:hover {
2446
+ color: hsl(var(--primary));
2447
+ }
2448
+
2449
+ .hover\:underline:hover {
2450
+ text-decoration-line: underline;
2451
+ }
2452
+
2453
+ .hover\:opacity-100:hover {
2454
+ opacity: 1;
2455
+ }
2456
+
2457
+ .focus\:bg-accent:focus {
2458
+ background-color: hsl(var(--accent));
2459
+ }
2460
+
2461
+ .focus\:text-accent-foreground:focus {
2462
+ color: hsl(var(--accent-foreground));
2463
+ }
2464
+
2465
+ .focus\:opacity-100:focus {
2466
+ opacity: 1;
2467
+ }
2468
+
2469
+ .focus\:outline-none:focus {
2470
+ outline: 2px solid transparent;
2471
+ outline-offset: 2px;
2472
+ }
2473
+
2474
+ .focus\:ring-2:focus {
2475
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2476
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2477
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2478
+ }
2479
+
2480
+ .focus\:ring-ring:focus {
2481
+ --tw-ring-color: hsl(var(--ring));
2482
+ }
2483
+
2484
+ .focus\:ring-offset-2:focus {
2485
+ --tw-ring-offset-width: 2px;
2486
+ }
2487
+
2488
+ .focus-visible\:outline-none:focus-visible {
2489
+ outline: 2px solid transparent;
2490
+ outline-offset: 2px;
2491
+ }
2492
+
2493
+ .focus-visible\:ring-2:focus-visible {
2494
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2495
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2496
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2497
+ }
2498
+
2499
+ .focus-visible\:ring-ring:focus-visible {
2500
+ --tw-ring-color: hsl(var(--ring));
2501
+ }
2502
+
2503
+ .focus-visible\:ring-transparent:focus-visible {
2504
+ --tw-ring-color: transparent;
2505
+ }
2506
+
2507
+ .focus-visible\:ring-offset-2:focus-visible {
2508
+ --tw-ring-offset-width: 2px;
2509
+ }
2510
+
2511
+ .active\:ring:active {
2512
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2513
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2514
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2515
+ }
2516
+
2517
+ .disabled\:pointer-events-none:disabled {
2518
+ pointer-events: none;
2519
+ }
2520
+
2521
+ .disabled\:cursor-not-allowed:disabled {
2522
+ cursor: not-allowed;
2523
+ }
2524
+
2525
+ .disabled\:opacity-50:disabled {
2526
+ opacity: 0.5;
2527
+ }
2528
+
2529
+ .group:hover .group-hover\:opacity-100 {
2530
+ opacity: 1;
2531
+ }
2532
+
2533
+ .group.destructive .group-\[\.destructive\]\:text-red-300 {
2534
+ --tw-text-opacity: 1;
2535
+ color: rgb(252 165 165 / var(--tw-text-opacity));
2536
+ }
2537
+
2538
+ .group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover {
2539
+ --tw-text-opacity: 1;
2540
+ color: rgb(254 242 242 / var(--tw-text-opacity));
2541
+ }
2542
+
2543
+ .group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus {
2544
+ --tw-ring-opacity: 1;
2545
+ --tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity));
2546
+ }
2547
+
2548
+ .group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus {
2549
+ --tw-ring-offset-color: #dc2626;
2550
+ }
2551
+
2552
+ .peer:checked ~ .peer-checked\:bg-primary {
2553
+ background-color: hsl(var(--primary));
2554
+ }
2555
+
2556
+ .peer:checked ~ .peer-checked\:text-primary-foreground {
2557
+ color: hsl(var(--primary-foreground));
2558
+ }
2559
+
2560
+ .peer:disabled ~ .peer-disabled\:cursor-not-allowed {
2561
+ cursor: not-allowed;
2562
+ }
2563
+
2564
+ .peer:disabled ~ .peer-disabled\:opacity-50 {
2565
+ opacity: 0.5;
2566
+ }
2567
+
2568
+ .peer:disabled ~ .peer-disabled\:opacity-70 {
2569
+ opacity: 0.7;
2570
+ }
2571
+
2572
+ .data-\[disabled\]\:pointer-events-none[data-disabled] {
2573
+ pointer-events: none;
2574
+ }
2575
+
2576
+ .data-\[state\=active\]\:bg-background[data-state="active"] {
2577
+ background-color: hsl(var(--background));
2578
+ }
2579
+
2580
+ .data-\[state\=selected\]\:bg-muted[data-state="selected"] {
2581
+ background-color: hsl(var(--muted));
2582
+ }
2583
+
2584
+ .data-\[state\=active\]\:text-foreground[data-state="active"] {
2585
+ color: hsl(var(--foreground));
2586
+ }
2587
+
2588
+ .data-\[disabled\]\:opacity-50[data-disabled] {
2589
+ opacity: 0.5;
2590
+ }
2591
+
2592
+ .data-\[state\=active\]\:shadow-sm[data-state="active"] {
2593
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
2594
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
2595
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2596
+ }
2597
+
2598
+ .data-\[state\=open\]\:animate-in[data-state="open"] {
2599
+ animation-name: enter;
2600
+ animation-duration: 150ms;
2601
+ --tw-enter-opacity: initial;
2602
+ --tw-enter-scale: initial;
2603
+ --tw-enter-rotate: initial;
2604
+ --tw-enter-translate-x: initial;
2605
+ --tw-enter-translate-y: initial;
2606
+ }
2607
+
2608
+ .data-\[state\=closed\]\:animate-out[data-state="closed"] {
2609
+ animation-name: exit;
2610
+ animation-duration: 150ms;
2611
+ --tw-exit-opacity: initial;
2612
+ --tw-exit-scale: initial;
2613
+ --tw-exit-rotate: initial;
2614
+ --tw-exit-translate-x: initial;
2615
+ --tw-exit-translate-y: initial;
2616
+ }
2617
+
2618
+ .data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
2619
+ --tw-exit-opacity: 0;
2620
+ }
2621
+
2622
+ .data-\[state\=open\]\:fade-in-0[data-state="open"] {
2623
+ --tw-enter-opacity: 0;
2624
+ }
2625
+
2626
+ .data-\[popper-placement\=\'bottom-start\'\]\:slide-in-from-top-2[data-popper-placement='bottom-start'] {
2627
+ --tw-enter-translate-y: -0.5rem;
2628
+ }
2629
+
2630
+ .data-\[popper-placement\=\'top-start\'\]\:slide-in-from-bottom-2[data-popper-placement='top-start'] {
2631
+ --tw-enter-translate-y: 0.5rem;
2632
+ }
2633
+
2634
+ .group[data-checked="true"] .group-data-\[checked\=true\]\:flex {
2635
+ display: flex;
2636
+ }
2637
+
2638
+ .group[data-state="checked"] .group-data-\[state\=checked\]\:flex {
2639
+ display: flex;
2640
+ }
2641
+
2642
+ .group[data-state="open"] .group-data-\[state\=open\]\:rotate-180 {
2643
+ --tw-rotate: 180deg;
2644
+ 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));
2645
+ }
2646
+
2647
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:duration-300 {
2648
+ transition-duration: 300ms;
2649
+ }
2650
+
2651
+ .group[data-state="open"] .group-data-\[state\=open\]\:duration-500 {
2652
+ transition-duration: 500ms;
2653
+ }
2654
+
2655
+ .group[data-state="open"] .group-data-\[state\=open\]\:animate-in {
2656
+ animation-name: enter;
2657
+ animation-duration: 150ms;
2658
+ --tw-enter-opacity: initial;
2659
+ --tw-enter-scale: initial;
2660
+ --tw-enter-rotate: initial;
2661
+ --tw-enter-translate-x: initial;
2662
+ --tw-enter-translate-y: initial;
2663
+ }
2664
+
2665
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:animate-out {
2666
+ animation-name: exit;
2667
+ animation-duration: 150ms;
2668
+ --tw-exit-opacity: initial;
2669
+ --tw-exit-scale: initial;
2670
+ --tw-exit-rotate: initial;
2671
+ --tw-exit-translate-x: initial;
2672
+ --tw-exit-translate-y: initial;
2673
+ }
2674
+
2675
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:fade-out-0 {
2676
+ --tw-exit-opacity: 0;
2677
+ }
2678
+
2679
+ .group[data-state="open"] .group-data-\[state\=open\]\:fade-in-0 {
2680
+ --tw-enter-opacity: 0;
2681
+ }
2682
+
2683
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:zoom-out-95 {
2684
+ --tw-exit-scale: .95;
2685
+ }
2686
+
2687
+ .group[data-state="open"] .group-data-\[state\=open\]\:zoom-in-95 {
2688
+ --tw-enter-scale: .95;
2689
+ }
2690
+
2691
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-bottom {
2692
+ --tw-exit-translate-y: 100%;
2693
+ }
2694
+
2695
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-left {
2696
+ --tw-exit-translate-x: -100%;
2697
+ }
2698
+
2699
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-left-1\/2 {
2700
+ --tw-exit-translate-x: -50%;
2701
+ }
2702
+
2703
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-right {
2704
+ --tw-exit-translate-x: 100%;
2705
+ }
2706
+
2707
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-top {
2708
+ --tw-exit-translate-y: -100%;
2709
+ }
2710
+
2711
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-top-\[48\%\] {
2712
+ --tw-exit-translate-y: -48%;
2713
+ }
2714
+
2715
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-bottom {
2716
+ --tw-enter-translate-y: 100%;
2717
+ }
2718
+
2719
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-left {
2720
+ --tw-enter-translate-x: -100%;
2721
+ }
2722
+
2723
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-left-1\/2 {
2724
+ --tw-enter-translate-x: -50%;
2725
+ }
2726
+
2727
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-right {
2728
+ --tw-enter-translate-x: 100%;
2729
+ }
2730
+
2731
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-top {
2732
+ --tw-enter-translate-y: -100%;
2733
+ }
2734
+
2735
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-top-\[48\%\] {
2736
+ --tw-enter-translate-y: -48%;
2737
+ }
2738
+
2739
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:duration-300 {
2740
+ animation-duration: 300ms;
2741
+ }
2742
+
2743
+ .group[data-state="open"] .group-data-\[state\=open\]\:duration-500 {
2744
+ animation-duration: 500ms;
2745
+ }
2746
+
2747
+ .peer[data-state="closed"] ~ .peer-data-\[state\=closed\]\:animate-accordion-up {
2748
+ animation: accordion-up 0.2s ease-out;
2749
+ }
2750
+
2751
+ .peer[data-state="open"] ~ .peer-data-\[state\=open\]\:animate-accordion-down {
2752
+ animation: accordion-down 0.2s ease-out;
2753
+ }
2754
+
2755
+ @media (min-width: 640px) {
2756
+ .sm\:bottom-0 {
2757
+ bottom: 0px;
2758
+ }
2759
+
2760
+ .sm\:right-0 {
2761
+ right: 0px;
2762
+ }
2763
+
2764
+ .sm\:top-auto {
2765
+ top: auto;
2766
+ }
2767
+
2768
+ .sm\:flex {
2769
+ display: flex;
2770
+ }
2771
+
2772
+ .sm\:max-w-sm {
2773
+ max-width: 24rem;
2774
+ }
2775
+
2776
+ .sm\:flex-row {
2777
+ flex-direction: row;
2778
+ }
2779
+
2780
+ .sm\:flex-col {
2781
+ flex-direction: column;
2782
+ }
2783
+
2784
+ .sm\:justify-end {
2785
+ justify-content: flex-end;
2786
+ }
2787
+
2788
+ .sm\:gap-2\.5 {
2789
+ gap: 0.625rem;
2790
+ }
2791
+
2792
+ .sm\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
2793
+ --tw-space-x-reverse: 0;
2794
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
2795
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
2796
+ }
2797
+
2798
+ .sm\:rounded-lg {
2799
+ border-radius: var(--radius);
2800
+ }
2801
+
2802
+ .sm\:text-left {
2803
+ text-align: left;
2804
+ }
2805
+ }
2806
+
2807
+ @media (min-width: 768px) {
2808
+ .md\:block {
2809
+ display: block;
2810
+ }
2811
+
2812
+ .md\:max-w-\[420px\] {
2813
+ max-width: 420px;
2814
+ }
2815
+
2816
+ .md\:grid-cols-\[minmax\(0\2c _45fr\)_minmax\(0\2c _15fr\)\] {
2817
+ grid-template-columns: minmax(0, 45fr) minmax(0, 15fr);
2818
+ }
2819
+
2820
+ .md\:text-2xl {
2821
+ font-size: 1.5rem;
2822
+ line-height: 2rem;
2823
+ }
2824
+
2825
+ .md\:text-7xl {
2826
+ font-size: 4.5rem;
2827
+ line-height: 1;
2828
+ }
2829
+
2830
+ .md\:text-6xl {
2831
+ font-size: 3.75rem;
2832
+ line-height: 1;
2833
+ }
2834
+
2835
+ .md\:text-xl {
2836
+ font-size: 1.25rem;
2837
+ line-height: 1.75rem;
2838
+ }
2839
+
2840
+ .md\:tracking-wide {
2841
+ letter-spacing: 0.025em;
2842
+ }
2843
+
2844
+ .md\:tracking-wider {
2845
+ letter-spacing: 0.05em;
2846
+ }
2847
+ }
2848
+
2849
+ @media (min-width: 1024px) {
2850
+ .lg\:space-x-6 > :not([hidden]) ~ :not([hidden]) {
2851
+ --tw-space-x-reverse: 0;
2852
+ margin-right: calc(1.5rem * var(--tw-space-x-reverse));
2853
+ margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
2854
+ }
2855
+ }
2856
+
2857
+ @media (min-width: 1280px) {
2858
+ .xl\:grid-rows-\[1fr_2fr\] {
2859
+ grid-template-rows: 1fr 2fr;
2860
+ }
2861
+ }
2862
+
2863
+ .dark\:block:where(.dark, .dark *) {
2864
+ display: block;
2865
+ }
2866
+
2867
+ .dark\:flex:where(.dark, .dark *) {
2868
+ display: flex;
2869
+ }
2870
+
2871
+ .dark\:hidden:where(.dark, .dark *) {
2872
+ display: none;
2873
+ }
2874
+
2875
+ .dark\:border-destructive:where(.dark, .dark *) {
2876
+ border-color: hsl(var(--destructive));
2877
+ }
2878
+
2879
+ .dark\:bg-background:where(.dark, .dark *) {
2880
+ background-color: hsl(var(--background));
2881
+ }
2882
+
2883
+ .dark\:from-\[\#1c2024\]:where(.dark, .dark *) {
2884
+ --tw-gradient-from: #1c2024 var(--tw-gradient-from-position);
2885
+ --tw-gradient-to: rgb(28 32 36 / 0) var(--tw-gradient-to-position);
2886
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2887
+ }
2888
+
2889
+ .dark\:from-white:where(.dark, .dark *) {
2890
+ --tw-gradient-from: #fff var(--tw-gradient-from-position);
2891
+ --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
2892
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2893
+ }
2894
+
2895
+ .dark\:to-slate-300:where(.dark, .dark *) {
2896
+ --tw-gradient-to: #cbd5e1 var(--tw-gradient-to-position);
2897
+ }
2898
+
2899
+ .dark\:hover\:border-white:hover:where(.dark, .dark *) {
2900
+ --tw-border-opacity: 1;
2901
+ border-color: rgb(255 255 255 / var(--tw-border-opacity));
2902
+ }
2903
+
2904
+ .\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]) {
2905
+ padding-right: 0px;
2906
+ }
2907
+
2908
+ .\[\&\>span\]\:line-clamp-1>span {
2909
+ overflow: hidden;
2910
+ display: -webkit-box;
2911
+ -webkit-box-orient: vertical;
2912
+ -webkit-line-clamp: 1;
2913
+ }
2914
+
2915
+ .\[\&\>span\]\:translate-x-0>span {
2916
+ --tw-translate-x: 0px;
2917
+ 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));
2918
+ }
2919
+
2920
+ .peer:checked ~ .peer-checked\:\[\&\>span\]\:flex>span {
2921
+ display: flex;
2922
+ }
2923
+
2924
+ .peer:checked ~ .peer-checked\:\[\&\>span\]\:translate-x-5>span {
2925
+ --tw-translate-x: 1.25rem;
2926
+ 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));
2927
+ }
2928
+
2929
+ .\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div {
2930
+ --tw-translate-y: -3px;
2931
+ 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));
2932
+ }
2933
+
2934
+ .\[\&\>svg\]\:absolute>svg {
2935
+ position: absolute;
2936
+ }
2937
+
2938
+ .\[\&\>svg\]\:left-4>svg {
2939
+ left: 1rem;
2940
+ }
2941
+
2942
+ .\[\&\>svg\]\:top-4>svg {
2943
+ top: 1rem;
2944
+ }
2945
+
2946
+ .\[\&\>svg\]\:text-destructive>svg {
2947
+ color: hsl(var(--destructive));
2948
+ }
2949
+
2950
+ .\[\&\>svg\]\:text-foreground>svg {
2951
+ color: hsl(var(--foreground));
2952
+ }
2953
+
2954
+ .\[\&\>svg\~\*\]\:pl-7>svg~* {
2955
+ padding-left: 1.75rem;
2956
+ }
2957
+
2958
+ .\[\&\>tr\]\:last\:border-b-0:last-child>tr {
2959
+ border-bottom-width: 0px;
2960
+ }
2961
+
2962
+ .\[\&_p\]\:leading-relaxed p {
2963
+ line-height: 1.625;
2964
+ }
2965
+
2966
+ .\[\&_tr\:last-child\]\:border-0 tr:last-child {
2967
+ border-width: 0px;
2968
+ }
2969
+
2970
+ .\[\&_tr\]\:border-b tr {
2971
+ border-bottom-width: 1px;
2972
+ }
2973
+
src/requirements.txt ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o src/requirements.txt
3
+ accelerate==0.34.2
4
+ # via peft
5
+ aiohappyeyeballs==2.4.3
6
+ # via aiohttp
7
+ aiohttp==3.12.14
8
+ # via
9
+ # datasets
10
+ # fsspec
11
+ # pyvespa
12
+ aiosignal==1.3.1
13
+ # via aiohttp
14
+ annotated-types==0.7.0
15
+ # via pydantic
16
+ anyio==4.6.0
17
+ # via
18
+ # httpx
19
+ # starlette
20
+ # watchfiles
21
+ async-timeout==4.0.3
22
+ # via aiohttp
23
+ attrs==24.2.0
24
+ # via aiohttp
25
+ beautifulsoup4==4.12.3
26
+ # via python-fasthtml
27
+ blis==0.7.11
28
+ # via thinc
29
+ cachetools==5.5.0
30
+ # via google-auth
31
+ catalogue==2.0.10
32
+ # via
33
+ # spacy
34
+ # srsly
35
+ # thinc
36
+ certifi==2024.8.30
37
+ # via
38
+ # httpcore
39
+ # httpx
40
+ # requests
41
+ cffi==1.17.1
42
+ # via cryptography
43
+ charset-normalizer==3.3.2
44
+ # via requests
45
+ click==8.1.7
46
+ # via
47
+ # typer
48
+ # uvicorn
49
+ cloudpathlib==0.20.0
50
+ # via weasel
51
+ colpali-engine==0.3.1
52
+ # via
53
+ # visual-retrieval-colpali (pyproject.toml)
54
+ # vidore-benchmark
55
+ confection==0.1.5
56
+ # via
57
+ # thinc
58
+ # weasel
59
+ contourpy==1.3.0
60
+ # via matplotlib
61
+ cryptography==44.0.1
62
+ # via pyvespa
63
+ cycler==0.12.1
64
+ # via matplotlib
65
+ cymem==2.0.8
66
+ # via
67
+ # preshed
68
+ # spacy
69
+ # thinc
70
+ datasets==2.21.0
71
+ # via
72
+ # mteb
73
+ # vidore-benchmark
74
+ dill==0.3.8
75
+ # via
76
+ # datasets
77
+ # multiprocess
78
+ docker==7.1.0
79
+ # via pyvespa
80
+ einops==0.8.0
81
+ # via
82
+ # visual-retrieval-colpali (pyproject.toml)
83
+ # vidore-benchmark
84
+ eval-type-backport==0.2.0
85
+ # via mteb
86
+ exceptiongroup==1.2.2
87
+ # via anyio
88
+ fastcore==1.7.11
89
+ # via
90
+ # fastlite
91
+ # python-fasthtml
92
+ # pyvespa
93
+ # sqlite-minutils
94
+ fastlite==0.0.11
95
+ # via python-fasthtml
96
+ filelock==3.16.1
97
+ # via
98
+ # datasets
99
+ # huggingface-hub
100
+ # torch
101
+ # transformers
102
+ fonttools==4.54.1
103
+ # via matplotlib
104
+ frozenlist==1.4.1
105
+ # via
106
+ # aiohttp
107
+ # aiosignal
108
+ fsspec==2024.6.1
109
+ # via
110
+ # datasets
111
+ # huggingface-hub
112
+ # torch
113
+ google-ai-generativelanguage==0.6.10
114
+ # via google-generativeai
115
+ google-api-core==2.21.0
116
+ # via
117
+ # google-ai-generativelanguage
118
+ # google-api-python-client
119
+ # google-generativeai
120
+ google-api-python-client==2.149.0
121
+ # via google-generativeai
122
+ google-auth==2.35.0
123
+ # via
124
+ # google-ai-generativelanguage
125
+ # google-api-core
126
+ # google-api-python-client
127
+ # google-auth-httplib2
128
+ # google-generativeai
129
+ google-auth-httplib2==0.2.0
130
+ # via google-api-python-client
131
+ google-generativeai==0.8.3
132
+ # via visual-retrieval-colpali (pyproject.toml)
133
+ googleapis-common-protos==1.65.0
134
+ # via
135
+ # google-api-core
136
+ # grpcio-status
137
+ gputil==1.4.0
138
+ # via
139
+ # colpali-engine
140
+ # vidore-benchmark
141
+ grpcio==1.67.0
142
+ # via
143
+ # google-api-core
144
+ # grpcio-status
145
+ grpcio-status==1.67.0
146
+ # via google-api-core
147
+ h11==0.16.0
148
+ # via
149
+ # httpcore
150
+ # uvicorn
151
+ h2==4.1.0
152
+ # via httpx
153
+ hpack==4.0.0
154
+ # via h2
155
+ httpcore==1.0.6
156
+ # via httpx
157
+ httplib2==0.22.0
158
+ # via
159
+ # google-api-python-client
160
+ # google-auth-httplib2
161
+ httptools==0.6.1
162
+ # via uvicorn
163
+ httpx==0.27.2
164
+ # via
165
+ # python-fasthtml
166
+ # pyvespa
167
+ huggingface-hub==0.25.1
168
+ # via
169
+ # visual-retrieval-colpali (pyproject.toml)
170
+ # accelerate
171
+ # datasets
172
+ # peft
173
+ # sentence-transformers
174
+ # tokenizers
175
+ # transformers
176
+ hyperframe==6.0.1
177
+ # via h2
178
+ idna==3.10
179
+ # via
180
+ # anyio
181
+ # httpx
182
+ # requests
183
+ # yarl
184
+ itsdangerous==2.2.0
185
+ # via python-fasthtml
186
+ jinja2==3.1.6
187
+ # via
188
+ # pyvespa
189
+ # spacy
190
+ # torch
191
+ joblib==1.4.2
192
+ # via scikit-learn
193
+ kiwisolver==1.4.7
194
+ # via matplotlib
195
+ langcodes==3.4.1
196
+ # via spacy
197
+ language-data==1.2.0
198
+ # via langcodes
199
+ loguru==0.7.2
200
+ # via vidore-benchmark
201
+ lucide-fasthtml==0.0.9
202
+ # via shad4fast
203
+ lxml==5.3.0
204
+ # via
205
+ # lucide-fasthtml
206
+ # pyvespa
207
+ marisa-trie==1.2.1
208
+ # via language-data
209
+ markdown-it-py==3.0.0
210
+ # via rich
211
+ markupsafe==2.1.5
212
+ # via jinja2
213
+ matplotlib==3.9.2
214
+ # via
215
+ # seaborn
216
+ # vidore-benchmark
217
+ mdurl==0.1.2
218
+ # via markdown-it-py
219
+ mpmath==1.3.0
220
+ # via sympy
221
+ mteb==1.15.3
222
+ # via vidore-benchmark
223
+ multidict==6.1.0
224
+ # via
225
+ # aiohttp
226
+ # yarl
227
+ multiprocess==0.70.16
228
+ # via datasets
229
+ murmurhash==1.0.10
230
+ # via
231
+ # preshed
232
+ # spacy
233
+ # thinc
234
+ networkx==3.3
235
+ # via torch
236
+ numpy==1.26.4
237
+ # via
238
+ # accelerate
239
+ # blis
240
+ # colpali-engine
241
+ # contourpy
242
+ # datasets
243
+ # matplotlib
244
+ # mteb
245
+ # pandas
246
+ # peft
247
+ # pyarrow
248
+ # scikit-learn
249
+ # scipy
250
+ # seaborn
251
+ # spacy
252
+ # thinc
253
+ # transformers
254
+ # vidore-benchmark
255
+ oauthlib==3.2.2
256
+ # via python-fasthtml
257
+ packaging==24.1
258
+ # via
259
+ # accelerate
260
+ # datasets
261
+ # fastcore
262
+ # huggingface-hub
263
+ # matplotlib
264
+ # peft
265
+ # spacy
266
+ # thinc
267
+ # transformers
268
+ # weasel
269
+ pandas==2.2.3
270
+ # via
271
+ # datasets
272
+ # seaborn
273
+ pdf2image==1.17.0
274
+ # via vidore-benchmark
275
+ peft==0.11.1
276
+ # via
277
+ # colpali-engine
278
+ # vidore-benchmark
279
+ pillow==10.4.0
280
+ # via
281
+ # colpali-engine
282
+ # matplotlib
283
+ # pdf2image
284
+ # sentence-transformers
285
+ # vidore-benchmark
286
+ pip==24.3.1
287
+ # via visual-retrieval-colpali (pyproject.toml)
288
+ polars==1.9.0
289
+ # via mteb
290
+ preshed==3.0.9
291
+ # via
292
+ # spacy
293
+ # thinc
294
+ proto-plus==1.24.0
295
+ # via
296
+ # google-ai-generativelanguage
297
+ # google-api-core
298
+ protobuf==5.29.5
299
+ # via
300
+ # google-ai-generativelanguage
301
+ # google-api-core
302
+ # google-generativeai
303
+ # googleapis-common-protos
304
+ # grpcio-status
305
+ # proto-plus
306
+ psutil==6.0.0
307
+ # via
308
+ # accelerate
309
+ # peft
310
+ pyarrow==17.0.0
311
+ # via datasets
312
+ pyasn1==0.6.1
313
+ # via
314
+ # pyasn1-modules
315
+ # rsa
316
+ pyasn1-modules==0.4.1
317
+ # via google-auth
318
+ pycparser==2.22
319
+ # via cffi
320
+ pydantic==2.9.2
321
+ # via
322
+ # confection
323
+ # google-generativeai
324
+ # mteb
325
+ # spacy
326
+ # thinc
327
+ # weasel
328
+ pydantic-core==2.23.4
329
+ # via pydantic
330
+ pygments==2.18.0
331
+ # via rich
332
+ pyparsing==3.1.4
333
+ # via
334
+ # httplib2
335
+ # matplotlib
336
+ pypdf==5.0.1
337
+ # via visual-retrieval-colpali (pyproject.toml)
338
+ python-dateutil==2.9.0.post0
339
+ # via
340
+ # matplotlib
341
+ # pandas
342
+ # python-fasthtml
343
+ # pyvespa
344
+ python-dotenv==1.0.1
345
+ # via
346
+ # visual-retrieval-colpali (pyproject.toml)
347
+ # uvicorn
348
+ # vidore-benchmark
349
+ python-fasthtml==0.6.9
350
+ # via
351
+ # visual-retrieval-colpali (pyproject.toml)
352
+ # lucide-fasthtml
353
+ # shad4fast
354
+ python-multipart==0.0.18
355
+ # via python-fasthtml
356
+ pytrec-eval-terrier==0.5.6
357
+ # via mteb
358
+ pytz==2024.2
359
+ # via pandas
360
+ pyvespa==0.50.0
361
+ # via visual-retrieval-colpali (pyproject.toml)
362
+ pyyaml==6.0.2
363
+ # via
364
+ # accelerate
365
+ # datasets
366
+ # huggingface-hub
367
+ # peft
368
+ # transformers
369
+ # uvicorn
370
+ regex==2024.9.11
371
+ # via transformers
372
+ requests==2.32.4
373
+ # via
374
+ # colpali-engine
375
+ # datasets
376
+ # docker
377
+ # google-api-core
378
+ # huggingface-hub
379
+ # lucide-fasthtml
380
+ # mteb
381
+ # pyvespa
382
+ # requests-toolbelt
383
+ # spacy
384
+ # transformers
385
+ # weasel
386
+ requests-toolbelt==1.0.0
387
+ # via pyvespa
388
+ rich==13.9.2
389
+ # via
390
+ # mteb
391
+ # typer
392
+ rsa==4.9
393
+ # via google-auth
394
+ safetensors==0.4.5
395
+ # via
396
+ # accelerate
397
+ # peft
398
+ # transformers
399
+ scikit-learn==1.5.2
400
+ # via
401
+ # mteb
402
+ # sentence-transformers
403
+ scipy==1.14.1
404
+ # via
405
+ # mteb
406
+ # scikit-learn
407
+ # sentence-transformers
408
+ seaborn==0.13.2
409
+ # via vidore-benchmark
410
+ sentence-transformers==3.1.1
411
+ # via
412
+ # mteb
413
+ # vidore-benchmark
414
+ sentencepiece==0.2.0
415
+ # via vidore-benchmark
416
+ setuptools==78.1.1
417
+ # via
418
+ # visual-retrieval-colpali (pyproject.toml)
419
+ # marisa-trie
420
+ # spacy
421
+ # thinc
422
+ shad4fast==1.2.1
423
+ # via visual-retrieval-colpali (pyproject.toml)
424
+ shellingham==1.5.4
425
+ # via typer
426
+ six==1.16.0
427
+ # via python-dateutil
428
+ smart-open==7.0.5
429
+ # via weasel
430
+ sniffio==1.3.1
431
+ # via
432
+ # anyio
433
+ # httpx
434
+ soupsieve==2.6
435
+ # via beautifulsoup4
436
+ spacy==3.7.5
437
+ # via visual-retrieval-colpali (pyproject.toml)
438
+ spacy-legacy==3.0.12
439
+ # via spacy
440
+ spacy-loggers==1.0.5
441
+ # via spacy
442
+ sqlite-minutils==3.37.0.post3
443
+ # via fastlite
444
+ srsly==2.4.8
445
+ # via
446
+ # confection
447
+ # spacy
448
+ # thinc
449
+ # weasel
450
+ starlette==0.47.2
451
+ # via python-fasthtml
452
+ sympy==1.13.3
453
+ # via torch
454
+ tenacity==9.0.0
455
+ # via pyvespa
456
+ thinc==8.2.5
457
+ # via spacy
458
+ threadpoolctl==3.5.0
459
+ # via scikit-learn
460
+ tokenizers==0.20.0
461
+ # via transformers
462
+ torch==2.7.1
463
+ # via
464
+ # visual-retrieval-colpali (pyproject.toml)
465
+ # accelerate
466
+ # colpali-engine
467
+ # mteb
468
+ # peft
469
+ # sentence-transformers
470
+ # vidore-benchmark
471
+ tqdm==4.66.5
472
+ # via
473
+ # datasets
474
+ # google-generativeai
475
+ # huggingface-hub
476
+ # mteb
477
+ # peft
478
+ # sentence-transformers
479
+ # spacy
480
+ # transformers
481
+ transformers==4.53.0
482
+ # via
483
+ # colpali-engine
484
+ # peft
485
+ # sentence-transformers
486
+ # vidore-benchmark
487
+ typer==0.12.5
488
+ # via
489
+ # spacy
490
+ # vidore-benchmark
491
+ # weasel
492
+ typing-extensions==4.12.2
493
+ # via
494
+ # anyio
495
+ # cloudpathlib
496
+ # google-generativeai
497
+ # huggingface-hub
498
+ # mteb
499
+ # multidict
500
+ # pydantic
501
+ # pydantic-core
502
+ # pypdf
503
+ # pyvespa
504
+ # rich
505
+ # torch
506
+ # typer
507
+ # uvicorn
508
+ tzdata==2024.2
509
+ # via pandas
510
+ uritemplate==4.1.1
511
+ # via google-api-python-client
512
+ urllib3==2.5.0
513
+ # via
514
+ # docker
515
+ # requests
516
+ uvicorn==0.31.0
517
+ # via python-fasthtml
518
+ uvloop==0.20.0
519
+ # via uvicorn
520
+ vespacli==8.391.23
521
+ # via visual-retrieval-colpali (pyproject.toml)
522
+ vidore-benchmark==4.0.0
523
+ # via visual-retrieval-colpali (pyproject.toml)
524
+ wasabi==1.1.3
525
+ # via
526
+ # spacy
527
+ # thinc
528
+ # weasel
529
+ watchfiles==0.24.0
530
+ # via uvicorn
531
+ weasel==0.4.1
532
+ # via spacy
533
+ websockets==13.1
534
+ # via uvicorn
535
+ wrapt==1.16.0
536
+ # via smart-open
537
+ xxhash==3.5.0
538
+ # via datasets
539
+ yarl==1.13.1
540
+ # via aiohttp
src/static/img/colpali_child.png ADDED

Git LFS Details

  • SHA256: 2302cf5fa940ec522f5a82290ce590250b8b5e56c2bfd229f213762f46a261c4
  • Pointer size: 131 Bytes
  • Size of remote file: 461 kB
src/static/img/linkedin.svg ADDED
src/static/img/vespa-colpali.png ADDED

Git LFS Details

  • SHA256: 4fe9f0ce3cad3448e7627a6a57cb3cb8a2a098f79f6764266044dd62a952c637
  • Pointer size: 131 Bytes
  • Size of remote file: 381 kB
src/static/img/visual-retrieval-demoapp-arch.png ADDED

Git LFS Details

  • SHA256: b393c7d9aeb2fbebe74cbcde4b4904fd0635faf3fe27b455bc5099cb531f95e6
  • Pointer size: 131 Bytes
  • Size of remote file: 353 kB
src/static/img/x.svg ADDED
src/static/js/highlightjs-theme.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ function getPreferredTheme() {
3
+ if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
4
+ return 'dark';
5
+ }
6
+ return 'light';
7
+ }
8
+
9
+ function syncHighlightTheme() {
10
+ const link = document.getElementById('highlight-theme');
11
+ const preferredTheme = getPreferredTheme();
12
+ link.href = preferredTheme === 'dark' ?
13
+ 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/github-dark.min.css' :
14
+ 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/github.min.css';
15
+ }
16
+
17
+ // Apply the correct theme immediately
18
+ syncHighlightTheme();
19
+
20
+ // Observe changes in the 'dark' class on the <html> element
21
+ const observer = new MutationObserver(syncHighlightTheme);
22
+ observer.observe(document.documentElement, {attributes: true, attributeFilter: ['class']});
23
+ })();
src/tailwind.config.js ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function filterDefault(values) {
2
+ return Object.fromEntries(
3
+ Object.entries(values).filter(([key]) => key !== "DEFAULT"),
4
+ );
5
+ }
6
+
7
+ /** @type {import('tailwindcss').Config} */
8
+ export default {
9
+ darkMode: ["selector"],
10
+ content: [
11
+ "../**/*.py",
12
+ "../.venv/lib/python3.12/site-packages/shad4fast/**/*.{py,js}",
13
+ ],
14
+ theme: {
15
+ container: {
16
+ center: true,
17
+ padding: "2rem",
18
+ screens: {
19
+ "2xl": "1400px",
20
+ },
21
+ },
22
+ extend: {
23
+ animation: {
24
+ "accordion-down": "accordion-down 0.2s ease-out",
25
+ "accordion-up": "accordion-up 0.2s ease-out",
26
+ },
27
+ animationDelay: ({theme}) => ({
28
+ ...theme("transitionDelay"),
29
+ }),
30
+ animationDuration: ({theme}) => ({
31
+ 0: "0ms",
32
+ ...theme("transitionDuration"),
33
+ }),
34
+ animationTimingFunction: ({theme}) => ({
35
+ ...theme("transitionTimingFunction"),
36
+ }),
37
+ animationFillMode: {
38
+ none: "none",
39
+ forwards: "forwards",
40
+ backwards: "backwards",
41
+ both: "both",
42
+ },
43
+ animationDirection: {
44
+ normal: "normal",
45
+ reverse: "reverse",
46
+ alternate: "alternate",
47
+ "alternate-reverse": "alternate-reverse",
48
+ },
49
+ animationOpacity: ({theme}) => ({
50
+ DEFAULT: 0,
51
+ ...theme("opacity"),
52
+ }),
53
+ animationTranslate: ({theme}) => ({
54
+ DEFAULT: "100%",
55
+ ...theme("translate"),
56
+ }),
57
+ animationScale: ({theme}) => ({
58
+ DEFAULT: 0,
59
+ ...theme("scale"),
60
+ }),
61
+ animationRotate: ({theme}) => ({
62
+ DEFAULT: "30deg",
63
+ ...theme("rotate"),
64
+ }),
65
+ animationRepeat: {
66
+ 0: "0",
67
+ 1: "1",
68
+ infinite: "infinite",
69
+ },
70
+ keyframes: {
71
+ enter: {
72
+ from: {
73
+ opacity: "var(--tw-enter-opacity, 1)",
74
+ transform:
75
+ "translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))",
76
+ },
77
+ },
78
+ exit: {
79
+ to: {
80
+ opacity: "var(--tw-exit-opacity, 1)",
81
+ transform:
82
+ "translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))",
83
+ },
84
+ },
85
+ },
86
+ colors: {
87
+ border: "hsl(var(--border))",
88
+ input: "hsl(var(--input))",
89
+ ring: "hsl(var(--ring))",
90
+ background: "hsl(var(--background))",
91
+ foreground: "hsl(var(--foreground))",
92
+ primary: {
93
+ DEFAULT: "hsl(var(--primary))",
94
+ foreground: "hsl(var(--primary-foreground))",
95
+ },
96
+ secondary: {
97
+ DEFAULT: "hsl(var(--secondary))",
98
+ foreground: "hsl(var(--secondary-foreground))",
99
+ },
100
+ destructive: {
101
+ DEFAULT: "hsl(var(--destructive))",
102
+ foreground: "hsl(var(--destructive-foreground))",
103
+ },
104
+ muted: {
105
+ DEFAULT: "hsl(var(--muted))",
106
+ foreground: "hsl(var(--muted-foreground))",
107
+ },
108
+ accent: {
109
+ DEFAULT: "hsl(var(--accent))",
110
+ foreground: "hsl(var(--accent-foreground))",
111
+ },
112
+ popover: {
113
+ DEFAULT: "hsl(var(--popover))",
114
+ foreground: "hsl(var(--popover-foreground))",
115
+ },
116
+ card: {
117
+ DEFAULT: "hsl(var(--card))",
118
+ foreground: "hsl(var(--card-foreground))",
119
+ },
120
+ },
121
+ borderRadius: {
122
+ lg: `var(--radius)`,
123
+ md: `calc(var(--radius) - 2px)`,
124
+ sm: "calc(var(--radius) - 4px)",
125
+ },
126
+ },
127
+ },
128
+ plugins: [
129
+ function ({addUtilities, matchUtilities, theme}) {
130
+ addUtilities({
131
+ "@keyframes enter": theme("keyframes.enter"),
132
+ "@keyframes exit": theme("keyframes.exit"),
133
+ ".animate-in": {
134
+ animationName: "enter",
135
+ animationDuration: theme("animationDuration.DEFAULT"),
136
+ "--tw-enter-opacity": "initial",
137
+ "--tw-enter-scale": "initial",
138
+ "--tw-enter-rotate": "initial",
139
+ "--tw-enter-translate-x": "initial",
140
+ "--tw-enter-translate-y": "initial",
141
+ },
142
+ ".animate-out": {
143
+ animationName: "exit",
144
+ animationDuration: theme("animationDuration.DEFAULT"),
145
+ "--tw-exit-opacity": "initial",
146
+ "--tw-exit-scale": "initial",
147
+ "--tw-exit-rotate": "initial",
148
+ "--tw-exit-translate-x": "initial",
149
+ "--tw-exit-translate-y": "initial",
150
+ },
151
+ });
152
+
153
+ matchUtilities(
154
+ {
155
+ "fade-in": (value) => ({"--tw-enter-opacity": value}),
156
+ "fade-out": (value) => ({"--tw-exit-opacity": value}),
157
+ },
158
+ {values: theme("animationOpacity")},
159
+ );
160
+
161
+ matchUtilities(
162
+ {
163
+ "zoom-in": (value) => ({"--tw-enter-scale": value}),
164
+ "zoom-out": (value) => ({"--tw-exit-scale": value}),
165
+ },
166
+ {values: theme("animationScale")},
167
+ );
168
+
169
+ matchUtilities(
170
+ {
171
+ "spin-in": (value) => ({"--tw-enter-rotate": value}),
172
+ "spin-out": (value) => ({"--tw-exit-rotate": value}),
173
+ },
174
+ {values: theme("animationRotate")},
175
+ );
176
+
177
+ matchUtilities(
178
+ {
179
+ "slide-in-from-top": (value) => ({
180
+ "--tw-enter-translate-y": `-${value}`,
181
+ }),
182
+ "slide-in-from-bottom": (value) => ({
183
+ "--tw-enter-translate-y": value,
184
+ }),
185
+ "slide-in-from-left": (value) => ({
186
+ "--tw-enter-translate-x": `-${value}`,
187
+ }),
188
+ "slide-in-from-right": (value) => ({
189
+ "--tw-enter-translate-x": value,
190
+ }),
191
+ "slide-out-to-top": (value) => ({
192
+ "--tw-exit-translate-y": `-${value}`,
193
+ }),
194
+ "slide-out-to-bottom": (value) => ({
195
+ "--tw-exit-translate-y": value,
196
+ }),
197
+ "slide-out-to-left": (value) => ({
198
+ "--tw-exit-translate-x": `-${value}`,
199
+ }),
200
+ "slide-out-to-right": (value) => ({
201
+ "--tw-exit-translate-x": value,
202
+ }),
203
+ },
204
+ {values: theme("animationTranslate")},
205
+ );
206
+
207
+ matchUtilities(
208
+ {duration: (value) => ({animationDuration: value})},
209
+ {values: filterDefault(theme("animationDuration"))},
210
+ );
211
+
212
+ matchUtilities(
213
+ {delay: (value) => ({animationDelay: value})},
214
+ {values: theme("animationDelay")},
215
+ );
216
+
217
+ matchUtilities(
218
+ {ease: (value) => ({animationTimingFunction: value})},
219
+ {values: filterDefault(theme("animationTimingFunction"))},
220
+ );
221
+
222
+ addUtilities({
223
+ ".running": {animationPlayState: "running"},
224
+ ".paused": {animationPlayState: "paused"},
225
+ });
226
+
227
+ matchUtilities(
228
+ {"fill-mode": (value) => ({animationFillMode: value})},
229
+ {values: theme("animationFillMode")},
230
+ );
231
+
232
+ matchUtilities(
233
+ {direction: (value) => ({animationDirection: value})},
234
+ {values: theme("animationDirection")},
235
+ );
236
+
237
+ matchUtilities(
238
+ {repeat: (value) => ({animationIterationCount: value})},
239
+ {values: theme("animationRepeat")},
240
+ );
241
+ },
242
+ ],
243
+ };
src/tailwindcss ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:327703a4646081906e11d116ff4e8e43076466c3d269282bbe612555b9fe0c58
3
+ size 47351504
uv.lock ADDED
The diff for this file is too large to render. See raw diff
 
vespa_feed_to_hf_dataset.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from dotenv import load_dotenv
3
+ import os
4
+ import base64
5
+ from PIL import Image
6
+ import io
7
+ from datasets import Dataset, Image as HFImage
8
+ from pathlib import Path
9
+ from tqdm import tqdm
10
+
11
+ load_dotenv()
12
+
13
+ df = pd.read_json("output/vespa_feed_full.jsonl", lines=True)
14
+ df = pd.json_normalize(df["fields"].tolist())
15
+
16
+ dataset_dir = Path("hf_dataset")
17
+ image_dir = dataset_dir / "images"
18
+ os.makedirs(image_dir, exist_ok=True)
19
+
20
+
21
+ def save_image(image_data, filename):
22
+ img_data = base64.b64decode(image_data)
23
+ img = Image.open(io.BytesIO(img_data))
24
+ img.save(filename)
25
+
26
+
27
+ for idx, row in tqdm(df.iterrows()):
28
+ blur_filename = os.path.join(image_dir, f"blur_{idx}.jpg")
29
+ full_filename = os.path.join(image_dir, f"full_{idx}.jpg")
30
+ save_image(row["blur_image"], blur_filename)
31
+ save_image(row["full_image"], full_filename)
32
+ df.at[idx, "blur_image"] = blur_filename
33
+ df.at[idx, "full_image"] = full_filename
34
+
35
+
36
+ # Step 3: Convert to Hugging Face Dataset
37
+ dataset = (
38
+ Dataset.from_dict(df.to_dict(orient="list"))
39
+ .cast_column("blur_image", HFImage())
40
+ .cast_column("full_image", HFImage())
41
+ )
42
+ dataset.push_to_hub("vespa-engine/gpfg-QA", private=True)