andreer commited on
Commit
2f08c82
β€’
1 Parent(s): 8dc2c8a

Upload folder using huggingface_hub

Browse files
.gitignore CHANGED
@@ -8,7 +8,8 @@ template/
8
  *.json
9
  output/
10
  pdfs/
11
- static/full_images/
12
- static/sim_maps/
 
13
  embeddings/
14
  hf_dataset/
 
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,19 +1,211 @@
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
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.ipynb) to prepare the data and deploy the Vespa application.
26
+
27
+ ## Setting up your .env variables
28
+
29
+ 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:
30
+
31
+ You can rename the `.env.example` file to `.env` and fill in the required values.
32
+ The other variables are optional, if you want to use mTLS authentication against the Vespa application.
33
+
34
+ ```bash
35
+ VESPA_APP_TOKEN_URL=https://abcde.z.vespa-app.cloud
36
+ VESPA_CLOUD_SECRET_TOKEN=vespa_cloud_xxxxxxxx
37
+ GEMINI_API_KEY=asdf
38
+ ```
39
+
40
+ If you want to deploy the application to Huggingface, you also need to set a `HF_TOKEN` variable, with write permissions.
41
+ This is personal, and must be created at [huggingface](https://huggingface.co/settings/tokens).
42
+
43
+ ```bash
44
+ HF_TOKEN=hf_xxxxxxxxxx
45
+ ```
46
+
47
+ ## Setting up python environment
48
+
49
+ This application should work on Python 3.8 and above.
50
+
51
+ You can install the dependencies with `pip`, but we recommend using `uv`.
52
+ Skip to [Installing dependencies using `uv`](#installing-dependencies-using-uv) if you want to use `uv`.
53
+
54
+ ### Installing dependencies using `pip`
55
+
56
+ You can install the dependencies with `pip`:
57
+
58
+ ```bash
59
+ pip install -r src/requirements.txt
60
+ ```
61
+
62
+ ### Installing dependencies using `uv`
63
+
64
+ We recommend installing the amazing `uv` to manage your python environment:
65
+ See also [installation - uv docs](https://docs.astral.sh/uv/getting-started/installation/) for other installation options.
66
+
67
+ ```bash
68
+ curl -LsSf https://astral.sh/uv/install.sh | sh
69
+ ```
70
+
71
+ Then, create a virtual environment:
72
+
73
+ ```bash
74
+ uv venv
75
+ ```
76
+
77
+ Activate the virtual environment:
78
+
79
+ ```bash
80
+ source .venv/bin/activate
81
+ ```
82
+
83
+ Sync your virtual environment with the dependencies:
84
+
85
+ ```bash
86
+ uv sync --extra dev
87
+ ```
88
+
89
+ ## Running the application locally
90
+
91
+ To run the application locally, you can change into the `src` directory and run:
92
+
93
+ ```bash
94
+ python main.py
95
+ ```
96
+
97
+ This will start a local server, and you can access the application at `http://localhost:7860`.
98
+
99
+ ## Deploy to huggingface πŸ€— spaces
100
+
101
+ ### Compiling dependencies
102
+
103
+ 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:
104
+
105
+ ```bash
106
+ uv pip compile pyproject.toml -o src/requirements.txt
107
+ ```
108
+
109
+ 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.
110
+
111
+ ### Deploying to huggingface
112
+
113
+ Note that you need to set `HF_TOKEN` environment variable first.
114
+ This is personal, and must be created at [huggingface](https://huggingface.co/settings/tokens).
115
+ Make sure the token has `write` access.
116
+ Be aware that this will not delete existing files, only modify or add,
117
+ see [huggingface-cli](https://huggingface.co/docs/huggingface_hub/en/guides/upload#upload-from-the-cli) for more
118
+ information.
119
+
120
+ #### Update your space configuration
121
+
122
+ The `src/README.md` file contains the configuration for the space.
123
+ Feel free to update this file to match your own configuration - name, description, etc.
124
+
125
+ 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`.
126
+ See [Custom python spaces](https://huggingface.co/docs/hub/en/spaces-sdks-python) for more information.
127
+
128
+ #### Upload the files
129
+
130
+ To deploy, run
131
+
132
+ (Replace `vespa-engine/colpali-vespa-visual-retrieval` with your own huggingface user/repo name, does not need to exist beforehand)
133
+
134
+ ```bash
135
+ huggingface-cli upload vespa-engine/colpali-vespa-visual-retrieval src . --repo-type=space
136
+ ```
137
+
138
+ Note that we upload only the `src` directory.
139
+
140
+ ## Development
141
+
142
+ This section applies if you want to make changes to the web app.
143
+
144
+ ### Adding dependencies
145
+
146
+ To add dependencies, you can add them to the `pyproject.toml` file and run:
147
+
148
+ ```bash
149
+ uv compile
150
+ ```
151
+
152
+ and then sync the dependencies:
153
+
154
+ ```bash
155
+ uv sync --extra dev
156
+ ```
157
+
158
+ ### Making changes to CSS
159
+
160
+ To make changes to output.css apply, run
161
+
162
+ ```bash
163
+ shad4fast watch # watches all files passed through the tailwind.config.js content section
164
+
165
+ shad4fast build # minifies the current output.css file to reduce bundle size in production.
166
+ ```
167
+
168
+ ### Instructions on creating and feeding the full dataset
169
+
170
+ This section is only relevant if you want to create and feed the full dataset to Vespa.
171
+ 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.
172
+
173
+ #### Prepare data and Vespa application
174
+
175
+ First, install `uv`:
176
+
177
+ ```bash
178
+ curl -LsSf https://astral.sh/uv/install.sh | sh
179
+ ```
180
+
181
+ Then, run:
182
+
183
+ ```bash
184
+ uv sync --extra dev --extra feed
185
+ ```
186
+
187
+ #### Converting to notebook
188
+
189
+ If you want to run the `prepare_feed_deploy.py` as a notebook, you can convert it using `jupytext`:
190
+
191
+ Convert the `prepare_feed_deploy.py` to notebook to:
192
+
193
+ ```bash
194
+ jupytext --to notebook prepare_feed_deploy.py
195
+ ```
196
+
197
+ And launch a Jupyter instance with:
198
+
199
+ ```bash
200
+ uv run --with jupyter jupyter lab
201
+ ```
202
+
203
+ ## Credits
204
+
205
+ Huge thanks to the amazing projects that made it a joy to create this demo πŸ™πŸ™Œ
206
+
207
+ - Freeing us from python dependency hell - [uv](https://astral.sh/uv/)
208
+ - Allowing us to build **beautiful** full stack web apps in Python [FastHTML](https://fastht.ml/)
209
+ - Introducing the ColPali architecture - [ColPali](https://huggingface.co/vidore/colpali-v1.2)
210
+ - Adding `shadcn` components to FastHTML - [Shad4Fast](https://www.shad4fasthtml.com/)
211
+
prepare_feed_deploy.py CHANGED
@@ -1,4 +1,3 @@
1
- # %% [markdown]
2
  # # Visual PDF Retrieval - demo application
3
  #
4
  # In this notebook, we will prepare the Vespa backend application for our visual retrieval demo.
@@ -23,11 +22,10 @@
23
  # We have tried to make it easy for others to run this notebook, to create your own PDF Enterprise Search application using Vespa.
24
  #
25
 
26
- # %% [markdown]
27
  # ## 0. Setup and Configuration
28
  #
29
 
30
- # %%
31
  import os
32
  import asyncio
33
  import json
@@ -80,8 +78,8 @@ load_dotenv()
80
 
81
  # Avoid warning from huggingface tokenizers
82
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
 
83
 
84
- # %% [markdown]
85
  # ### Create a free trial in Vespa Cloud
86
  #
87
  # Create a tenant from [here](https://vespa.ai/free-trial/).
@@ -89,49 +87,39 @@ os.environ["TOKENIZERS_PARALLELISM"] = "false"
89
  # Take note of your tenant name.
90
  #
91
 
92
- # %%
93
  VESPA_TENANT_NAME = "vespa-team"
94
 
95
- # %% [markdown]
96
  # Here, set your desired application name. (Will be created in later steps)
97
  # Note that you can not have hyphen `-` or underscore `_` in the application name.
98
  #
99
 
100
- # %%
101
  VESPA_APPLICATION_NAME = "colpalidemo"
102
  VESPA_SCHEMA_NAME = "pdf_page"
103
 
104
- # %% [markdown]
105
  # Next, you need to create some tokens for feeding data, and querying the application.
106
  # We recommend separate tokens for feeding and querying, (the former with write permission, and the latter with read permission).
107
  # The tokens can be created from the [Vespa Cloud console](https://console.vespa-cloud.com/) in the 'Account' -> 'Tokens' section.
108
  #
109
 
110
- # %%
111
  VESPA_TOKEN_ID_WRITE = "colpalidemo_write"
112
- VESPA_TOKEN_ID_READ = "colpalidemo_read"
113
 
114
- # %% [markdown]
115
  # We also need to set the value of the write token to be able to feed data to the Vespa application.
116
  #
117
 
118
- # %%
119
  VESPA_CLOUD_SECRET_TOKEN = os.getenv("VESPA_CLOUD_SECRET_TOKEN") or input(
120
  "Enter Vespa cloud secret token: "
121
  )
122
 
123
- # %% [markdown]
124
  # We will also use the Gemini API to create sample queries for our images.
125
  # You can also use other VLM's to create these queries.
126
  # Create a Gemini API key from [here](https://aistudio.google.com/app/apikey).
127
  #
128
 
129
- # %%
130
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or input(
131
  "Enter Google Generative AI API key: "
132
  )
133
 
134
- # %%
135
  MODEL_NAME = "vidore/colpali-v1.2"
136
 
137
  # Configure Google Generative AI
@@ -149,8 +137,8 @@ model = ColPali.from_pretrained(
149
  ).eval()
150
 
151
  processor = ColPaliProcessor.from_pretrained(MODEL_NAME)
 
152
 
153
- # %% [markdown]
154
  # ## 1. Download PDFs
155
  #
156
  # We are going to use public reports from the Norwegian Government Pension Fund Global (also known as the Oil Fund).
@@ -163,12 +151,11 @@ processor = ColPaliProcessor.from_pretrained(MODEL_NAME)
163
  # ![Sample2](./static/img/gfpg-sample-2.png)
164
  #
165
 
166
- # %% [markdown]
167
  # As we can see, a lot of the information is in the form of tables, charts and numbers.
168
  # These are not easily extractable using pdf-readers or OCR tools.
169
  #
170
 
171
- # %%
172
  import requests
173
 
174
  url = "https://www.nbim.no/en/publications/reports/"
@@ -194,14 +181,14 @@ for year_div in soup.find_all("div", id=lambda x: x and x.startswith("year-")):
194
  links.append(full_url)
195
  url_to_year[full_url] = year
196
  links, url_to_year
 
197
 
198
- # %%
199
  # Limit the number of PDFs to download
200
  NUM_PDFS = 2 # Set to None to download all PDFs
201
  links = links[:NUM_PDFS] if NUM_PDFS else links
202
  links
203
 
204
- # %%
205
  from nest_asyncio import apply
206
  from typing import List
207
 
@@ -272,16 +259,15 @@ async def download_pdfs(links: List[str]) -> List[dict]:
272
  os.makedirs("pdfs", exist_ok=True)
273
  # Now run the download_pdfs function with the URL
274
  pdfs = asyncio.run(download_pdfs(links))
 
275
 
276
- # %%
277
  pdfs
278
 
279
- # %% [markdown]
280
  # ## 2. Convert PDFs to Images
281
  #
282
 
283
 
284
- # %%
285
  def get_pdf_images(pdf_path):
286
  reader = PdfReader(pdf_path)
287
  page_texts = []
@@ -313,11 +299,11 @@ for pdf in tqdm(pdfs):
313
  "page_no": page_no,
314
  }
315
  )
 
316
 
317
- # %%
318
  len(pdf_pages)
319
 
320
- # %%
321
  from collections import Counter
322
 
323
  # Print the length of the text fields - mean, max and min
@@ -327,8 +313,8 @@ print(f"Max text length: {np.max(text_lengths)}")
327
  print(f"Min text length: {np.min(text_lengths)}")
328
  print(f"Median text length: {np.median(text_lengths)}")
329
  print(f"Number of text with length == 0: {Counter(text_lengths)[0]}")
 
330
 
331
- # %% [markdown]
332
  # ## 3. Generate Queries
333
  #
334
  # In this step, we want to generate queries for each page image.
@@ -342,7 +328,7 @@ print(f"Number of text with length == 0: {Counter(text_lengths)[0]}")
342
  # We will use the Gemini API to generate these queries, with `gemini-1.5-flash-8b` as the model.
343
  #
344
 
345
- # %%
346
  from pydantic import BaseModel
347
 
348
 
@@ -394,7 +380,7 @@ Only return JSON. Don't return any extra explanation text. """
394
 
395
  prompt_text, pydantic_model = get_retrieval_prompt()
396
 
397
- # %%
398
  gemini_model = genai.GenerativeModel("gemini-1.5-flash-8b")
399
 
400
 
@@ -420,18 +406,17 @@ def generate_queries(image, prompt_text, pydantic_model):
420
  return queries
421
 
422
 
423
- # %%
 
424
  for pdf in tqdm(pdf_pages):
425
  image = pdf.get("image")
426
  pdf["queries"] = generate_queries(image, prompt_text, pydantic_model)
427
 
428
- # %%
429
  pdf_pages[46]["image"]
430
 
431
- # %%
432
  pdf_pages[46]["queries"]
433
 
434
- # %%
435
  # Generate queries async - keeping for now as we probably need when applying to the full dataset
436
  # import asyncio
437
  # from tenacity import retry, stop_after_attempt, wait_exponential
@@ -482,7 +467,7 @@ pdf_pages[46]["queries"]
482
 
483
  # pdf_pages = asyncio.run(enrich_pdfs())
484
 
485
- # %%
486
  # write title, url, page_no, text, queries, not image to JSON
487
  with open("output/pdf_pages.json", "w") as f:
488
  to_write = [{k: v for k, v in pdf.items() if k != "image"} for pdf in pdf_pages]
@@ -492,15 +477,14 @@ with open("output/pdf_pages.json", "w") as f:
492
  # saved_pdf_pages = json.load(f)
493
  # for pdf, saved_pdf in zip(pdf_pages, saved_pdf_pages):
494
  # pdf.update(saved_pdf)
 
495
 
496
- # %% [markdown]
497
  # ## 4. Generate embeddings
498
  #
499
  # Now that we have the queries, we can use the ColPali model to generate embeddings for each page image.
500
  #
501
 
502
 
503
- # %%
504
  def generate_embeddings(images, model, processor, batch_size=2) -> np.ndarray:
505
  """
506
  Generate embeddings for a list of images.
@@ -539,22 +523,18 @@ def generate_embeddings(images, model, processor, batch_size=2) -> np.ndarray:
539
  return all_embeddings
540
 
541
 
542
- # %%
543
  # Generate embeddings for all images
544
  images = [pdf["image"] for pdf in pdf_pages]
545
  embeddings = generate_embeddings(images, model, processor)
546
 
547
- # %%
548
  embeddings.shape
549
 
550
- # %% [markdown]
551
  # ## 5. Prepare Data on Vespa Format
552
  #
553
  # Now, that we have all the data we need, all that remains is to make sure it is in the right format for Vespa.
554
  #
555
 
556
 
557
- # %%
558
  def float_to_binary_embedding(float_query_embedding: dict) -> dict:
559
  """Utility function to convert float query embeddings to binary query embeddings."""
560
  binary_query_embeddings = {}
@@ -566,7 +546,6 @@ def float_to_binary_embedding(float_query_embedding: dict) -> dict:
566
  return binary_query_embeddings
567
 
568
 
569
- # %%
570
  vespa_feed = []
571
  for pdf, embedding in zip(pdf_pages, embeddings):
572
  url = pdf["url"]
@@ -604,7 +583,7 @@ for pdf, embedding in zip(pdf_pages, embeddings):
604
  }
605
  vespa_feed.append(page)
606
 
607
- # %%
608
  # We will prepare the Vespa feed data, including the embeddings and the generated queries
609
 
610
 
@@ -618,20 +597,19 @@ with open("output/vespa_feed.json", "w") as f:
618
  vespa_feed_to_save.append({"put": put_id, "fields": page["fields"]})
619
  json.dump(vespa_feed_to_save, f)
620
 
621
- # %%
622
  # import json
623
 
624
  # with open("output/vespa_feed.json", "r") as f:
625
  # vespa_feed = json.load(f)
 
626
 
627
- # %%
628
  len(vespa_feed)
629
 
630
- # %% [markdown]
631
  # ## 5. Prepare Vespa Application
632
  #
633
 
634
- # %%
635
  # Define the Vespa schema
636
  colpali_schema = Schema(
637
  name=VESPA_SCHEMA_NAME,
@@ -678,32 +656,15 @@ colpali_schema = Schema(
678
  Field(
679
  name="questions",
680
  type="array<string>",
681
- indexing=["summary", "index", "attribute"],
682
- index="enable-bm25",
683
- stemming="best",
684
  ),
685
  Field(
686
  name="queries",
687
  type="array<string>",
688
- indexing=["summary", "index", "attribute"],
689
- index="enable-bm25",
690
- stemming="best",
691
  ),
692
- # Add synthetic fields for the questions and queries
693
- # Field(
694
- # name="questions_exact",
695
- # type="array<string>",
696
- # indexing=["input questions", "index", "attribute"],
697
- # match=["word"],
698
- # is_document_field=False,
699
- # ),
700
- # Field(
701
- # name="queries_exact",
702
- # type="array<string>",
703
- # indexing=["input queries", "index"],
704
- # match=["word"],
705
- # is_document_field=False,
706
- # ),
707
  ]
708
  ),
709
  fieldsets=[
@@ -868,7 +829,7 @@ colpali_retrieval_profile = RankProfile(
868
  colpali_schema.add_rank_profile(colpali_retrieval_profile)
869
  colpali_schema.add_rank_profile(with_quantized_similarity(colpali_retrieval_profile))
870
 
871
- # %%
872
  from vespa.configuration.services import (
873
  services,
874
  container,
@@ -908,11 +869,6 @@ service_config = ServicesConfiguration(
908
  id="token_write",
909
  permissions="read,write",
910
  ),
911
- client(
912
- token(id=f"{VESPA_TOKEN_ID_READ}"),
913
- id="token_read",
914
- permissions="read",
915
- ),
916
  ),
917
  config(
918
  vt("tag")(
@@ -944,8 +900,8 @@ service_config = ServicesConfiguration(
944
  version="1.0",
945
  ),
946
  )
 
947
 
948
- # %%
949
  # Create the Vespa application package
950
  vespa_application_package = ApplicationPackage(
951
  name=VESPA_APPLICATION_NAME,
@@ -953,16 +909,14 @@ vespa_application_package = ApplicationPackage(
953
  services_config=service_config,
954
  )
955
 
956
- # %% [markdown]
957
  # ## 6. Deploy Vespa Application
958
  #
959
 
960
- # %%
961
  VESPA_TEAM_API_KEY = os.getenv("VESPA_TEAM_API_KEY") or input(
962
  "Enter Vespa team API key: "
963
  )
964
 
965
- # %%
966
  vespa_cloud = VespaCloud(
967
  tenant=VESPA_TENANT_NAME,
968
  application=VESPA_APPLICATION_NAME,
@@ -976,23 +930,21 @@ vespa_cloud.deploy()
976
  # Output the endpoint URL
977
  endpoint_url = vespa_cloud.get_token_endpoint()
978
  print(f"Application deployed. Token endpoint URL: {endpoint_url}")
 
979
 
980
- # %% [markdown]
981
  # Make sure to take note of the token endpoint_url.
982
  # 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.
983
  #
984
 
985
- # %% [markdown]
986
  # ## 8. Feed Data to Vespa
987
  #
988
 
989
- # %%
990
  # Instantiate Vespa connection using token
991
  app = Vespa(url=endpoint_url, vespa_cloud_secret_token=VESPA_CLOUD_SECRET_TOKEN)
992
  app.get_application_status()
993
 
994
 
995
- # %%
996
  def callback(response: VespaResponse, id: str):
997
  if not response.is_successful():
998
  print(
 
 
1
  # # Visual PDF Retrieval - demo application
2
  #
3
  # In this notebook, we will prepare the Vespa backend application for our visual retrieval demo.
 
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
 
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/).
 
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
 
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).
 
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/"
 
181
  links.append(full_url)
182
  url_to_year[full_url] = year
183
  links, url_to_year
184
+ # -
185
 
 
186
  # Limit the number of PDFs to download
187
  NUM_PDFS = 2 # Set to None to download all PDFs
188
  links = links[:NUM_PDFS] if NUM_PDFS else links
189
  links
190
 
191
+ # +
192
  from nest_asyncio import apply
193
  from typing import List
194
 
 
259
  os.makedirs("pdfs", exist_ok=True)
260
  # Now run the download_pdfs function with the URL
261
  pdfs = asyncio.run(download_pdfs(links))
262
+ # -
263
 
 
264
  pdfs
265
 
 
266
  # ## 2. Convert PDFs to Images
267
  #
268
 
269
 
270
+ # +
271
  def get_pdf_images(pdf_path):
272
  reader = PdfReader(pdf_path)
273
  page_texts = []
 
299
  "page_no": page_no,
300
  }
301
  )
302
+ # -
303
 
 
304
  len(pdf_pages)
305
 
306
+ # +
307
  from collections import Counter
308
 
309
  # Print the length of the text fields - mean, max and min
 
313
  print(f"Min text length: {np.min(text_lengths)}")
314
  print(f"Median text length: {np.median(text_lengths)}")
315
  print(f"Number of text with length == 0: {Counter(text_lengths)[0]}")
316
+ # -
317
 
 
318
  # ## 3. Generate Queries
319
  #
320
  # In this step, we want to generate queries for each page image.
 
328
  # We will use the Gemini API to generate these queries, with `gemini-1.5-flash-8b` as the model.
329
  #
330
 
331
+ # +
332
  from pydantic import BaseModel
333
 
334
 
 
380
 
381
  prompt_text, pydantic_model = get_retrieval_prompt()
382
 
383
+ # +
384
  gemini_model = genai.GenerativeModel("gemini-1.5-flash-8b")
385
 
386
 
 
406
  return queries
407
 
408
 
409
+ # -
410
+
411
  for pdf in tqdm(pdf_pages):
412
  image = pdf.get("image")
413
  pdf["queries"] = generate_queries(image, prompt_text, pydantic_model)
414
 
 
415
  pdf_pages[46]["image"]
416
 
 
417
  pdf_pages[46]["queries"]
418
 
419
+ # +
420
  # Generate queries async - keeping for now as we probably need when applying to the full dataset
421
  # import asyncio
422
  # from tenacity import retry, stop_after_attempt, wait_exponential
 
467
 
468
  # pdf_pages = asyncio.run(enrich_pdfs())
469
 
470
+ # +
471
  # write title, url, page_no, text, queries, not image to JSON
472
  with open("output/pdf_pages.json", "w") as f:
473
  to_write = [{k: v for k, v in pdf.items() if k != "image"} for pdf in pdf_pages]
 
477
  # saved_pdf_pages = json.load(f)
478
  # for pdf, saved_pdf in zip(pdf_pages, saved_pdf_pages):
479
  # pdf.update(saved_pdf)
480
+ # -
481
 
 
482
  # ## 4. Generate embeddings
483
  #
484
  # Now that we have the queries, we can use the ColPali model to generate embeddings for each page image.
485
  #
486
 
487
 
 
488
  def generate_embeddings(images, model, processor, batch_size=2) -> np.ndarray:
489
  """
490
  Generate embeddings for a list of images.
 
523
  return all_embeddings
524
 
525
 
 
526
  # Generate embeddings for all images
527
  images = [pdf["image"] for pdf in pdf_pages]
528
  embeddings = generate_embeddings(images, model, processor)
529
 
 
530
  embeddings.shape
531
 
 
532
  # ## 5. Prepare Data on Vespa Format
533
  #
534
  # Now, that we have all the data we need, all that remains is to make sure it is in the right format for Vespa.
535
  #
536
 
537
 
 
538
  def float_to_binary_embedding(float_query_embedding: dict) -> dict:
539
  """Utility function to convert float query embeddings to binary query embeddings."""
540
  binary_query_embeddings = {}
 
546
  return binary_query_embeddings
547
 
548
 
 
549
  vespa_feed = []
550
  for pdf, embedding in zip(pdf_pages, embeddings):
551
  url = pdf["url"]
 
583
  }
584
  vespa_feed.append(page)
585
 
586
+ # +
587
  # We will prepare the Vespa feed data, including the embeddings and the generated queries
588
 
589
 
 
597
  vespa_feed_to_save.append({"put": put_id, "fields": page["fields"]})
598
  json.dump(vespa_feed_to_save, f)
599
 
600
+ # +
601
  # import json
602
 
603
  # with open("output/vespa_feed.json", "r") as f:
604
  # vespa_feed = json.load(f)
605
+ # -
606
 
 
607
  len(vespa_feed)
608
 
 
609
  # ## 5. Prepare Vespa Application
610
  #
611
 
612
+ # +
613
  # Define the Vespa schema
614
  colpali_schema = Schema(
615
  name=VESPA_SCHEMA_NAME,
 
656
  Field(
657
  name="questions",
658
  type="array<string>",
659
+ indexing=["summary", "attribute"],
660
+ summary=Summary(fields=["matched-elements-only"]),
 
661
  ),
662
  Field(
663
  name="queries",
664
  type="array<string>",
665
+ indexing=["summary", "attribute"],
666
+ summary=Summary(fields=["matched-elements-only"]),
 
667
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  ]
669
  ),
670
  fieldsets=[
 
829
  colpali_schema.add_rank_profile(colpali_retrieval_profile)
830
  colpali_schema.add_rank_profile(with_quantized_similarity(colpali_retrieval_profile))
831
 
832
+ # +
833
  from vespa.configuration.services import (
834
  services,
835
  container,
 
869
  id="token_write",
870
  permissions="read,write",
871
  ),
 
 
 
 
 
872
  ),
873
  config(
874
  vt("tag")(
 
900
  version="1.0",
901
  ),
902
  )
903
+ # -
904
 
 
905
  # Create the Vespa application package
906
  vespa_application_package = ApplicationPackage(
907
  name=VESPA_APPLICATION_NAME,
 
909
  services_config=service_config,
910
  )
911
 
 
912
  # ## 6. Deploy Vespa Application
913
  #
914
 
 
915
  VESPA_TEAM_API_KEY = os.getenv("VESPA_TEAM_API_KEY") or input(
916
  "Enter Vespa team API key: "
917
  )
918
 
919
+ # +
920
  vespa_cloud = VespaCloud(
921
  tenant=VESPA_TENANT_NAME,
922
  application=VESPA_APPLICATION_NAME,
 
930
  # Output the endpoint URL
931
  endpoint_url = vespa_cloud.get_token_endpoint()
932
  print(f"Application deployed. Token endpoint URL: {endpoint_url}")
933
+ # -
934
 
 
935
  # Make sure to take note of the token endpoint_url.
936
  # 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.
937
  #
938
 
 
939
  # ## 8. Feed Data to Vespa
940
  #
941
 
 
942
  # Instantiate Vespa connection using token
943
  app = Vespa(url=endpoint_url, vespa_cloud_secret_token=VESPA_CLOUD_SECRET_TOKEN)
944
  app.get_application_status()
945
 
946
 
947
+ # +
948
  def callback(response: VespaResponse, id: str):
949
  if not response.is_successful():
950
  print(
pyproject.toml CHANGED
@@ -3,7 +3,7 @@ name = "visual-retrieval-colpali"
3
  version = "0.1.0"
4
  description = "Visual retrieval with ColPali"
5
  readme = "README.md"
6
- requires-python = ">=3.10"
7
  license = { text = "Apache-2.0" }
8
  dependencies = [
9
  "python-fasthtml",
@@ -37,4 +37,82 @@ feed = [
37
  "beautifulsoup4",
38
  "pdf2image",
39
  "google-generativeai"
40
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  version = "0.1.0"
4
  description = "Visual retrieval with ColPali"
5
  readme = "README.md"
6
+ requires-python = ">=3.10, <3.13"
7
  license = { text = "Apache-2.0" }
8
  dependencies = [
9
  "python-fasthtml",
 
37
  "beautifulsoup4",
38
  "pdf2image",
39
  "google-generativeai"
40
+ ]
41
+ [tool.ruff]
42
+ # Exclude a variety of commonly ignored directories.
43
+ exclude = [
44
+ ".bzr",
45
+ ".direnv",
46
+ ".eggs",
47
+ ".git",
48
+ ".git-rewrite",
49
+ ".hg",
50
+ ".ipynb_checkpoints",
51
+ ".mypy_cache",
52
+ ".nox",
53
+ ".pants.d",
54
+ ".pyenv",
55
+ ".pytest_cache",
56
+ ".pytype",
57
+ ".ruff_cache",
58
+ ".svn",
59
+ ".tox",
60
+ ".venv",
61
+ ".vscode",
62
+ "__pypackages__",
63
+ "_build",
64
+ "buck-out",
65
+ "build",
66
+ "dist",
67
+ "node_modules",
68
+ "site-packages",
69
+ "venv",
70
+ ]
71
+
72
+ # Same as Black.
73
+ line-length = 88
74
+ indent-width = 4
75
+
76
+ # Assume Python 3.8
77
+ target-version = "py38"
78
+
79
+ [tool.ruff.lint]
80
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
81
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
82
+ # McCabe complexity (`C901`) by default.
83
+ select = ["E4", "E7", "E9", "F"]
84
+ ignore = []
85
+
86
+ # Allow fix for all enabled rules (when `--fix`) is provided.
87
+ fixable = ["ALL"]
88
+ unfixable = []
89
+
90
+ # Allow unused variables when underscore-prefixed.
91
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
92
+
93
+ [tool.ruff.format]
94
+ # Like Black, use double quotes for strings.
95
+ quote-style = "double"
96
+
97
+ # Like Black, indent with spaces, rather than tabs.
98
+ indent-style = "space"
99
+
100
+ # Like Black, respect magic trailing commas.
101
+ skip-magic-trailing-comma = false
102
+
103
+ # Like Black, automatically detect the appropriate line ending.
104
+ line-ending = "auto"
105
+
106
+ # Enable auto-formatting of code examples in docstrings. Markdown,
107
+ # reStructuredText code/literal blocks and doctests are all supported.
108
+ #
109
+ # This is currently disabled by default, but it is planned for this
110
+ # to be opt-out in the future.
111
+ docstring-code-format = false
112
+
113
+ # Set the line length limit used when formatting code snippets in
114
+ # docstrings.
115
+ #
116
+ # This only has an effect when the `docstring-code-format` setting is
117
+ # enabled.
118
+ 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,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_default(
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 default ranking profile.
118
+
119
+ Args:
120
+ query (str): The query text.
121
+ q_emb (torch.Tensor): Query embeddings.
122
+ hits (int, optional): Number of hits to retrieve. Defaults to 3.
123
+ timeout (str, optional): Query timeout. Defaults to "10s".
124
+
125
+ Returns:
126
+ dict: The formatted query results.
127
+ """
128
+ async with self.app.asyncio(connections=1) as session:
129
+ query_embedding = self.format_q_embs(q_emb)
130
+
131
+ start = time.perf_counter()
132
+ response: VespaQueryResponse = await session.query(
133
+ body={
134
+ "yql": (
135
+ f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where userQuery();"
136
+ ),
137
+ "ranking": self.get_rank_profile("default", sim_map),
138
+ "query": query,
139
+ "timeout": timeout,
140
+ "hits": hits,
141
+ "input.query(qt)": query_embedding,
142
+ "presentation.timing": True,
143
+ **kwargs,
144
+ },
145
+ )
146
+ assert response.is_successful(), response.json
147
+ stop = time.perf_counter()
148
+ self.logger.debug(
149
+ f"Query time + data transfer took: {stop - start} s, Vespa reported searchtime was "
150
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
151
+ )
152
+ return self.format_query_results(query, response)
153
+
154
+ async def query_vespa_bm25(
155
+ self,
156
+ query: str,
157
+ q_emb: torch.Tensor,
158
+ hits: int = 3,
159
+ timeout: str = "10s",
160
+ sim_map: bool = False,
161
+ **kwargs,
162
+ ) -> dict:
163
+ """
164
+ Query Vespa using the BM25 ranking profile.
165
+
166
+ Args:
167
+ query (str): The query text.
168
+ q_emb (torch.Tensor): Query embeddings.
169
+ hits (int, optional): Number of hits to retrieve. Defaults to 3.
170
+ timeout (str, optional): Query timeout. Defaults to "10s".
171
+
172
+ Returns:
173
+ dict: The formatted query results.
174
+ """
175
+ async with self.app.asyncio(connections=1) as session:
176
+ query_embedding = self.format_q_embs(q_emb)
177
+
178
+ start = time.perf_counter()
179
+ response: VespaQueryResponse = await session.query(
180
+ body={
181
+ "yql": (
182
+ f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where userQuery();"
183
+ ),
184
+ "ranking": self.get_rank_profile("bm25", sim_map),
185
+ "query": query,
186
+ "timeout": timeout,
187
+ "hits": hits,
188
+ "input.query(qt)": query_embedding,
189
+ "presentation.timing": True,
190
+ **kwargs,
191
+ },
192
+ )
193
+ assert response.is_successful(), response.json
194
+ stop = time.perf_counter()
195
+ self.logger.debug(
196
+ f"Query time + data transfer took: {stop - start} s, Vespa reported searchtime was "
197
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
198
+ )
199
+ return self.format_query_results(query, response)
200
+
201
+ def float_to_binary_embedding(self, float_query_embedding: dict) -> dict:
202
+ """
203
+ Convert float query embeddings to binary embeddings.
204
+
205
+ Args:
206
+ float_query_embedding (dict): Dictionary of float embeddings.
207
+
208
+ Returns:
209
+ dict: Dictionary of binary embeddings.
210
+ """
211
+ binary_query_embeddings = {}
212
+ for key, vector in float_query_embedding.items():
213
+ binary_vector = (
214
+ np.packbits(np.where(np.array(vector) > 0, 1, 0))
215
+ .astype(np.int8)
216
+ .tolist()
217
+ )
218
+ binary_query_embeddings[key] = binary_vector
219
+ if len(binary_query_embeddings) >= self.MAX_QUERY_TERMS:
220
+ self.logger.warning(
221
+ f"Warning: Query has more than {self.MAX_QUERY_TERMS} terms. Truncating."
222
+ )
223
+ break
224
+ return binary_query_embeddings
225
+
226
+ def create_nn_query_strings(
227
+ self, binary_query_embeddings: dict, target_hits_per_query_tensor: int = 20
228
+ ) -> Tuple[str, dict]:
229
+ """
230
+ Create nearest neighbor query strings for Vespa.
231
+
232
+ Args:
233
+ binary_query_embeddings (dict): Binary query embeddings.
234
+ target_hits_per_query_tensor (int, optional): Target hits per query tensor. Defaults to 20.
235
+
236
+ Returns:
237
+ Tuple[str, dict]: Nearest neighbor query string and query tensor dictionary.
238
+ """
239
+ nn_query_dict = {}
240
+ for i in range(len(binary_query_embeddings)):
241
+ nn_query_dict[f"input.query(rq{i})"] = binary_query_embeddings[i]
242
+ nn = " OR ".join(
243
+ [
244
+ f"({{targetHits:{target_hits_per_query_tensor}}}nearestNeighbor(embedding,rq{i}))"
245
+ for i in range(len(binary_query_embeddings))
246
+ ]
247
+ )
248
+ return nn, nn_query_dict
249
+
250
+ def format_q_embs(self, q_embs: torch.Tensor) -> dict:
251
+ """
252
+ Convert query embeddings to a dictionary of lists.
253
+
254
+ Args:
255
+ q_embs (torch.Tensor): Query embeddings tensor.
256
+
257
+ Returns:
258
+ dict: Dictionary where each key is an index and value is the embedding list.
259
+ """
260
+ return {idx: emb.tolist() for idx, emb in enumerate(q_embs)}
261
+
262
+ async def get_result_from_query(
263
+ self,
264
+ query: str,
265
+ q_embs: torch.Tensor,
266
+ ranking: str,
267
+ idx_to_token: dict,
268
+ ) -> Dict[str, Any]:
269
+ """
270
+ Get query results from Vespa based on the ranking method.
271
+
272
+ Args:
273
+ query (str): The query text.
274
+ q_embs (torch.Tensor): Query embeddings.
275
+ ranking (str): The ranking method to use.
276
+ idx_to_token (dict): Index to token mapping.
277
+
278
+ Returns:
279
+ Dict[str, Any]: The query results.
280
+ """
281
+
282
+ # Remove stopwords from the query to avoid visual emphasis on irrelevant words (e.g., "the", "and", "of")
283
+ query = backend.stopwords.filter(query)
284
+
285
+ rank_method = ranking.split("_")[0]
286
+ sim_map: bool = len(ranking.split("_")) > 1 and ranking.split("_")[1] == "sim"
287
+ if rank_method == "nn+colpali":
288
+ result = await self.query_vespa_nearest_neighbor(
289
+ query, q_embs, sim_map=sim_map
290
+ )
291
+ elif rank_method == "bm25+colpali":
292
+ result = await self.query_vespa_default(query, q_embs, sim_map=sim_map)
293
+ elif rank_method == "bm25":
294
+ result = await self.query_vespa_bm25(query, q_embs, sim_map=sim_map)
295
+ else:
296
+ raise ValueError(f"Unsupported ranking: {rank_method}")
297
+ if "root" not in result or "children" not in result["root"]:
298
+ result["root"] = {"children": []}
299
+ return result
300
+ for single_result in result["root"]["children"]:
301
+ self.logger.debug(single_result["fields"].keys())
302
+ return result
303
+
304
+ def get_sim_maps_from_query(
305
+ self, query: str, q_embs: torch.Tensor, ranking: str, idx_to_token: dict
306
+ ):
307
+ """
308
+ Get similarity maps from Vespa based on the ranking method.
309
+
310
+ Args:
311
+ query (str): The query text.
312
+ q_embs (torch.Tensor): Query embeddings.
313
+ ranking (str): The ranking method to use.
314
+ idx_to_token (dict): Index to token mapping.
315
+
316
+ Returns:
317
+ Dict[str, Any]: The query results.
318
+ """
319
+ # Get the result by calling asyncio.run
320
+ result = asyncio.run(
321
+ self.get_result_from_query(query, q_embs, ranking, idx_to_token)
322
+ )
323
+ vespa_sim_maps = []
324
+ for single_result in result["root"]["children"]:
325
+ vespa_sim_map = single_result["fields"].get("summaryfeatures", None)
326
+ if vespa_sim_map is not None:
327
+ vespa_sim_maps.append(vespa_sim_map)
328
+ else:
329
+ raise ValueError("No sim_map found in Vespa response")
330
+ return vespa_sim_maps
331
+
332
+ async def get_full_image_from_vespa(self, doc_id: str) -> str:
333
+ """
334
+ Retrieve the full image from Vespa for a given document ID.
335
+
336
+ Args:
337
+ doc_id (str): The document ID.
338
+
339
+ Returns:
340
+ str: The full image data.
341
+ """
342
+ async with self.app.asyncio(connections=1) as session:
343
+ start = time.perf_counter()
344
+ response: VespaQueryResponse = await session.query(
345
+ body={
346
+ "yql": f'select full_image from {self.VESPA_SCHEMA_NAME} where id contains "{doc_id}"',
347
+ "ranking": "unranked",
348
+ "presentation.timing": True,
349
+ },
350
+ )
351
+ assert response.is_successful(), response.json
352
+ stop = time.perf_counter()
353
+ self.logger.debug(
354
+ f"Getting image from Vespa took: {stop - start} s, Vespa reported searchtime was "
355
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
356
+ )
357
+ return response.json["root"]["children"][0]["fields"]["full_image"]
358
+
359
+ def get_results_children(self, result: VespaQueryResponse) -> list:
360
+ return result["root"]["children"]
361
+
362
+ def results_to_search_results(
363
+ self, result: VespaQueryResponse, idx_to_token: dict
364
+ ) -> list:
365
+ # Initialize sim_map_ fields in the result
366
+ fields_to_add = [
367
+ f"sim_map_{token}_{idx}"
368
+ for idx, token in idx_to_token.items()
369
+ if not SimMapGenerator.should_filter_token(token)
370
+ ]
371
+ for child in result["root"]["children"]:
372
+ for sim_map_key in fields_to_add:
373
+ child["fields"][sim_map_key] = None
374
+ return self.get_results_children(result)
375
+
376
+ async def get_suggestions(self, query: str) -> list:
377
+ async with self.app.asyncio(connections=1) as session:
378
+ start = time.perf_counter()
379
+ yql = f'select questions from {self.VESPA_SCHEMA_NAME} where questions matches (".*{query}.*")'
380
+ response: VespaQueryResponse = await session.query(
381
+ body={
382
+ "yql": yql,
383
+ "query": query,
384
+ "ranking": "unranked",
385
+ "presentation.timing": True,
386
+ "presentation.summary": "suggestions",
387
+ },
388
+ )
389
+ assert response.is_successful(), response.json
390
+ stop = time.perf_counter()
391
+ self.logger.debug(
392
+ f"Getting suggestions from Vespa took: {stop - start} s, Vespa reported searchtime was "
393
+ f"{response.json.get('timing', {}).get('searchtime', -1)} s"
394
+ )
395
+ search_results = (
396
+ response.json["root"]["children"]
397
+ if "root" in response.json and "children" in response.json["root"]
398
+ else []
399
+ )
400
+ questions = [
401
+ result["fields"]["questions"]
402
+ for result in search_results
403
+ if "questions" in result["fields"]
404
+ ]
405
+ for q in questions:
406
+ print(q)
407
+ unique_questions = set([item for sublist in questions for item in sublist])
408
+
409
+ # remove an artifact from our data generation
410
+ if "string" in unique_questions:
411
+ unique_questions.remove("string")
412
+
413
+ return list(unique_questions)
414
+
415
+ def get_rank_profile(self, ranking: str, sim_map: bool) -> str:
416
+ if sim_map:
417
+ return f"{ranking}_sim"
418
+ else:
419
+ return ranking
420
+
421
+ async def query_vespa_nearest_neighbor(
422
+ self,
423
+ query: str,
424
+ q_emb: torch.Tensor,
425
+ target_hits_per_query_tensor: int = 20,
426
+ hits: int = 3,
427
+ timeout: str = "10s",
428
+ sim_map: bool = False,
429
+ **kwargs,
430
+ ) -> dict:
431
+ """
432
+ Query Vespa using nearest neighbor search with mixed tensors for MaxSim calculations.
433
+
434
+ Args:
435
+ query (str): The query text.
436
+ q_emb (torch.Tensor): Query embeddings.
437
+ target_hits_per_query_tensor (int, optional): Target hits per query tensor. Defaults to 20.
438
+ hits (int, optional): Number of hits to retrieve. Defaults to 3.
439
+ timeout (str, optional): Query timeout. Defaults to "10s".
440
+
441
+ Returns:
442
+ dict: The formatted query results.
443
+ """
444
+ async with self.app.asyncio(connections=1) as session:
445
+ float_query_embedding = self.format_q_embs(q_emb)
446
+ binary_query_embeddings = self.float_to_binary_embedding(
447
+ float_query_embedding
448
+ )
449
+
450
+ # Mixed tensors for MaxSim calculations
451
+ query_tensors = {
452
+ "input.query(qtb)": binary_query_embeddings,
453
+ "input.query(qt)": float_query_embedding,
454
+ }
455
+ nn_string, nn_query_dict = self.create_nn_query_strings(
456
+ binary_query_embeddings, target_hits_per_query_tensor
457
+ )
458
+ query_tensors.update(nn_query_dict)
459
+ response: VespaQueryResponse = await session.query(
460
+ body={
461
+ **query_tensors,
462
+ "presentation.timing": True,
463
+ "yql": (
464
+ f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where {nn_string} or userQuery()"
465
+ ),
466
+ "ranking.profile": self.get_rank_profile(
467
+ "retrieval-and-rerank", sim_map
468
+ ),
469
+ "timeout": timeout,
470
+ "hits": hits,
471
+ "query": query,
472
+ **kwargs,
473
+ },
474
+ )
475
+ assert response.is_successful(), response.json
476
+ return self.format_query_results(query, response)
477
+
478
+ async def keepalive(self) -> bool:
479
+ """
480
+ Query Vespa to keep the connection alive.
481
+
482
+ Returns:
483
+ bool: True if the connection is alive.
484
+ """
485
+ async with self.app.asyncio(connections=1) as session:
486
+ response: VespaQueryResponse = await session.query(
487
+ body={
488
+ "yql": f"select title from {self.VESPA_SCHEMA_NAME} where true limit 1;",
489
+ "ranking": "unranked",
490
+ "query": "keepalive",
491
+ "timeout": "3s",
492
+ "hits": 1,
493
+ },
494
+ )
495
+ assert response.is_successful(), response.json
496
+ return True
src/frontend/__init__.py ADDED
File without changes
src/frontend/app.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from urllib.parse import quote_plus
3
+
4
+ from fasthtml.components import H1, H2, H3, Br, Div, Form, Img, NotStr, P, Span, Strong
5
+ from fasthtml.xtend import A, Script
6
+ from lucide_fasthtml import Lucide
7
+ from shad4fast import Badge, Button, Input, Label, RadioGroup, RadioGroupItem, Separator
8
+
9
+ # JavaScript to check the input value and enable/disable the search button and radio buttons
10
+ check_input_script = Script(
11
+ """
12
+ window.onload = function() {
13
+ const input = document.getElementById('search-input');
14
+ const button = document.querySelector('[data-button="search-button"]');
15
+ const radioGroupItems = document.querySelectorAll('button[data-ref="radio-item"]'); // Get all radio buttons
16
+
17
+ function checkInputValue() {
18
+ const isInputEmpty = input.value.trim() === "";
19
+ button.disabled = isInputEmpty; // Disable the submit button
20
+ radioGroupItems.forEach(item => {
21
+ item.disabled = isInputEmpty; // Disable/enable the radio buttons
22
+ });
23
+ }
24
+
25
+ input.addEventListener('input', checkInputValue); // Listen for input changes
26
+ checkInputValue(); // Initial check when the page loads
27
+ };
28
+ """
29
+ )
30
+
31
+ # JavaScript to handle the image swapping, reset button, and active class toggling
32
+ image_swapping = Script(
33
+ """
34
+ document.addEventListener('click', function (e) {
35
+ if (e.target.classList.contains('sim-map-button') || e.target.classList.contains('reset-button')) {
36
+ const imgContainer = e.target.closest('.relative');
37
+ const overlayContainer = imgContainer.querySelector('.overlay-container');
38
+ const newSrc = e.target.getAttribute('data-image-src');
39
+
40
+ // If it's a reset button, remove the overlay image
41
+ if (e.target.classList.contains('reset-button')) {
42
+ overlayContainer.innerHTML = ''; // Clear the overlay container, showing only the full image
43
+ } else {
44
+ // Create a new overlay image
45
+ const img = document.createElement('img');
46
+ img.src = newSrc;
47
+ img.classList.add('overlay-image', 'absolute', 'top-0', 'left-0', 'w-full', 'h-full');
48
+ overlayContainer.innerHTML = ''; // Clear any previous overlay
49
+ overlayContainer.appendChild(img); // Add the new overlay image
50
+ }
51
+
52
+ // Toggle active class on buttons
53
+ const activeButton = document.querySelector('.sim-map-button.active');
54
+ if (activeButton) {
55
+ activeButton.classList.remove('active');
56
+ }
57
+ if (e.target.classList.contains('sim-map-button')) {
58
+ e.target.classList.add('active');
59
+ }
60
+ }
61
+ });
62
+ """
63
+ )
64
+
65
+ toggle_text_content = Script(
66
+ """
67
+ function toggleTextContent(idx) {
68
+ const textColumn = document.getElementById(`text-column-${idx}`);
69
+ const imageTextColumns = document.getElementById(`image-text-columns-${idx}`);
70
+ const toggleButton = document.getElementById(`toggle-button-${idx}`);
71
+
72
+ if (textColumn.classList.contains('md-grid-text-column')) {
73
+ // Hide the text column
74
+ textColumn.classList.remove('md-grid-text-column');
75
+ imageTextColumns.classList.remove('grid-image-text-columns');
76
+ toggleButton.innerText = `Show Text`;
77
+ } else {
78
+ // Show the text column
79
+ textColumn.classList.add('md-grid-text-column');
80
+ imageTextColumns.classList.add('grid-image-text-columns');
81
+ toggleButton.innerText = `Hide Text`;
82
+ }
83
+ }
84
+ """
85
+ )
86
+
87
+ autocomplete_script = Script(
88
+ """
89
+ document.addEventListener('DOMContentLoaded', function() {
90
+ const input = document.querySelector('#search-input');
91
+ const awesomplete = new Awesomplete(input, { minChars: 1, maxItems: 5 });
92
+
93
+ input.addEventListener('input', function() {
94
+ if (this.value.length >= 1) {
95
+ // Use template literals to insert the input value dynamically in the query parameter
96
+ fetch(`/suggestions?query=${encodeURIComponent(this.value)}`)
97
+ .then(response => response.json())
98
+ .then(data => {
99
+ // Update the Awesomplete list dynamically with fetched suggestions
100
+ awesomplete.list = data.suggestions;
101
+ })
102
+ .catch(err => console.error('Error fetching suggestions:', err));
103
+ }
104
+ });
105
+ });
106
+ """
107
+ )
108
+
109
+ dynamic_elements_scrollbars = Script(
110
+ """
111
+ (function () {
112
+ const { applyOverlayScrollbars, getScrollbarTheme } = OverlayScrollbarsManager;
113
+
114
+ function applyScrollbarsToDynamicElements() {
115
+ const scrollbarTheme = getScrollbarTheme();
116
+
117
+ // Apply scrollbars to dynamically loaded result-text-full and result-text-snippet elements
118
+ const resultTextFullElements = document.querySelectorAll('[id^="result-text-full"]');
119
+ const resultTextSnippetElements = document.querySelectorAll('[id^="result-text-snippet"]');
120
+
121
+ resultTextFullElements.forEach(element => {
122
+ applyOverlayScrollbars(element, scrollbarTheme);
123
+ });
124
+
125
+ resultTextSnippetElements.forEach(element => {
126
+ applyOverlayScrollbars(element, scrollbarTheme);
127
+ });
128
+ }
129
+
130
+ // Apply scrollbars after dynamic content is loaded (e.g., after search results)
131
+ applyScrollbarsToDynamicElements();
132
+
133
+ // Observe changes in the 'dark' class to adjust the theme dynamically if needed
134
+ const observer = new MutationObserver(applyScrollbarsToDynamicElements);
135
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
136
+ })();
137
+ """
138
+ )
139
+
140
+ submit_form_on_radio_change = Script(
141
+ """
142
+ document.addEventListener('click', function (e) {
143
+ // if target has data-ref="radio-item" and type is button
144
+ if (e.target.getAttribute('data-ref') === 'radio-item' && e.target.type === 'button') {
145
+ console.log('Radio button clicked');
146
+ const form = e.target.closest('form');
147
+ form.submit();
148
+ }
149
+ });
150
+ """
151
+ )
152
+
153
+
154
+ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
155
+ grid_cls = "grid gap-2 items-center p-3 bg-muted w-full"
156
+
157
+ if with_border:
158
+ 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"
159
+
160
+ return Form(
161
+ Div(
162
+ Lucide(
163
+ icon="search", cls="absolute left-2 top-2 text-muted-foreground z-10"
164
+ ),
165
+ Input(
166
+ placeholder="Enter your search query...",
167
+ name="query",
168
+ value=query_value,
169
+ id="search-input",
170
+ cls="text-base pl-10 border-transparent ring-offset-transparent ring-0 focus-visible:ring-transparent bg-white dark:bg-background awesomplete",
171
+ data_list="#suggestions",
172
+ style="font-size: 1rem",
173
+ autofocus=True,
174
+ ),
175
+ cls="relative",
176
+ ),
177
+ Div(
178
+ Div(
179
+ Span("Ranking by:", cls="text-muted-foreground text-xs font-semibold"),
180
+ RadioGroup(
181
+ Div(
182
+ RadioGroupItem(value="nn+colpali", id="nn+colpali"),
183
+ Label("ColPali", htmlFor="ColPali"),
184
+ cls="flex items-center space-x-2",
185
+ ),
186
+ Div(
187
+ RadioGroupItem(value="bm25", id="bm25"),
188
+ Label("BM25", htmlFor="BM25"),
189
+ cls="flex items-center space-x-2",
190
+ ),
191
+ Div(
192
+ RadioGroupItem(value="bm25+colpali", id="bm25+colpali"),
193
+ Label("Hybrid ColPali + BM25", htmlFor="Hybrid ColPali + BM25"),
194
+ cls="flex items-center space-x-2",
195
+ ),
196
+ name="ranking",
197
+ default_value=ranking_value,
198
+ cls="grid-flow-col gap-x-5 text-muted-foreground",
199
+ # Submit form when radio button is clicked
200
+ ),
201
+ cls="grid grid-flow-col items-center gap-x-3 border border-input px-3 rounded-sm",
202
+ ),
203
+ Button(
204
+ Lucide(icon="arrow-right", size="21"),
205
+ size="sm",
206
+ type="submit",
207
+ data_button="search-button",
208
+ disabled=True,
209
+ ),
210
+ cls="flex justify-between",
211
+ ),
212
+ check_input_script,
213
+ autocomplete_script,
214
+ submit_form_on_radio_change,
215
+ action=f"/search?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
216
+ method="GET",
217
+ 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
218
+ hx_trigger="load",
219
+ hx_target="#search-results",
220
+ hx_swap="outerHTML",
221
+ hx_indicator="#loading-indicator",
222
+ cls=grid_cls,
223
+ )
224
+
225
+
226
+ def SampleQueries():
227
+ sample_queries = [
228
+ "Total amount of fixed salaries paid in 2023?",
229
+ "Proportion of female new hires 2021-2023?",
230
+ "Number of internship applications trend 2021-2023",
231
+ "Gender balance at level 4 or above in NY office 2023?",
232
+ "What percentage of the funds unlisted real estate investments were in Switzerland 2023?",
233
+ "child jumping over puddle",
234
+ "hula hoop kid",
235
+ ]
236
+
237
+ query_badges = []
238
+ for query in sample_queries:
239
+ query_badges.append(
240
+ A(
241
+ Badge(
242
+ Div(
243
+ Lucide(
244
+ icon="text-search", size="18", cls="text-muted-foreground"
245
+ ),
246
+ Span(query, cls="text-base font-normal"),
247
+ cls="flex gap-2 items-center",
248
+ ),
249
+ variant="outline",
250
+ cls="text-base font-normal text-muted-foreground hover:border-black dark:hover:border-white",
251
+ ),
252
+ href=f"/search?query={quote_plus(query)}",
253
+ cls="no-underline",
254
+ )
255
+ )
256
+
257
+ return Div(*query_badges, cls="grid gap-2 justify-items-center")
258
+
259
+
260
+ def Hero():
261
+ return Div(
262
+ H1(
263
+ "Vespa.ai + ColPali",
264
+ cls="text-5xl md:text-7xl 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",
265
+ ),
266
+ P(
267
+ "Efficient Document Retrieval with Vision Language Models",
268
+ cls="text-lg md:text-2xl text-muted-foreground md:tracking-wide",
269
+ ),
270
+ cls="grid gap-5 text-center",
271
+ )
272
+
273
+
274
+ def Home():
275
+ return Div(
276
+ Div(
277
+ Hero(),
278
+ SearchBox(with_border=True),
279
+ SampleQueries(),
280
+ cls="grid gap-8 content-start mt-[13vh]",
281
+ ),
282
+ cls="grid w-full h-full max-w-screen-md gap-4 mx-auto",
283
+ )
284
+
285
+
286
+ def AboutThisDemo():
287
+ return Div(
288
+ Div(
289
+ Div(
290
+ H1(
291
+ "Vespa.ai + ColPali",
292
+ cls="text-5xl font-bold tracking-wide md:tracking-wider",
293
+ ),
294
+ P(
295
+ "Efficient Document Retrieval with Vision Language Models",
296
+ cls="text-lg text-muted-foreground md:tracking-wide",
297
+ ),
298
+ Div(
299
+ Img(
300
+ src="/static/img/vespa-colpali.png",
301
+ alt="Vespa and ColPali",
302
+ cls="object-contain h-[377px]",
303
+ ),
304
+ cls="grid justify-center",
305
+ ),
306
+ Div(
307
+ P(
308
+ "This is a demo application showcasing the integration of Vespa.ai and ColPali for visual retrieval of documents.",
309
+ cls="text-base",
310
+ ),
311
+ P(
312
+ "The application uses a combination of neural networks and traditional search algorithms to retrieve relevant documents based on visual and textual queries.",
313
+ cls="text-base",
314
+ ),
315
+ cls="grid gap-2 text-center",
316
+ ),
317
+ cls="grid gap-5 text-center",
318
+ ),
319
+ cls="grid gap-8 content-start mt-[8vh]",
320
+ ),
321
+ cls="grid w-full h-full max-w-screen-md gap-4 mx-auto",
322
+ )
323
+
324
+
325
+ def Search(request, search_results=[]):
326
+ query_value = request.query_params.get("query", "").strip()
327
+ ranking_value = request.query_params.get("ranking", "nn+colpali")
328
+ return Div(
329
+ Div(
330
+ Div(
331
+ SearchBox(query_value=query_value, ranking_value=ranking_value),
332
+ Div(
333
+ LoadingMessage(),
334
+ id="search-results", # This will be replaced by the search results
335
+ ),
336
+ cls="grid",
337
+ ),
338
+ cls="grid",
339
+ ),
340
+ )
341
+
342
+
343
+ def LoadingMessage(display_text="Retrieving search results"):
344
+ return Div(
345
+ Lucide(icon="loader-circle", cls="size-5 mr-1.5 animate-spin"),
346
+ Span(display_text, cls="text-base text-center"),
347
+ cls="p-10 text-muted-foreground flex items-center justify-center",
348
+ id="loading-indicator",
349
+ )
350
+
351
+
352
+ def LoadingSkeleton():
353
+ return Div(
354
+ Div(cls="h-5 bg-muted"),
355
+ Div(cls="h-5 bg-muted"),
356
+ Div(cls="h-5 bg-muted"),
357
+ cls="grid gap-2 animate-pulse",
358
+ )
359
+
360
+
361
+ def SimMapButtonReady(query_id, idx, token, token_idx, img_src):
362
+ return Button(
363
+ token.replace("\u2581", ""),
364
+ size="sm",
365
+ data_image_src=img_src,
366
+ id=f"sim-map-button-{query_id}-{idx}-{token_idx}-{token}",
367
+ cls="sim-map-button pointer-events-auto font-mono text-xs h-5 rounded-none px-2",
368
+ )
369
+
370
+
371
+ def SimMapButtonPoll(query_id, idx, token, token_idx):
372
+ return Button(
373
+ Lucide(icon="loader-circle", size="15", cls="animate-spin"),
374
+ size="sm",
375
+ disabled=True,
376
+ hx_get=f"/get_sim_map?query_id={query_id}&idx={idx}&token={token}&token_idx={token_idx}",
377
+ hx_trigger="every 0.5s",
378
+ hx_swap="outerHTML",
379
+ cls="pointer-events-auto text-xs h-5 rounded-none px-2",
380
+ )
381
+
382
+
383
+ def SearchInfo(search_time, total_count):
384
+ return (
385
+ Div(
386
+ Span(
387
+ "Retrieved ",
388
+ Strong(total_count),
389
+ Span(" results"),
390
+ Span(" in "),
391
+ Strong(f"{search_time:.3f}"), # 3 significant digits
392
+ Span(" seconds."),
393
+ ),
394
+ cls="grid bg-background border-t text-sm text-center p-3",
395
+ ),
396
+ )
397
+
398
+
399
+ def SearchResult(
400
+ results: list,
401
+ query: str,
402
+ query_id: Optional[str] = None,
403
+ search_time: float = 0,
404
+ total_count: int = 0,
405
+ ):
406
+ if not results:
407
+ return Div(
408
+ P(
409
+ "No results found for your query.",
410
+ cls="text-muted-foreground text-base text-center",
411
+ ),
412
+ cls="grid p-10",
413
+ )
414
+
415
+ doc_ids = []
416
+ # Otherwise, display the search results
417
+ result_items = []
418
+ for idx, result in enumerate(results):
419
+ fields = result["fields"] # Extract the 'fields' part of each result
420
+ doc_id = fields["id"]
421
+ doc_ids.append(doc_id)
422
+ blur_image_base64 = f"data:image/jpeg;base64,{fields['blur_image']}"
423
+
424
+ sim_map_fields = {
425
+ key: value
426
+ for key, value in fields.items()
427
+ if key.startswith(
428
+ "sim_map_"
429
+ ) # filtering is done before creating with 'should_filter_token'-function
430
+ }
431
+
432
+ # Generate buttons for the sim_map fields
433
+ sim_map_buttons = []
434
+ for key, value in sim_map_fields.items():
435
+ token = key.split("_")[-2]
436
+ token_idx = int(key.split("_")[-1])
437
+ if value is not None:
438
+ sim_map_base64 = f"data:image/jpeg;base64,{value}"
439
+ sim_map_buttons.append(
440
+ SimMapButtonReady(
441
+ query_id=query_id,
442
+ idx=idx,
443
+ token=token,
444
+ token_idx=token_idx,
445
+ img_src=sim_map_base64,
446
+ )
447
+ )
448
+ else:
449
+ sim_map_buttons.append(
450
+ SimMapButtonPoll(
451
+ query_id=query_id,
452
+ idx=idx,
453
+ token=token,
454
+ token_idx=token_idx,
455
+ )
456
+ )
457
+
458
+ # Add "Reset Image" button to restore the full image
459
+ reset_button = Button(
460
+ "Reset",
461
+ variant="outline",
462
+ size="sm",
463
+ data_image_src=blur_image_base64,
464
+ cls="reset-button pointer-events-auto font-mono text-xs h-5 rounded-none px-2",
465
+ )
466
+
467
+ tokens_icon = Lucide(icon="images", size="15")
468
+
469
+ # Add "Tokens" button - this has no action, just a placeholder
470
+ tokens_button = Button(
471
+ tokens_icon,
472
+ "Tokens",
473
+ size="sm",
474
+ cls="tokens-button flex gap-[3px] font-bold pointer-events-none font-mono text-xs h-5 rounded-none px-2",
475
+ )
476
+
477
+ result_items.append(
478
+ Div(
479
+ Div(
480
+ Div(
481
+ Lucide(icon="file-text"),
482
+ H2(fields["title"], cls="text-xl md:text-2xl font-semibold"),
483
+ Separator(orientation="vertical"),
484
+ Badge(
485
+ f"Relevance score: {result['relevance']:.4f}",
486
+ cls="flex gap-1.5 items-center justify-center",
487
+ ),
488
+ cls="flex items-center gap-2",
489
+ ),
490
+ Div(
491
+ Button(
492
+ "Hide Text",
493
+ size="sm",
494
+ id=f"toggle-button-{idx}",
495
+ onclick=f"toggleTextContent({idx})",
496
+ cls="hidden md:block",
497
+ ),
498
+ ),
499
+ cls="flex flex-wrap items-center justify-between bg-background px-3 py-4",
500
+ ),
501
+ Div(
502
+ Div(
503
+ Div(
504
+ tokens_button,
505
+ *sim_map_buttons,
506
+ reset_button,
507
+ cls="flex flex-wrap gap-px w-full pointer-events-none",
508
+ ),
509
+ Div(
510
+ Div(
511
+ Div(
512
+ Img(
513
+ src=blur_image_base64,
514
+ hx_get=f"/full_image?doc_id={doc_id}",
515
+ style="backdrop-filter: blur(5px);",
516
+ hx_trigger="load",
517
+ hx_swap="outerHTML",
518
+ alt=fields["title"],
519
+ cls="result-image w-full h-full object-contain",
520
+ ),
521
+ Div(
522
+ cls="overlay-container absolute top-0 left-0 w-full h-full pointer-events-none"
523
+ ),
524
+ cls="relative w-full h-full",
525
+ ),
526
+ cls="grid bg-muted p-2",
527
+ ),
528
+ cls="block",
529
+ ),
530
+ id=f"image-column-{idx}",
531
+ cls="image-column relative bg-background px-3 py-5 grid-image-column",
532
+ ),
533
+ Div(
534
+ Div(
535
+ A(
536
+ Lucide(icon="external-link", size="18"),
537
+ f"PDF Source (Page {fields['page_number'] + 1})",
538
+ href=f"{fields['url']}#page={fields['page_number'] + 1}",
539
+ target="_blank",
540
+ cls="flex items-center gap-1.5 font-mono bold text-sm",
541
+ ),
542
+ cls="flex items-center justify-end",
543
+ ),
544
+ Div(
545
+ Div(
546
+ Div(
547
+ Div(
548
+ Div(
549
+ H3(
550
+ "Dynamic summary",
551
+ cls="text-base font-semibold",
552
+ ),
553
+ P(
554
+ NotStr(fields.get("snippet", "")),
555
+ cls="text-highlight text-muted-foreground",
556
+ ),
557
+ cls="grid grid-rows-[auto_0px] content-start gap-y-3",
558
+ ),
559
+ id=f"result-text-snippet-{idx}",
560
+ cls="grid gap-y-3 p-8 border border-dashed",
561
+ ),
562
+ Div(
563
+ Div(
564
+ Div(
565
+ H3(
566
+ "Full text",
567
+ cls="text-base font-semibold",
568
+ ),
569
+ Div(
570
+ P(
571
+ NotStr(fields.get("text", "")),
572
+ cls="text-highlight text-muted-foreground",
573
+ ),
574
+ Br(),
575
+ ),
576
+ cls="grid grid-rows-[auto_0px] content-start gap-y-3",
577
+ ),
578
+ id=f"result-text-full-{idx}",
579
+ cls="grid gap-y-3 p-8 border border-dashed",
580
+ ),
581
+ Div(
582
+ cls="absolute inset-x-0 bottom-0 bg-gradient-to-t from-[#fcfcfd] dark:from-[#1c2024] pt-[7%]"
583
+ ),
584
+ cls="relative grid",
585
+ ),
586
+ cls="grid grid-rows-[1fr_1fr] xl:grid-rows-[1fr_2fr] gap-y-8 p-8 text-sm",
587
+ ),
588
+ cls="grid bg-background",
589
+ ),
590
+ cls="grid bg-muted p-2",
591
+ ),
592
+ id=f"text-column-{idx}",
593
+ cls="text-column relative bg-background px-3 py-5 hidden md-grid-text-column",
594
+ ),
595
+ id=f"image-text-columns-{idx}",
596
+ cls="relative grid grid-cols-1 border-t grid-image-text-columns",
597
+ ),
598
+ cls="grid grid-cols-1 grid-rows-[auto_auto_1fr]",
599
+ ),
600
+ )
601
+
602
+ return [
603
+ Div(
604
+ SearchInfo(search_time, total_count),
605
+ *result_items,
606
+ image_swapping,
607
+ toggle_text_content,
608
+ dynamic_elements_scrollbars,
609
+ id="search-results",
610
+ cls="grid grid-cols-1 gap-px bg-border min-h-0",
611
+ ),
612
+ Div(
613
+ ChatResult(query_id=query_id, query=query, doc_ids=doc_ids),
614
+ hx_swap_oob="true",
615
+ id="chat_messages",
616
+ ),
617
+ ]
618
+
619
+
620
+ def ChatResult(query_id: str, query: str, doc_ids: Optional[list] = None):
621
+ messages = Div(LoadingSkeleton())
622
+
623
+ if doc_ids:
624
+ messages = Div(
625
+ LoadingSkeleton(),
626
+ hx_ext="sse",
627
+ sse_connect=f"/get-message?query_id={query_id}&doc_ids={','.join(doc_ids)}&query={quote_plus(query)}",
628
+ sse_swap="message",
629
+ sse_close="close",
630
+ hx_swap="innerHTML",
631
+ )
632
+
633
+ return Div(
634
+ Div("AI-response (Gemini-8B)", cls="text-xl font-semibold p-5"),
635
+ Div(
636
+ Div(
637
+ messages,
638
+ ),
639
+ id="chat-messages",
640
+ cls="overflow-auto min-h-0 grid items-end px-5",
641
+ ),
642
+ id="chat_messages",
643
+ cls="h-full grid grid-rows-[auto_1fr_auto] min-h-0 gap-3",
644
+ )
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>"}
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-1.5-flash-8b", 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 = "nn+colpali"):
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-8B!\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,2893 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ .static {
711
+ position: static;
712
+ }
713
+
714
+ .fixed {
715
+ position: fixed;
716
+ }
717
+
718
+ .absolute {
719
+ position: absolute;
720
+ }
721
+
722
+ .relative {
723
+ position: relative;
724
+ }
725
+
726
+ .inset-0 {
727
+ inset: 0px;
728
+ }
729
+
730
+ .inset-x-0 {
731
+ left: 0px;
732
+ right: 0px;
733
+ }
734
+
735
+ .inset-y-0 {
736
+ top: 0px;
737
+ bottom: 0px;
738
+ }
739
+
740
+ .-bottom-12 {
741
+ bottom: -3rem;
742
+ }
743
+
744
+ .-left-12 {
745
+ left: -3rem;
746
+ }
747
+
748
+ .-right-12 {
749
+ right: -3rem;
750
+ }
751
+
752
+ .-top-12 {
753
+ top: -3rem;
754
+ }
755
+
756
+ .bottom-0 {
757
+ bottom: 0px;
758
+ }
759
+
760
+ .left-0 {
761
+ left: 0px;
762
+ }
763
+
764
+ .left-1\/2 {
765
+ left: 50%;
766
+ }
767
+
768
+ .left-2 {
769
+ left: 0.5rem;
770
+ }
771
+
772
+ .left-\[50\%\] {
773
+ left: 50%;
774
+ }
775
+
776
+ .right-0 {
777
+ right: 0px;
778
+ }
779
+
780
+ .right-2 {
781
+ right: 0.5rem;
782
+ }
783
+
784
+ .right-4 {
785
+ right: 1rem;
786
+ }
787
+
788
+ .top-0 {
789
+ top: 0px;
790
+ }
791
+
792
+ .top-1\/2 {
793
+ top: 50%;
794
+ }
795
+
796
+ .top-2 {
797
+ top: 0.5rem;
798
+ }
799
+
800
+ .top-4 {
801
+ top: 1rem;
802
+ }
803
+
804
+ .top-\[50\%\] {
805
+ top: 50%;
806
+ }
807
+
808
+ .z-10 {
809
+ z-index: 10;
810
+ }
811
+
812
+ .z-50 {
813
+ z-index: 50;
814
+ }
815
+
816
+ .z-\[100\] {
817
+ z-index: 100;
818
+ }
819
+
820
+ .-mx-1 {
821
+ margin-left: -0.25rem;
822
+ margin-right: -0.25rem;
823
+ }
824
+
825
+ .mx-auto {
826
+ margin-left: auto;
827
+ margin-right: auto;
828
+ }
829
+
830
+ .my-1 {
831
+ margin-top: 0.25rem;
832
+ margin-bottom: 0.25rem;
833
+ }
834
+
835
+ .-ml-4 {
836
+ margin-left: -1rem;
837
+ }
838
+
839
+ .-mt-4 {
840
+ margin-top: -1rem;
841
+ }
842
+
843
+ .mb-1 {
844
+ margin-bottom: 0.25rem;
845
+ }
846
+
847
+ .mr-1\.5 {
848
+ margin-right: 0.375rem;
849
+ }
850
+
851
+ .mt-2 {
852
+ margin-top: 0.5rem;
853
+ }
854
+
855
+ .mt-\[13vh\] {
856
+ margin-top: 13vh;
857
+ }
858
+
859
+ .mt-\[8vh\] {
860
+ margin-top: 8vh;
861
+ }
862
+
863
+ .block {
864
+ display: block;
865
+ }
866
+
867
+ .flex {
868
+ display: flex;
869
+ }
870
+
871
+ .inline-flex {
872
+ display: inline-flex;
873
+ }
874
+
875
+ .table {
876
+ display: table;
877
+ }
878
+
879
+ .grid {
880
+ display: grid;
881
+ }
882
+
883
+ .contents {
884
+ display: contents;
885
+ }
886
+
887
+ .hidden {
888
+ display: none;
889
+ }
890
+
891
+ .aspect-square {
892
+ aspect-ratio: 1 / 1;
893
+ }
894
+
895
+ .size-4 {
896
+ width: 1rem;
897
+ height: 1rem;
898
+ }
899
+
900
+ .size-5 {
901
+ width: 1.25rem;
902
+ height: 1.25rem;
903
+ }
904
+
905
+ .h-10 {
906
+ height: 2.5rem;
907
+ }
908
+
909
+ .h-11 {
910
+ height: 2.75rem;
911
+ }
912
+
913
+ .h-12 {
914
+ height: 3rem;
915
+ }
916
+
917
+ .h-2 {
918
+ height: 0.5rem;
919
+ }
920
+
921
+ .h-2\.5 {
922
+ height: 0.625rem;
923
+ }
924
+
925
+ .h-3\.5 {
926
+ height: 0.875rem;
927
+ }
928
+
929
+ .h-4 {
930
+ height: 1rem;
931
+ }
932
+
933
+ .h-5 {
934
+ height: 1.25rem;
935
+ }
936
+
937
+ .h-6 {
938
+ height: 1.5rem;
939
+ }
940
+
941
+ .h-8 {
942
+ height: 2rem;
943
+ }
944
+
945
+ .h-9 {
946
+ height: 2.25rem;
947
+ }
948
+
949
+ .h-\[1\.5px\] {
950
+ height: 1.5px;
951
+ }
952
+
953
+ .h-\[27px\] {
954
+ height: 27px;
955
+ }
956
+
957
+ .h-\[377px\] {
958
+ height: 377px;
959
+ }
960
+
961
+ .h-\[55px\] {
962
+ height: 55px;
963
+ }
964
+
965
+ .h-\[var\(--radix-select-trigger-height\)\] {
966
+ height: var(--radix-select-trigger-height);
967
+ }
968
+
969
+ .h-full {
970
+ height: 100%;
971
+ }
972
+
973
+ .h-px {
974
+ height: 1px;
975
+ }
976
+
977
+ .max-h-96 {
978
+ max-height: 24rem;
979
+ }
980
+
981
+ .max-h-screen {
982
+ max-height: 100vh;
983
+ }
984
+
985
+ .min-h-0 {
986
+ min-height: 0px;
987
+ }
988
+
989
+ .min-h-\[55px\] {
990
+ min-height: 55px;
991
+ }
992
+
993
+ .min-h-\[80px\] {
994
+ min-height: 80px;
995
+ }
996
+
997
+ .min-h-screen {
998
+ min-height: 100vh;
999
+ }
1000
+
1001
+ .w-10 {
1002
+ width: 2.5rem;
1003
+ }
1004
+
1005
+ .w-11 {
1006
+ width: 2.75rem;
1007
+ }
1008
+
1009
+ .w-2\.5 {
1010
+ width: 0.625rem;
1011
+ }
1012
+
1013
+ .w-3\.5 {
1014
+ width: 0.875rem;
1015
+ }
1016
+
1017
+ .w-3\/4 {
1018
+ width: 75%;
1019
+ }
1020
+
1021
+ .w-4 {
1022
+ width: 1rem;
1023
+ }
1024
+
1025
+ .w-5 {
1026
+ width: 1.25rem;
1027
+ }
1028
+
1029
+ .w-8 {
1030
+ width: 2rem;
1031
+ }
1032
+
1033
+ .w-\[1\.5px\] {
1034
+ width: 1.5px;
1035
+ }
1036
+
1037
+ .w-full {
1038
+ width: 100%;
1039
+ }
1040
+
1041
+ .min-w-0 {
1042
+ min-width: 0px;
1043
+ }
1044
+
1045
+ .min-w-\[8rem\] {
1046
+ min-width: 8rem;
1047
+ }
1048
+
1049
+ .min-w-\[var\(--radix-select-trigger-width\)\] {
1050
+ min-width: var(--radix-select-trigger-width);
1051
+ }
1052
+
1053
+ .max-w-lg {
1054
+ max-width: 32rem;
1055
+ }
1056
+
1057
+ .max-w-screen-md {
1058
+ max-width: 768px;
1059
+ }
1060
+
1061
+ .flex-1 {
1062
+ flex: 1 1 0%;
1063
+ }
1064
+
1065
+ .shrink-0 {
1066
+ flex-shrink: 0;
1067
+ }
1068
+
1069
+ .grow {
1070
+ flex-grow: 1;
1071
+ }
1072
+
1073
+ .grow-0 {
1074
+ flex-grow: 0;
1075
+ }
1076
+
1077
+ .basis-full {
1078
+ flex-basis: 100%;
1079
+ }
1080
+
1081
+ .caption-bottom {
1082
+ caption-side: bottom;
1083
+ }
1084
+
1085
+ .-translate-x-1\/2 {
1086
+ --tw-translate-x: -50%;
1087
+ 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));
1088
+ }
1089
+
1090
+ .-translate-y-1\/2 {
1091
+ --tw-translate-y: -50%;
1092
+ 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));
1093
+ }
1094
+
1095
+ .translate-x-\[-50\%\] {
1096
+ --tw-translate-x: -50%;
1097
+ 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));
1098
+ }
1099
+
1100
+ .translate-y-\[-50\%\] {
1101
+ --tw-translate-y: -50%;
1102
+ 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));
1103
+ }
1104
+
1105
+ .rotate-90 {
1106
+ --tw-rotate: 90deg;
1107
+ 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));
1108
+ }
1109
+
1110
+ .transform {
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
+ @keyframes pulse {
1115
+ 50% {
1116
+ opacity: .5;
1117
+ }
1118
+ }
1119
+
1120
+ .animate-pulse {
1121
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1122
+ }
1123
+
1124
+ @keyframes spin {
1125
+ to {
1126
+ transform: rotate(360deg);
1127
+ }
1128
+ }
1129
+
1130
+ .animate-spin {
1131
+ animation: spin 1s linear infinite;
1132
+ }
1133
+
1134
+ .cursor-default {
1135
+ cursor: default;
1136
+ }
1137
+
1138
+ .cursor-pointer {
1139
+ cursor: pointer;
1140
+ }
1141
+
1142
+ .touch-none {
1143
+ touch-action: none;
1144
+ }
1145
+
1146
+ .select-none {
1147
+ -webkit-user-select: none;
1148
+ -moz-user-select: none;
1149
+ user-select: none;
1150
+ }
1151
+
1152
+ .resize {
1153
+ resize: both;
1154
+ }
1155
+
1156
+ .grid-flow-col {
1157
+ grid-auto-flow: column;
1158
+ }
1159
+
1160
+ .grid-cols-1 {
1161
+ grid-template-columns: repeat(1, minmax(0, 1fr));
1162
+ }
1163
+
1164
+ .grid-rows-\[1fr_1fr\] {
1165
+ grid-template-rows: 1fr 1fr;
1166
+ }
1167
+
1168
+ .grid-rows-\[auto_0px\] {
1169
+ grid-template-rows: auto 0px;
1170
+ }
1171
+
1172
+ .grid-rows-\[auto_1fr\] {
1173
+ grid-template-rows: auto 1fr;
1174
+ }
1175
+
1176
+ .grid-rows-\[auto_1fr_auto\] {
1177
+ grid-template-rows: auto 1fr auto;
1178
+ }
1179
+
1180
+ .grid-rows-\[minmax\(0\2c 55px\)_minmax\(0\2c 1fr\)\] {
1181
+ grid-template-rows: minmax(0,55px) minmax(0,1fr);
1182
+ }
1183
+
1184
+ .grid-rows-\[auto_auto_1fr\] {
1185
+ grid-template-rows: auto auto 1fr;
1186
+ }
1187
+
1188
+ .flex-col {
1189
+ flex-direction: column;
1190
+ }
1191
+
1192
+ .flex-col-reverse {
1193
+ flex-direction: column-reverse;
1194
+ }
1195
+
1196
+ .flex-wrap {
1197
+ flex-wrap: wrap;
1198
+ }
1199
+
1200
+ .content-start {
1201
+ align-content: flex-start;
1202
+ }
1203
+
1204
+ .items-end {
1205
+ align-items: flex-end;
1206
+ }
1207
+
1208
+ .items-center {
1209
+ align-items: center;
1210
+ }
1211
+
1212
+ .justify-end {
1213
+ justify-content: flex-end;
1214
+ }
1215
+
1216
+ .justify-center {
1217
+ justify-content: center;
1218
+ }
1219
+
1220
+ .justify-between {
1221
+ justify-content: space-between;
1222
+ }
1223
+
1224
+ .justify-items-center {
1225
+ justify-items: center;
1226
+ }
1227
+
1228
+ .gap-1\.5 {
1229
+ gap: 0.375rem;
1230
+ }
1231
+
1232
+ .gap-2 {
1233
+ gap: 0.5rem;
1234
+ }
1235
+
1236
+ .gap-3 {
1237
+ gap: 0.75rem;
1238
+ }
1239
+
1240
+ .gap-4 {
1241
+ gap: 1rem;
1242
+ }
1243
+
1244
+ .gap-5 {
1245
+ gap: 1.25rem;
1246
+ }
1247
+
1248
+ .gap-8 {
1249
+ gap: 2rem;
1250
+ }
1251
+
1252
+ .gap-\[3px\] {
1253
+ gap: 3px;
1254
+ }
1255
+
1256
+ .gap-px {
1257
+ gap: 1px;
1258
+ }
1259
+
1260
+ .gap-x-3 {
1261
+ -moz-column-gap: 0.75rem;
1262
+ column-gap: 0.75rem;
1263
+ }
1264
+
1265
+ .gap-x-5 {
1266
+ -moz-column-gap: 1.25rem;
1267
+ column-gap: 1.25rem;
1268
+ }
1269
+
1270
+ .gap-y-3 {
1271
+ row-gap: 0.75rem;
1272
+ }
1273
+
1274
+ .gap-y-8 {
1275
+ row-gap: 2rem;
1276
+ }
1277
+
1278
+ .space-x-2 > :not([hidden]) ~ :not([hidden]) {
1279
+ --tw-space-x-reverse: 0;
1280
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
1281
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
1282
+ }
1283
+
1284
+ .space-x-4 > :not([hidden]) ~ :not([hidden]) {
1285
+ --tw-space-x-reverse: 0;
1286
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
1287
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
1288
+ }
1289
+
1290
+ .space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
1291
+ --tw-space-y-reverse: 0;
1292
+ margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
1293
+ margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
1294
+ }
1295
+
1296
+ .space-y-2 > :not([hidden]) ~ :not([hidden]) {
1297
+ --tw-space-y-reverse: 0;
1298
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
1299
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
1300
+ }
1301
+
1302
+ .space-x-1 > :not([hidden]) ~ :not([hidden]) {
1303
+ --tw-space-x-reverse: 0;
1304
+ margin-right: calc(0.25rem * var(--tw-space-x-reverse));
1305
+ margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
1306
+ }
1307
+
1308
+ .self-stretch {
1309
+ align-self: stretch;
1310
+ }
1311
+
1312
+ .overflow-auto {
1313
+ overflow: auto;
1314
+ }
1315
+
1316
+ .overflow-hidden {
1317
+ overflow: hidden;
1318
+ }
1319
+
1320
+ .whitespace-nowrap {
1321
+ white-space: nowrap;
1322
+ }
1323
+
1324
+ .\!rounded-full {
1325
+ border-radius: 9999px !important;
1326
+ }
1327
+
1328
+ .rounded-\[inherit\] {
1329
+ border-radius: inherit;
1330
+ }
1331
+
1332
+ .rounded-full {
1333
+ border-radius: 9999px;
1334
+ }
1335
+
1336
+ .rounded-lg {
1337
+ border-radius: var(--radius);
1338
+ }
1339
+
1340
+ .rounded-md {
1341
+ border-radius: calc(var(--radius) - 2px);
1342
+ }
1343
+
1344
+ .rounded-none {
1345
+ border-radius: 0px;
1346
+ }
1347
+
1348
+ .rounded-sm {
1349
+ border-radius: calc(var(--radius) - 4px);
1350
+ }
1351
+
1352
+ .border {
1353
+ border-width: 1px;
1354
+ }
1355
+
1356
+ .border-2 {
1357
+ border-width: 2px;
1358
+ }
1359
+
1360
+ .border-b {
1361
+ border-bottom-width: 1px;
1362
+ }
1363
+
1364
+ .border-l {
1365
+ border-left-width: 1px;
1366
+ }
1367
+
1368
+ .border-r {
1369
+ border-right-width: 1px;
1370
+ }
1371
+
1372
+ .border-t {
1373
+ border-top-width: 1px;
1374
+ }
1375
+
1376
+ .border-dashed {
1377
+ border-style: dashed;
1378
+ }
1379
+
1380
+ .border-destructive {
1381
+ border-color: hsl(var(--destructive));
1382
+ }
1383
+
1384
+ .border-destructive\/50 {
1385
+ border-color: hsl(var(--destructive) / 0.5);
1386
+ }
1387
+
1388
+ .border-input {
1389
+ border-color: hsl(var(--input));
1390
+ }
1391
+
1392
+ .border-primary {
1393
+ border-color: hsl(var(--primary));
1394
+ }
1395
+
1396
+ .border-transparent {
1397
+ border-color: transparent;
1398
+ }
1399
+
1400
+ .border-l-transparent {
1401
+ border-left-color: transparent;
1402
+ }
1403
+
1404
+ .border-t-transparent {
1405
+ border-top-color: transparent;
1406
+ }
1407
+
1408
+ .bg-background {
1409
+ background-color: hsl(var(--background));
1410
+ }
1411
+
1412
+ .bg-black\/80 {
1413
+ background-color: rgb(0 0 0 / 0.8);
1414
+ }
1415
+
1416
+ .bg-blue-500 {
1417
+ --tw-bg-opacity: 1;
1418
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
1419
+ }
1420
+
1421
+ .bg-border {
1422
+ background-color: hsl(var(--border));
1423
+ }
1424
+
1425
+ .bg-card {
1426
+ background-color: hsl(var(--card));
1427
+ }
1428
+
1429
+ .bg-destructive {
1430
+ background-color: hsl(var(--destructive));
1431
+ }
1432
+
1433
+ .bg-input {
1434
+ background-color: hsl(var(--input));
1435
+ }
1436
+
1437
+ .bg-muted {
1438
+ background-color: hsl(var(--muted));
1439
+ }
1440
+
1441
+ .bg-muted\/50 {
1442
+ background-color: hsl(var(--muted) / 0.5);
1443
+ }
1444
+
1445
+ .bg-popover {
1446
+ background-color: hsl(var(--popover));
1447
+ }
1448
+
1449
+ .bg-primary {
1450
+ background-color: hsl(var(--primary));
1451
+ }
1452
+
1453
+ .bg-red-300 {
1454
+ --tw-bg-opacity: 1;
1455
+ background-color: rgb(252 165 165 / var(--tw-bg-opacity));
1456
+ }
1457
+
1458
+ .bg-red-500 {
1459
+ --tw-bg-opacity: 1;
1460
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
1461
+ }
1462
+
1463
+ .bg-secondary {
1464
+ background-color: hsl(var(--secondary));
1465
+ }
1466
+
1467
+ .bg-white {
1468
+ --tw-bg-opacity: 1;
1469
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1470
+ }
1471
+
1472
+ .bg-gradient-to-r {
1473
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
1474
+ }
1475
+
1476
+ .bg-gradient-to-t {
1477
+ background-image: linear-gradient(to top, var(--tw-gradient-stops));
1478
+ }
1479
+
1480
+ .from-\[\#fcfcfd\] {
1481
+ --tw-gradient-from: #fcfcfd var(--tw-gradient-from-position);
1482
+ --tw-gradient-to: rgb(252 252 253 / 0) var(--tw-gradient-to-position);
1483
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1484
+ }
1485
+
1486
+ .from-black {
1487
+ --tw-gradient-from: #000 var(--tw-gradient-from-position);
1488
+ --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
1489
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1490
+ }
1491
+
1492
+ .to-slate-700 {
1493
+ --tw-gradient-to: #334155 var(--tw-gradient-to-position);
1494
+ }
1495
+
1496
+ .bg-clip-text {
1497
+ -webkit-background-clip: text;
1498
+ background-clip: text;
1499
+ }
1500
+
1501
+ .fill-current {
1502
+ fill: currentColor;
1503
+ }
1504
+
1505
+ .object-contain {
1506
+ -o-object-fit: contain;
1507
+ object-fit: contain;
1508
+ }
1509
+
1510
+ .p-1 {
1511
+ padding: 0.25rem;
1512
+ }
1513
+
1514
+ .p-10 {
1515
+ padding: 2.5rem;
1516
+ }
1517
+
1518
+ .p-2 {
1519
+ padding: 0.5rem;
1520
+ }
1521
+
1522
+ .p-3 {
1523
+ padding: 0.75rem;
1524
+ }
1525
+
1526
+ .p-4 {
1527
+ padding: 1rem;
1528
+ }
1529
+
1530
+ .p-5 {
1531
+ padding: 1.25rem;
1532
+ }
1533
+
1534
+ .p-6 {
1535
+ padding: 1.5rem;
1536
+ }
1537
+
1538
+ .p-8 {
1539
+ padding: 2rem;
1540
+ }
1541
+
1542
+ .p-\[1px\] {
1543
+ padding: 1px;
1544
+ }
1545
+
1546
+ .px-2 {
1547
+ padding-left: 0.5rem;
1548
+ padding-right: 0.5rem;
1549
+ }
1550
+
1551
+ .px-2\.5 {
1552
+ padding-left: 0.625rem;
1553
+ padding-right: 0.625rem;
1554
+ }
1555
+
1556
+ .px-3 {
1557
+ padding-left: 0.75rem;
1558
+ padding-right: 0.75rem;
1559
+ }
1560
+
1561
+ .px-4 {
1562
+ padding-left: 1rem;
1563
+ padding-right: 1rem;
1564
+ }
1565
+
1566
+ .px-5 {
1567
+ padding-left: 1.25rem;
1568
+ padding-right: 1.25rem;
1569
+ }
1570
+
1571
+ .px-8 {
1572
+ padding-left: 2rem;
1573
+ padding-right: 2rem;
1574
+ }
1575
+
1576
+ .py-0\.5 {
1577
+ padding-top: 0.125rem;
1578
+ padding-bottom: 0.125rem;
1579
+ }
1580
+
1581
+ .py-1 {
1582
+ padding-top: 0.25rem;
1583
+ padding-bottom: 0.25rem;
1584
+ }
1585
+
1586
+ .py-1\.5 {
1587
+ padding-top: 0.375rem;
1588
+ padding-bottom: 0.375rem;
1589
+ }
1590
+
1591
+ .py-2 {
1592
+ padding-top: 0.5rem;
1593
+ padding-bottom: 0.5rem;
1594
+ }
1595
+
1596
+ .py-4 {
1597
+ padding-top: 1rem;
1598
+ padding-bottom: 1rem;
1599
+ }
1600
+
1601
+ .py-5 {
1602
+ padding-top: 1.25rem;
1603
+ padding-bottom: 1.25rem;
1604
+ }
1605
+
1606
+ .pl-10 {
1607
+ padding-left: 2.5rem;
1608
+ }
1609
+
1610
+ .pl-4 {
1611
+ padding-left: 1rem;
1612
+ }
1613
+
1614
+ .pl-8 {
1615
+ padding-left: 2rem;
1616
+ }
1617
+
1618
+ .pr-2 {
1619
+ padding-right: 0.5rem;
1620
+ }
1621
+
1622
+ .pr-8 {
1623
+ padding-right: 2rem;
1624
+ }
1625
+
1626
+ .pt-0 {
1627
+ padding-top: 0px;
1628
+ }
1629
+
1630
+ .pt-4 {
1631
+ padding-top: 1rem;
1632
+ }
1633
+
1634
+ .pt-\[7\%\] {
1635
+ padding-top: 7%;
1636
+ }
1637
+
1638
+ .text-left {
1639
+ text-align: left;
1640
+ }
1641
+
1642
+ .text-center {
1643
+ text-align: center;
1644
+ }
1645
+
1646
+ .align-middle {
1647
+ vertical-align: middle;
1648
+ }
1649
+
1650
+ .font-mono {
1651
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1652
+ }
1653
+
1654
+ .text-2xl {
1655
+ font-size: 1.5rem;
1656
+ line-height: 2rem;
1657
+ }
1658
+
1659
+ .text-3xl {
1660
+ font-size: 1.875rem;
1661
+ line-height: 2.25rem;
1662
+ }
1663
+
1664
+ .text-5xl {
1665
+ font-size: 3rem;
1666
+ line-height: 1;
1667
+ }
1668
+
1669
+ .text-base {
1670
+ font-size: 1rem;
1671
+ line-height: 1.5rem;
1672
+ }
1673
+
1674
+ .text-lg {
1675
+ font-size: 1.125rem;
1676
+ line-height: 1.75rem;
1677
+ }
1678
+
1679
+ .text-sm {
1680
+ font-size: 0.875rem;
1681
+ line-height: 1.25rem;
1682
+ }
1683
+
1684
+ .text-xl {
1685
+ font-size: 1.25rem;
1686
+ line-height: 1.75rem;
1687
+ }
1688
+
1689
+ .text-xs {
1690
+ font-size: 0.75rem;
1691
+ line-height: 1rem;
1692
+ }
1693
+
1694
+ .font-bold {
1695
+ font-weight: 700;
1696
+ }
1697
+
1698
+ .font-medium {
1699
+ font-weight: 500;
1700
+ }
1701
+
1702
+ .font-normal {
1703
+ font-weight: 400;
1704
+ }
1705
+
1706
+ .font-semibold {
1707
+ font-weight: 600;
1708
+ }
1709
+
1710
+ .leading-none {
1711
+ line-height: 1;
1712
+ }
1713
+
1714
+ .tracking-tight {
1715
+ letter-spacing: -0.025em;
1716
+ }
1717
+
1718
+ .tracking-wide {
1719
+ letter-spacing: 0.025em;
1720
+ }
1721
+
1722
+ .text-card-foreground {
1723
+ color: hsl(var(--card-foreground));
1724
+ }
1725
+
1726
+ .text-current {
1727
+ color: currentColor;
1728
+ }
1729
+
1730
+ .text-destructive {
1731
+ color: hsl(var(--destructive));
1732
+ }
1733
+
1734
+ .text-destructive-foreground {
1735
+ color: hsl(var(--destructive-foreground));
1736
+ }
1737
+
1738
+ .text-foreground {
1739
+ color: hsl(var(--foreground));
1740
+ }
1741
+
1742
+ .text-foreground\/50 {
1743
+ color: hsl(var(--foreground) / 0.5);
1744
+ }
1745
+
1746
+ .text-gray-800 {
1747
+ --tw-text-opacity: 1;
1748
+ color: rgb(31 41 55 / var(--tw-text-opacity));
1749
+ }
1750
+
1751
+ .text-gray-900 {
1752
+ --tw-text-opacity: 1;
1753
+ color: rgb(17 24 39 / var(--tw-text-opacity));
1754
+ }
1755
+
1756
+ .text-muted-foreground {
1757
+ color: hsl(var(--muted-foreground));
1758
+ }
1759
+
1760
+ .text-popover-foreground {
1761
+ color: hsl(var(--popover-foreground));
1762
+ }
1763
+
1764
+ .text-primary {
1765
+ color: hsl(var(--primary));
1766
+ }
1767
+
1768
+ .text-primary-foreground {
1769
+ color: hsl(var(--primary-foreground));
1770
+ }
1771
+
1772
+ .text-secondary-foreground {
1773
+ color: hsl(var(--secondary-foreground));
1774
+ }
1775
+
1776
+ .text-transparent {
1777
+ color: transparent;
1778
+ }
1779
+
1780
+ .no-underline {
1781
+ text-decoration-line: none;
1782
+ }
1783
+
1784
+ .underline-offset-4 {
1785
+ text-underline-offset: 4px;
1786
+ }
1787
+
1788
+ .antialiased {
1789
+ -webkit-font-smoothing: antialiased;
1790
+ -moz-osx-font-smoothing: grayscale;
1791
+ }
1792
+
1793
+ .opacity-0 {
1794
+ opacity: 0;
1795
+ }
1796
+
1797
+ .opacity-50 {
1798
+ opacity: 0.5;
1799
+ }
1800
+
1801
+ .opacity-70 {
1802
+ opacity: 0.7;
1803
+ }
1804
+
1805
+ .opacity-90 {
1806
+ opacity: 0.9;
1807
+ }
1808
+
1809
+ .shadow-lg {
1810
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
1811
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1812
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1813
+ }
1814
+
1815
+ .shadow-md {
1816
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
1817
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
1818
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1819
+ }
1820
+
1821
+ .shadow-sm {
1822
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
1823
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
1824
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1825
+ }
1826
+
1827
+ .outline-none {
1828
+ outline: 2px solid transparent;
1829
+ outline-offset: 2px;
1830
+ }
1831
+
1832
+ .outline {
1833
+ outline-style: solid;
1834
+ }
1835
+
1836
+ .ring {
1837
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1838
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1839
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1840
+ }
1841
+
1842
+ .ring-0 {
1843
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1844
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1845
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1846
+ }
1847
+
1848
+ .ring-offset-background {
1849
+ --tw-ring-offset-color: hsl(var(--background));
1850
+ }
1851
+
1852
+ .ring-offset-transparent {
1853
+ --tw-ring-offset-color: transparent;
1854
+ }
1855
+
1856
+ .blur {
1857
+ --tw-blur: blur(8px);
1858
+ 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);
1859
+ }
1860
+
1861
+ .filter {
1862
+ 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);
1863
+ }
1864
+
1865
+ .backdrop-filter {
1866
+ -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);
1867
+ 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);
1868
+ }
1869
+
1870
+ .transition {
1871
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
1872
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1873
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
1874
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1875
+ transition-duration: 150ms;
1876
+ }
1877
+
1878
+ .transition-all {
1879
+ transition-property: all;
1880
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1881
+ transition-duration: 150ms;
1882
+ }
1883
+
1884
+ .transition-colors {
1885
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
1886
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1887
+ transition-duration: 150ms;
1888
+ }
1889
+
1890
+ .transition-opacity {
1891
+ transition-property: opacity;
1892
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1893
+ transition-duration: 150ms;
1894
+ }
1895
+
1896
+ .transition-transform {
1897
+ transition-property: transform;
1898
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1899
+ transition-duration: 150ms;
1900
+ }
1901
+
1902
+ .duration-200 {
1903
+ transition-duration: 200ms;
1904
+ }
1905
+
1906
+ .duration-300 {
1907
+ transition-duration: 300ms;
1908
+ }
1909
+
1910
+ .ease-in-out {
1911
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1912
+ }
1913
+
1914
+ .ease-out {
1915
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1916
+ }
1917
+
1918
+ @keyframes enter {
1919
+ from {
1920
+ opacity: var(--tw-enter-opacity, 1);
1921
+ 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));
1922
+ }
1923
+ }
1924
+
1925
+ @keyframes exit {
1926
+ to {
1927
+ opacity: var(--tw-exit-opacity, 1);
1928
+ 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));
1929
+ }
1930
+ }
1931
+
1932
+ .animate-in {
1933
+ animation-name: enter;
1934
+ animation-duration: 150ms;
1935
+ --tw-enter-opacity: initial;
1936
+ --tw-enter-scale: initial;
1937
+ --tw-enter-rotate: initial;
1938
+ --tw-enter-translate-x: initial;
1939
+ --tw-enter-translate-y: initial;
1940
+ }
1941
+
1942
+ .animate-out {
1943
+ animation-name: exit;
1944
+ animation-duration: 150ms;
1945
+ --tw-exit-opacity: initial;
1946
+ --tw-exit-scale: initial;
1947
+ --tw-exit-rotate: initial;
1948
+ --tw-exit-translate-x: initial;
1949
+ --tw-exit-translate-y: initial;
1950
+ }
1951
+
1952
+ .fade-in {
1953
+ --tw-enter-opacity: 0;
1954
+ }
1955
+
1956
+ .fade-out {
1957
+ --tw-exit-opacity: 0;
1958
+ }
1959
+
1960
+ .zoom-in {
1961
+ --tw-enter-scale: 0;
1962
+ }
1963
+
1964
+ .zoom-out {
1965
+ --tw-exit-scale: 0;
1966
+ }
1967
+
1968
+ .spin-in {
1969
+ --tw-enter-rotate: 30deg;
1970
+ }
1971
+
1972
+ .spin-out {
1973
+ --tw-exit-rotate: 30deg;
1974
+ }
1975
+
1976
+ .slide-in-from-bottom {
1977
+ --tw-enter-translate-y: 100%;
1978
+ }
1979
+
1980
+ .slide-in-from-left {
1981
+ --tw-enter-translate-x: -100%;
1982
+ }
1983
+
1984
+ .slide-in-from-right {
1985
+ --tw-enter-translate-x: 100%;
1986
+ }
1987
+
1988
+ .slide-in-from-top {
1989
+ --tw-enter-translate-y: -100%;
1990
+ }
1991
+
1992
+ .slide-out-to-bottom {
1993
+ --tw-exit-translate-y: 100%;
1994
+ }
1995
+
1996
+ .slide-out-to-left {
1997
+ --tw-exit-translate-x: -100%;
1998
+ }
1999
+
2000
+ .slide-out-to-right {
2001
+ --tw-exit-translate-x: 100%;
2002
+ }
2003
+
2004
+ .slide-out-to-top {
2005
+ --tw-exit-translate-y: -100%;
2006
+ }
2007
+
2008
+ .duration-200 {
2009
+ animation-duration: 200ms;
2010
+ }
2011
+
2012
+ .duration-300 {
2013
+ animation-duration: 300ms;
2014
+ }
2015
+
2016
+ .ease-in-out {
2017
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2018
+ }
2019
+
2020
+ .ease-out {
2021
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
2022
+ }
2023
+
2024
+ .running {
2025
+ animation-play-state: running;
2026
+ }
2027
+
2028
+ .paused {
2029
+ animation-play-state: paused;
2030
+ }
2031
+
2032
+ /* Hide scrollbar for Chrome, Safari and Opera */
2033
+
2034
+ .no-scrollbar::-webkit-scrollbar {
2035
+ display: none;
2036
+ }
2037
+
2038
+ /* Hide scrollbar for IE, Edge and Firefox */
2039
+
2040
+ .no-scrollbar {
2041
+ -webkit-overflow-scrolling: touch;
2042
+ -ms-overflow-style: none;
2043
+ /* IE and Edge */
2044
+ scrollbar-width: none;
2045
+ /* Firefox */
2046
+ }
2047
+
2048
+ @keyframes slideInFromTop {
2049
+ from {
2050
+ transform: translateY(-100%);
2051
+ }
2052
+
2053
+ to {
2054
+ transform: translateY(0);
2055
+ }
2056
+ }
2057
+
2058
+ @keyframes slideInFromBottom {
2059
+ from {
2060
+ transform: translateY(100%);
2061
+ }
2062
+
2063
+ to {
2064
+ transform: translateY(0);
2065
+ }
2066
+ }
2067
+
2068
+ .toast {
2069
+ animation-duration: 0.2s;
2070
+ animation-fill-mode: forwards;
2071
+ }
2072
+
2073
+ @media (max-width: 640px) {
2074
+ .toast {
2075
+ animation-name: slideInFromTop;
2076
+ }
2077
+ }
2078
+
2079
+ @media (min-width: 641px) {
2080
+ .toast {
2081
+ animation-name: slideInFromBottom;
2082
+ }
2083
+ }
2084
+
2085
+ @keyframes fade-in {
2086
+ from {
2087
+ opacity: 0;
2088
+ }
2089
+
2090
+ to {
2091
+ opacity: 1;
2092
+ }
2093
+ }
2094
+
2095
+ @keyframes slide-up {
2096
+ from {
2097
+ transform: translateY(20px);
2098
+ opacity: 0;
2099
+ }
2100
+
2101
+ to {
2102
+ transform: translateY(0);
2103
+ opacity: 1;
2104
+ }
2105
+ }
2106
+
2107
+ .animate-fade-in {
2108
+ animation: fade-in 1s ease-out forwards;
2109
+ }
2110
+
2111
+ .animate-slide-up {
2112
+ animation: slide-up 1s ease-out forwards;
2113
+ }
2114
+
2115
+ .sim-map-button.active {
2116
+ background-color: #61D790;
2117
+ color: #2E2F27;
2118
+ &:hover {
2119
+ background-color: #61D790;
2120
+ }
2121
+ }
2122
+
2123
+ .text-highlight strong {
2124
+ color: black;
2125
+ .dark & {
2126
+ color: white;
2127
+ }
2128
+ }
2129
+
2130
+ .tokens-button {
2131
+ background-color: #B7E2F1;
2132
+ color: #2E2F27;
2133
+ }
2134
+
2135
+ .overlay-image {
2136
+ opacity: 0.5;
2137
+ position: absolute;
2138
+ top: 0;
2139
+ left: 0;
2140
+ width: 100%;
2141
+ height: 100%;
2142
+ z-index: 10;
2143
+ }
2144
+
2145
+ header {
2146
+ grid-column: 1/-1;
2147
+ }
2148
+
2149
+ body {
2150
+ &[data-is-home="true"] {
2151
+ background: radial-gradient(circle at 50% 100%, #fcfcfd, #fcfcfd, #fdfdfe, #fdfdfe, #fefefe, #fefefe, #ffffff, #ffffff);
2152
+ .dark & {
2153
+ background: radial-gradient(circle at 50% 50%, #272a2d, #242629, #212326, #1e1f22, #1b1c1e, #18181b, #151517, #111113);
2154
+ }
2155
+ }
2156
+ }
2157
+
2158
+ main {
2159
+ overflow: auto;
2160
+ }
2161
+
2162
+ aside {
2163
+ overflow: auto;
2164
+ }
2165
+
2166
+ .scroll-container {
2167
+ padding-right: 10px;
2168
+ }
2169
+
2170
+ .question-message {
2171
+ background-color: #61D790;
2172
+ color: #2E2F27;
2173
+ }
2174
+
2175
+ @media (min-width: 768px) {
2176
+ .grid-image-text-columns {
2177
+ grid-column: span 2 / span 2;
2178
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2179
+ }
2180
+ }
2181
+
2182
+ .grid-image-column {
2183
+ grid-row: span 2 / span 2;
2184
+ display: grid;
2185
+ grid-template-rows: subgrid;
2186
+ align-content: flex-start;
2187
+ }
2188
+
2189
+ @media (min-width: 768px) {
2190
+ .md-grid-text-column {
2191
+ grid-row: span 2 / span 2;
2192
+ display: grid;
2193
+ grid-template-rows: subgrid;
2194
+ align-content: flex-start;
2195
+ }
2196
+ }
2197
+
2198
+ #search-input[aria-expanded="true"] {
2199
+ border-top: 1px solid hsl(var(--input));
2200
+ border-left: 1px solid hsl(var(--input));
2201
+ border-right: 1px solid hsl(var(--input));
2202
+ border-bottom: none;
2203
+ border-bottom-left-radius: 0;
2204
+ border-bottom-right-radius: 0;
2205
+ }
2206
+
2207
+ .awesomplete {
2208
+ width: 100%;
2209
+ }
2210
+
2211
+ .awesomplete > ul > :not([hidden]) ~ :not([hidden]) {
2212
+ --tw-space-y-reverse: 0;
2213
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
2214
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
2215
+ }
2216
+
2217
+ .awesomplete > ul {
2218
+ font-size: 0.875rem;
2219
+ line-height: 1.25rem;
2220
+ margin: 0;
2221
+ border-top: none;
2222
+ border-left: 1px solid hsl(var(--input));
2223
+ border-right: 1px solid hsl(var(--input));
2224
+ border-bottom: 1px solid hsl(var(--input));
2225
+ border-radius: 0 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px);
2226
+ background: white;
2227
+ .dark & {
2228
+ background: hsl(var(--background));
2229
+ }
2230
+ box-shadow: none;
2231
+ text-shadow: none;
2232
+ }
2233
+
2234
+ .awesomplete > ul:before {
2235
+ display: none;
2236
+ }
2237
+
2238
+ .awesomplete > ul > li:hover {
2239
+ background-color: #B7E2F1;
2240
+ color: #2E2F27;
2241
+ }
2242
+
2243
+ .awesomplete > ul > li[aria-selected="true"] {
2244
+ background-color: #B7E2F1;
2245
+ color: #2E2F27;
2246
+ }
2247
+
2248
+ .awesomplete mark {
2249
+ background-color: #61D790;
2250
+ color: #2E2F27;
2251
+ }
2252
+
2253
+ .awesomplete li:hover mark {
2254
+ background-color: #61D790;
2255
+ color: #2E2F27;
2256
+ }
2257
+
2258
+ .awesomplete li[aria-selected="true"] mark {
2259
+ background-color: #61D790;
2260
+ color: #2E2F27;
2261
+ }
2262
+
2263
+ :root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
2264
+ overflow: hidden;
2265
+ }
2266
+
2267
+ :root:has(.group[data-state="open"] .group-data-\[state\=open\]\:no-bg-scroll) {
2268
+ overflow: hidden;
2269
+ }
2270
+
2271
+ .file\:border-0::file-selector-button {
2272
+ border-width: 0px;
2273
+ }
2274
+
2275
+ .file\:bg-transparent::file-selector-button {
2276
+ background-color: transparent;
2277
+ }
2278
+
2279
+ .file\:text-sm::file-selector-button {
2280
+ font-size: 0.875rem;
2281
+ line-height: 1.25rem;
2282
+ }
2283
+
2284
+ .file\:font-medium::file-selector-button {
2285
+ font-weight: 500;
2286
+ }
2287
+
2288
+ .placeholder\:text-muted-foreground::-moz-placeholder {
2289
+ color: hsl(var(--muted-foreground));
2290
+ }
2291
+
2292
+ .placeholder\:text-muted-foreground::placeholder {
2293
+ color: hsl(var(--muted-foreground));
2294
+ }
2295
+
2296
+ .focus-within\:border-input:focus-within {
2297
+ border-color: hsl(var(--input));
2298
+ }
2299
+
2300
+ .focus-within\:outline-none:focus-within {
2301
+ outline: 2px solid transparent;
2302
+ outline-offset: 2px;
2303
+ }
2304
+
2305
+ .focus-within\:ring-2:focus-within {
2306
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2307
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2308
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2309
+ }
2310
+
2311
+ .focus-within\:ring-ring:focus-within {
2312
+ --tw-ring-color: hsl(var(--ring));
2313
+ }
2314
+
2315
+ .focus-within\:ring-offset-2:focus-within {
2316
+ --tw-ring-offset-width: 2px;
2317
+ }
2318
+
2319
+ .hover\:border-black:hover {
2320
+ --tw-border-opacity: 1;
2321
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
2322
+ }
2323
+
2324
+ .hover\:bg-accent:hover {
2325
+ background-color: hsl(var(--accent));
2326
+ }
2327
+
2328
+ .hover\:bg-destructive\/80:hover {
2329
+ background-color: hsl(var(--destructive) / 0.8);
2330
+ }
2331
+
2332
+ .hover\:bg-destructive\/90:hover {
2333
+ background-color: hsl(var(--destructive) / 0.9);
2334
+ }
2335
+
2336
+ .hover\:bg-muted\/50:hover {
2337
+ background-color: hsl(var(--muted) / 0.5);
2338
+ }
2339
+
2340
+ .hover\:bg-primary\/80:hover {
2341
+ background-color: hsl(var(--primary) / 0.8);
2342
+ }
2343
+
2344
+ .hover\:bg-primary\/90:hover {
2345
+ background-color: hsl(var(--primary) / 0.9);
2346
+ }
2347
+
2348
+ .hover\:bg-secondary\/80:hover {
2349
+ background-color: hsl(var(--secondary) / 0.8);
2350
+ }
2351
+
2352
+ .hover\:text-accent-foreground:hover {
2353
+ color: hsl(var(--accent-foreground));
2354
+ }
2355
+
2356
+ .hover\:text-foreground:hover {
2357
+ color: hsl(var(--foreground));
2358
+ }
2359
+
2360
+ .hover\:underline:hover {
2361
+ text-decoration-line: underline;
2362
+ }
2363
+
2364
+ .hover\:opacity-100:hover {
2365
+ opacity: 1;
2366
+ }
2367
+
2368
+ .focus\:bg-accent:focus {
2369
+ background-color: hsl(var(--accent));
2370
+ }
2371
+
2372
+ .focus\:text-accent-foreground:focus {
2373
+ color: hsl(var(--accent-foreground));
2374
+ }
2375
+
2376
+ .focus\:opacity-100:focus {
2377
+ opacity: 1;
2378
+ }
2379
+
2380
+ .focus\:outline-none:focus {
2381
+ outline: 2px solid transparent;
2382
+ outline-offset: 2px;
2383
+ }
2384
+
2385
+ .focus\:ring-2:focus {
2386
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2387
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2388
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2389
+ }
2390
+
2391
+ .focus\:ring-ring:focus {
2392
+ --tw-ring-color: hsl(var(--ring));
2393
+ }
2394
+
2395
+ .focus\:ring-offset-2:focus {
2396
+ --tw-ring-offset-width: 2px;
2397
+ }
2398
+
2399
+ .focus-visible\:outline-none:focus-visible {
2400
+ outline: 2px solid transparent;
2401
+ outline-offset: 2px;
2402
+ }
2403
+
2404
+ .focus-visible\:ring-2:focus-visible {
2405
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2406
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2407
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2408
+ }
2409
+
2410
+ .focus-visible\:ring-ring:focus-visible {
2411
+ --tw-ring-color: hsl(var(--ring));
2412
+ }
2413
+
2414
+ .focus-visible\:ring-transparent:focus-visible {
2415
+ --tw-ring-color: transparent;
2416
+ }
2417
+
2418
+ .focus-visible\:ring-offset-2:focus-visible {
2419
+ --tw-ring-offset-width: 2px;
2420
+ }
2421
+
2422
+ .active\:ring:active {
2423
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2424
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2425
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2426
+ }
2427
+
2428
+ .disabled\:pointer-events-none:disabled {
2429
+ pointer-events: none;
2430
+ }
2431
+
2432
+ .disabled\:cursor-not-allowed:disabled {
2433
+ cursor: not-allowed;
2434
+ }
2435
+
2436
+ .disabled\:opacity-50:disabled {
2437
+ opacity: 0.5;
2438
+ }
2439
+
2440
+ .group:hover .group-hover\:opacity-100 {
2441
+ opacity: 1;
2442
+ }
2443
+
2444
+ .group.destructive .group-\[\.destructive\]\:text-red-300 {
2445
+ --tw-text-opacity: 1;
2446
+ color: rgb(252 165 165 / var(--tw-text-opacity));
2447
+ }
2448
+
2449
+ .group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover {
2450
+ --tw-text-opacity: 1;
2451
+ color: rgb(254 242 242 / var(--tw-text-opacity));
2452
+ }
2453
+
2454
+ .group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus {
2455
+ --tw-ring-opacity: 1;
2456
+ --tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity));
2457
+ }
2458
+
2459
+ .group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus {
2460
+ --tw-ring-offset-color: #dc2626;
2461
+ }
2462
+
2463
+ .peer:checked ~ .peer-checked\:bg-primary {
2464
+ background-color: hsl(var(--primary));
2465
+ }
2466
+
2467
+ .peer:checked ~ .peer-checked\:text-primary-foreground {
2468
+ color: hsl(var(--primary-foreground));
2469
+ }
2470
+
2471
+ .peer:disabled ~ .peer-disabled\:cursor-not-allowed {
2472
+ cursor: not-allowed;
2473
+ }
2474
+
2475
+ .peer:disabled ~ .peer-disabled\:opacity-50 {
2476
+ opacity: 0.5;
2477
+ }
2478
+
2479
+ .peer:disabled ~ .peer-disabled\:opacity-70 {
2480
+ opacity: 0.7;
2481
+ }
2482
+
2483
+ .data-\[disabled\]\:pointer-events-none[data-disabled] {
2484
+ pointer-events: none;
2485
+ }
2486
+
2487
+ .data-\[side\=bottom\]\:translate-y-1[data-side="bottom"] {
2488
+ --tw-translate-y: 0.25rem;
2489
+ 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));
2490
+ }
2491
+
2492
+ .data-\[side\=left\]\:-translate-x-1[data-side="left"] {
2493
+ --tw-translate-x: -0.25rem;
2494
+ 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));
2495
+ }
2496
+
2497
+ .data-\[side\=right\]\:translate-x-1[data-side="right"] {
2498
+ --tw-translate-x: 0.25rem;
2499
+ 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));
2500
+ }
2501
+
2502
+ .data-\[side\=top\]\:-translate-y-1[data-side="top"] {
2503
+ --tw-translate-y: -0.25rem;
2504
+ 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));
2505
+ }
2506
+
2507
+ .data-\[state\=active\]\:bg-background[data-state="active"] {
2508
+ background-color: hsl(var(--background));
2509
+ }
2510
+
2511
+ .data-\[state\=selected\]\:bg-muted[data-state="selected"] {
2512
+ background-color: hsl(var(--muted));
2513
+ }
2514
+
2515
+ .data-\[state\=active\]\:text-foreground[data-state="active"] {
2516
+ color: hsl(var(--foreground));
2517
+ }
2518
+
2519
+ .data-\[disabled\]\:opacity-50[data-disabled] {
2520
+ opacity: 0.5;
2521
+ }
2522
+
2523
+ .data-\[state\=active\]\:shadow-sm[data-state="active"] {
2524
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
2525
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
2526
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2527
+ }
2528
+
2529
+ .data-\[state\=open\]\:animate-in[data-state="open"] {
2530
+ animation-name: enter;
2531
+ animation-duration: 150ms;
2532
+ --tw-enter-opacity: initial;
2533
+ --tw-enter-scale: initial;
2534
+ --tw-enter-rotate: initial;
2535
+ --tw-enter-translate-x: initial;
2536
+ --tw-enter-translate-y: initial;
2537
+ }
2538
+
2539
+ .data-\[state\=closed\]\:animate-out[data-state="closed"] {
2540
+ animation-name: exit;
2541
+ animation-duration: 150ms;
2542
+ --tw-exit-opacity: initial;
2543
+ --tw-exit-scale: initial;
2544
+ --tw-exit-rotate: initial;
2545
+ --tw-exit-translate-x: initial;
2546
+ --tw-exit-translate-y: initial;
2547
+ }
2548
+
2549
+ .data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
2550
+ --tw-exit-opacity: 0;
2551
+ }
2552
+
2553
+ .data-\[state\=open\]\:fade-in-0[data-state="open"] {
2554
+ --tw-enter-opacity: 0;
2555
+ }
2556
+
2557
+ .data-\[state\=closed\]\:zoom-out-95[data-state="closed"] {
2558
+ --tw-exit-scale: .95;
2559
+ }
2560
+
2561
+ .data-\[state\=open\]\:zoom-in-95[data-state="open"] {
2562
+ --tw-enter-scale: .95;
2563
+ }
2564
+
2565
+ .data-\[side\=bottom\]\:slide-in-from-top-2[data-side="bottom"] {
2566
+ --tw-enter-translate-y: -0.5rem;
2567
+ }
2568
+
2569
+ .data-\[side\=left\]\:slide-in-from-right-2[data-side="left"] {
2570
+ --tw-enter-translate-x: 0.5rem;
2571
+ }
2572
+
2573
+ .data-\[side\=right\]\:slide-in-from-left-2[data-side="right"] {
2574
+ --tw-enter-translate-x: -0.5rem;
2575
+ }
2576
+
2577
+ .data-\[side\=top\]\:slide-in-from-bottom-2[data-side="top"] {
2578
+ --tw-enter-translate-y: 0.5rem;
2579
+ }
2580
+
2581
+ .group[data-checked="true"] .group-data-\[checked\=true\]\:flex {
2582
+ display: flex;
2583
+ }
2584
+
2585
+ .group[data-state="checked"] .group-data-\[state\=checked\]\:flex {
2586
+ display: flex;
2587
+ }
2588
+
2589
+ .group[data-state="open"] .group-data-\[state\=open\]\:bg-accent {
2590
+ background-color: hsl(var(--accent));
2591
+ }
2592
+
2593
+ .group[data-state="open"] .group-data-\[state\=open\]\:text-muted-foreground {
2594
+ color: hsl(var(--muted-foreground));
2595
+ }
2596
+
2597
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:duration-300 {
2598
+ transition-duration: 300ms;
2599
+ }
2600
+
2601
+ .group[data-state="open"] .group-data-\[state\=open\]\:duration-500 {
2602
+ transition-duration: 500ms;
2603
+ }
2604
+
2605
+ .group[data-state="open"] .group-data-\[state\=open\]\:animate-in {
2606
+ animation-name: enter;
2607
+ animation-duration: 150ms;
2608
+ --tw-enter-opacity: initial;
2609
+ --tw-enter-scale: initial;
2610
+ --tw-enter-rotate: initial;
2611
+ --tw-enter-translate-x: initial;
2612
+ --tw-enter-translate-y: initial;
2613
+ }
2614
+
2615
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:animate-out {
2616
+ animation-name: exit;
2617
+ animation-duration: 150ms;
2618
+ --tw-exit-opacity: initial;
2619
+ --tw-exit-scale: initial;
2620
+ --tw-exit-rotate: initial;
2621
+ --tw-exit-translate-x: initial;
2622
+ --tw-exit-translate-y: initial;
2623
+ }
2624
+
2625
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:fade-out-0 {
2626
+ --tw-exit-opacity: 0;
2627
+ }
2628
+
2629
+ .group[data-state="open"] .group-data-\[state\=open\]\:fade-in-0 {
2630
+ --tw-enter-opacity: 0;
2631
+ }
2632
+
2633
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:zoom-out-95 {
2634
+ --tw-exit-scale: .95;
2635
+ }
2636
+
2637
+ .group[data-state="open"] .group-data-\[state\=open\]\:zoom-in-95 {
2638
+ --tw-enter-scale: .95;
2639
+ }
2640
+
2641
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-bottom {
2642
+ --tw-exit-translate-y: 100%;
2643
+ }
2644
+
2645
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-left {
2646
+ --tw-exit-translate-x: -100%;
2647
+ }
2648
+
2649
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-left-1\/2 {
2650
+ --tw-exit-translate-x: -50%;
2651
+ }
2652
+
2653
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-right {
2654
+ --tw-exit-translate-x: 100%;
2655
+ }
2656
+
2657
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-top {
2658
+ --tw-exit-translate-y: -100%;
2659
+ }
2660
+
2661
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:slide-out-to-top-\[48\%\] {
2662
+ --tw-exit-translate-y: -48%;
2663
+ }
2664
+
2665
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-bottom {
2666
+ --tw-enter-translate-y: 100%;
2667
+ }
2668
+
2669
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-left {
2670
+ --tw-enter-translate-x: -100%;
2671
+ }
2672
+
2673
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-left-1\/2 {
2674
+ --tw-enter-translate-x: -50%;
2675
+ }
2676
+
2677
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-right {
2678
+ --tw-enter-translate-x: 100%;
2679
+ }
2680
+
2681
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-top {
2682
+ --tw-enter-translate-y: -100%;
2683
+ }
2684
+
2685
+ .group[data-state="open"] .group-data-\[state\=open\]\:slide-in-from-top-\[48\%\] {
2686
+ --tw-enter-translate-y: -48%;
2687
+ }
2688
+
2689
+ .group[data-state="closed"] .group-data-\[state\=closed\]\:duration-300 {
2690
+ animation-duration: 300ms;
2691
+ }
2692
+
2693
+ .group[data-state="open"] .group-data-\[state\=open\]\:duration-500 {
2694
+ animation-duration: 500ms;
2695
+ }
2696
+
2697
+ @media (min-width: 640px) {
2698
+ .sm\:bottom-0 {
2699
+ bottom: 0px;
2700
+ }
2701
+
2702
+ .sm\:right-0 {
2703
+ right: 0px;
2704
+ }
2705
+
2706
+ .sm\:top-auto {
2707
+ top: auto;
2708
+ }
2709
+
2710
+ .sm\:flex {
2711
+ display: flex;
2712
+ }
2713
+
2714
+ .sm\:max-w-sm {
2715
+ max-width: 24rem;
2716
+ }
2717
+
2718
+ .sm\:flex-row {
2719
+ flex-direction: row;
2720
+ }
2721
+
2722
+ .sm\:flex-col {
2723
+ flex-direction: column;
2724
+ }
2725
+
2726
+ .sm\:justify-end {
2727
+ justify-content: flex-end;
2728
+ }
2729
+
2730
+ .sm\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
2731
+ --tw-space-x-reverse: 0;
2732
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
2733
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
2734
+ }
2735
+
2736
+ .sm\:rounded-lg {
2737
+ border-radius: var(--radius);
2738
+ }
2739
+
2740
+ .sm\:text-left {
2741
+ text-align: left;
2742
+ }
2743
+ }
2744
+
2745
+ @media (min-width: 768px) {
2746
+ .md\:block {
2747
+ display: block;
2748
+ }
2749
+
2750
+ .md\:max-w-\[420px\] {
2751
+ max-width: 420px;
2752
+ }
2753
+
2754
+ .md\:grid-cols-\[minmax\(0\2c _45fr\)_minmax\(0\2c _15fr\)\] {
2755
+ grid-template-columns: minmax(0, 45fr) minmax(0, 15fr);
2756
+ }
2757
+
2758
+ .md\:text-2xl {
2759
+ font-size: 1.5rem;
2760
+ line-height: 2rem;
2761
+ }
2762
+
2763
+ .md\:text-7xl {
2764
+ font-size: 4.5rem;
2765
+ line-height: 1;
2766
+ }
2767
+
2768
+ .md\:tracking-wide {
2769
+ letter-spacing: 0.025em;
2770
+ }
2771
+
2772
+ .md\:tracking-wider {
2773
+ letter-spacing: 0.05em;
2774
+ }
2775
+ }
2776
+
2777
+ @media (min-width: 1280px) {
2778
+ .xl\:grid-rows-\[1fr_2fr\] {
2779
+ grid-template-rows: 1fr 2fr;
2780
+ }
2781
+ }
2782
+
2783
+ .dark\:block:where(.dark, .dark *) {
2784
+ display: block;
2785
+ }
2786
+
2787
+ .dark\:flex:where(.dark, .dark *) {
2788
+ display: flex;
2789
+ }
2790
+
2791
+ .dark\:hidden:where(.dark, .dark *) {
2792
+ display: none;
2793
+ }
2794
+
2795
+ .dark\:border-destructive:where(.dark, .dark *) {
2796
+ border-color: hsl(var(--destructive));
2797
+ }
2798
+
2799
+ .dark\:bg-background:where(.dark, .dark *) {
2800
+ background-color: hsl(var(--background));
2801
+ }
2802
+
2803
+ .dark\:from-\[\#1c2024\]:where(.dark, .dark *) {
2804
+ --tw-gradient-from: #1c2024 var(--tw-gradient-from-position);
2805
+ --tw-gradient-to: rgb(28 32 36 / 0) var(--tw-gradient-to-position);
2806
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2807
+ }
2808
+
2809
+ .dark\:from-white:where(.dark, .dark *) {
2810
+ --tw-gradient-from: #fff var(--tw-gradient-from-position);
2811
+ --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
2812
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2813
+ }
2814
+
2815
+ .dark\:to-slate-300:where(.dark, .dark *) {
2816
+ --tw-gradient-to: #cbd5e1 var(--tw-gradient-to-position);
2817
+ }
2818
+
2819
+ .dark\:hover\:border-white:hover:where(.dark, .dark *) {
2820
+ --tw-border-opacity: 1;
2821
+ border-color: rgb(255 255 255 / var(--tw-border-opacity));
2822
+ }
2823
+
2824
+ .\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]) {
2825
+ padding-right: 0px;
2826
+ }
2827
+
2828
+ .\[\&\>span\]\:line-clamp-1>span {
2829
+ overflow: hidden;
2830
+ display: -webkit-box;
2831
+ -webkit-box-orient: vertical;
2832
+ -webkit-line-clamp: 1;
2833
+ }
2834
+
2835
+ .\[\&\>span\]\:translate-x-0>span {
2836
+ --tw-translate-x: 0px;
2837
+ 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));
2838
+ }
2839
+
2840
+ .peer:checked ~ .peer-checked\:\[\&\>span\]\:flex>span {
2841
+ display: flex;
2842
+ }
2843
+
2844
+ .peer:checked ~ .peer-checked\:\[\&\>span\]\:translate-x-5>span {
2845
+ --tw-translate-x: 1.25rem;
2846
+ 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));
2847
+ }
2848
+
2849
+ .\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div {
2850
+ --tw-translate-y: -3px;
2851
+ 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));
2852
+ }
2853
+
2854
+ .\[\&\>svg\]\:absolute>svg {
2855
+ position: absolute;
2856
+ }
2857
+
2858
+ .\[\&\>svg\]\:left-4>svg {
2859
+ left: 1rem;
2860
+ }
2861
+
2862
+ .\[\&\>svg\]\:top-4>svg {
2863
+ top: 1rem;
2864
+ }
2865
+
2866
+ .\[\&\>svg\]\:text-destructive>svg {
2867
+ color: hsl(var(--destructive));
2868
+ }
2869
+
2870
+ .\[\&\>svg\]\:text-foreground>svg {
2871
+ color: hsl(var(--foreground));
2872
+ }
2873
+
2874
+ .\[\&\>svg\~\*\]\:pl-7>svg~* {
2875
+ padding-left: 1.75rem;
2876
+ }
2877
+
2878
+ .\[\&\>tr\]\:last\:border-b-0:last-child>tr {
2879
+ border-bottom-width: 0px;
2880
+ }
2881
+
2882
+ .\[\&_p\]\:leading-relaxed p {
2883
+ line-height: 1.625;
2884
+ }
2885
+
2886
+ .\[\&_tr\:last-child\]\:border-0 tr:last-child {
2887
+ border-width: 0px;
2888
+ }
2889
+
2890
+ .\[\&_tr\]\:border-b tr {
2891
+ border-bottom-width: 1px;
2892
+ }
2893
+
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.10.9
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==43.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.14.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.4
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.28.3
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.12
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.3
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==75.1.0
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.39.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.4.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.45.1
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.2.3
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/vespa-colpali.png 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 CHANGED
@@ -1,10 +1,10 @@
1
  version = 1
2
- requires-python = ">=3.10"
3
  resolution-markers = [
4
  "python_full_version < '3.11'",
5
  "python_full_version == '3.11.*'",
6
- "python_full_version == '3.12.*'",
7
- "python_full_version >= '3.13'",
8
  ]
9
 
10
  [[package]]
@@ -3693,7 +3693,7 @@ dependencies = [
3693
  { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
3694
  { name = "setuptools", marker = "python_full_version >= '3.12'" },
3695
  { name = "sympy" },
3696
- { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'" },
3697
  { name = "typing-extensions" },
3698
  ]
3699
  wheels = [
@@ -3777,7 +3777,7 @@ name = "triton"
3777
  version = "3.1.0"
3778
  source = { registry = "https://pypi.org/simple" }
3779
  dependencies = [
3780
- { name = "filelock", marker = "python_full_version < '3.13'" },
3781
  ]
3782
  wheels = [
3783
  { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 },
 
1
  version = 1
2
+ requires-python = ">=3.10, <3.13"
3
  resolution-markers = [
4
  "python_full_version < '3.11'",
5
  "python_full_version == '3.11.*'",
6
+ "python_full_version >= '3.12'",
7
+ "python_full_version < '0'",
8
  ]
9
 
10
  [[package]]
 
3693
  { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
3694
  { name = "setuptools", marker = "python_full_version >= '3.12'" },
3695
  { name = "sympy" },
3696
+ { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
3697
  { name = "typing-extensions" },
3698
  ]
3699
  wheels = [
 
3777
  version = "3.1.0"
3778
  source = { registry = "https://pypi.org/simple" }
3779
  dependencies = [
3780
+ { name = "filelock" },
3781
  ]
3782
  wheels = [
3783
  { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 },