Spaces:
Running
on
T4
Running
on
T4
thomasht86
commited on
Commit
β’
e0b54fb
1
Parent(s):
a0b3781
Upload folder using huggingface_hub
Browse files- README.md +0 -2
- backend/vespa_app.py +20 -0
- frontend/app.py +108 -48
- frontend/layout.py +7 -7
- globals.css +12 -0
- icons.py +1 -1
- main.py +31 -9
- output.css +57 -49
README.md
CHANGED
@@ -78,8 +78,6 @@ python hello.py
|
|
78 |
|
79 |
First, set up your `.env` file by renaming `.env.example` to `.env` and filling in the required values.
|
80 |
(Token can be shared with 1password, `HF_TOKEN` is personal and must be created at huggingface)
|
81 |
-
If you are just connecting to a deployed Vespa app, you can skip
|
82 |
-
to [Connecting to the Vespa app](#connecting-to-the-vespa-app-and-querying).
|
83 |
|
84 |
### Deploying the Vespa app
|
85 |
|
|
|
78 |
|
79 |
First, set up your `.env` file by renaming `.env.example` to `.env` and filling in the required values.
|
80 |
(Token can be shared with 1password, `HF_TOKEN` is personal and must be created at huggingface)
|
|
|
|
|
81 |
|
82 |
### Deploying the Vespa app
|
83 |
|
backend/vespa_app.py
CHANGED
@@ -330,3 +330,23 @@ class VespaQueryClient:
|
|
330 |
)
|
331 |
assert response.is_successful(), response.json
|
332 |
return self.format_query_results(query, response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
)
|
331 |
assert response.is_successful(), response.json
|
332 |
return self.format_query_results(query, response)
|
333 |
+
|
334 |
+
async def keepalive(self) -> bool:
|
335 |
+
"""
|
336 |
+
Query Vespa to keep the connection alive.
|
337 |
+
|
338 |
+
Returns:
|
339 |
+
bool: True if the connection is alive.
|
340 |
+
"""
|
341 |
+
async with self.app.asyncio(connections=1) as session:
|
342 |
+
response: VespaQueryResponse = await session.query(
|
343 |
+
body={
|
344 |
+
"yql": f"select title from {self.VESPA_SCHEMA_NAME} where true limit 1;",
|
345 |
+
"ranking": "unranked",
|
346 |
+
"query": "keepalive",
|
347 |
+
"timeout": "3s",
|
348 |
+
"hits": 1,
|
349 |
+
},
|
350 |
+
)
|
351 |
+
assert response.is_successful(), response.json
|
352 |
+
return True
|
frontend/app.py
CHANGED
@@ -62,6 +62,28 @@ image_swapping = Script(
|
|
62 |
"""
|
63 |
)
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
67 |
grid_cls = "grid gap-2 items-center p-3 bg-muted/80 dark:bg-muted/40 w-full"
|
@@ -131,13 +153,11 @@ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
|
131 |
|
132 |
def SampleQueries():
|
133 |
sample_queries = [
|
134 |
-
"Proportion of female new hires 2021-2023?",
|
135 |
"Total amount of fixed salaries paid in 2023?",
|
136 |
-
"
|
137 |
-
"
|
138 |
-
"
|
139 |
-
"
|
140 |
-
"fund currency basket returns",
|
141 |
]
|
142 |
|
143 |
query_badges = []
|
@@ -167,13 +187,13 @@ def Hero():
|
|
167 |
return Div(
|
168 |
H1(
|
169 |
"Vespa.ai + ColPali",
|
170 |
-
cls="text-
|
171 |
),
|
172 |
P(
|
173 |
"Efficient Document Retrieval with Vision Language Models",
|
174 |
cls="text-lg md:text-2xl text-muted-foreground md:tracking-wide",
|
175 |
),
|
176 |
-
cls="grid gap-5 text-center
|
177 |
)
|
178 |
|
179 |
|
@@ -183,7 +203,7 @@ def Home():
|
|
183 |
Hero(),
|
184 |
SearchBox(with_border=True),
|
185 |
SampleQueries(),
|
186 |
-
cls="grid gap-8
|
187 |
),
|
188 |
cls="grid w-full h-full max-w-screen-md items-center gap-4 mx-auto",
|
189 |
)
|
@@ -219,6 +239,15 @@ def LoadingMessage(display_text="Retrieving search results"):
|
|
219 |
)
|
220 |
|
221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
def SimMapButtonReady(query_id, idx, token, img_src):
|
223 |
return Button(
|
224 |
token,
|
@@ -310,76 +339,107 @@ def SearchResult(results: list, query_id: Optional[str] = None):
|
|
310 |
Div(
|
311 |
Div(
|
312 |
Div(
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
cls="flex flex-wrap gap-px w-full pointer-events-none",
|
317 |
),
|
318 |
Div(
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
hx_trigger="load",
|
325 |
-
hx_swap="outerHTML",
|
326 |
-
alt=fields["title"],
|
327 |
-
cls="result-image w-full h-full object-contain",
|
328 |
-
),
|
329 |
-
Div(
|
330 |
-
cls="overlay-container absolute top-0 left-0 w-full h-full pointer-events-none"
|
331 |
-
),
|
332 |
-
cls="relative w-full h-full",
|
333 |
),
|
334 |
-
cls="grid bg-border p-2",
|
335 |
),
|
336 |
-
cls="
|
337 |
),
|
338 |
Div(
|
339 |
Div(
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
344 |
),
|
|
|
|
|
|
|
|
|
345 |
Div(
|
|
|
|
|
|
|
|
|
346 |
Badge(
|
347 |
f"Relevance score: {result['relevance']:.4f}",
|
348 |
cls="flex gap-1.5 items-center justify-center",
|
349 |
),
|
|
|
350 |
),
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
358 |
),
|
359 |
-
|
|
|
360 |
),
|
361 |
-
|
|
|
362 |
),
|
363 |
-
cls="grid grid-cols-1
|
364 |
-
)
|
365 |
)
|
366 |
|
367 |
return Div(
|
368 |
*result_items,
|
369 |
image_swapping,
|
|
|
370 |
id="search-results",
|
371 |
-
cls="grid grid-cols-
|
372 |
)
|
373 |
|
374 |
|
375 |
def ChatResult(query_id: str, query: str):
|
376 |
return Div(
|
377 |
-
Div("
|
378 |
Div(
|
379 |
Div(
|
380 |
Div(
|
381 |
-
|
382 |
-
cls="bg-muted/80 dark:bg-muted/40 text-black dark:text-white p-2 rounded-md",
|
383 |
hx_ext="sse",
|
384 |
sse_connect=f"/get-message?query_id={query_id}&query={quote_plus(query)}",
|
385 |
sse_swap="message",
|
|
|
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 |
|
88 |
def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
89 |
grid_cls = "grid gap-2 items-center p-3 bg-muted/80 dark:bg-muted/40 w-full"
|
|
|
153 |
|
154 |
def SampleQueries():
|
155 |
sample_queries = [
|
|
|
156 |
"Total amount of fixed salaries paid in 2023?",
|
157 |
+
"Proportion of female new hires 2021-2023?",
|
158 |
+
"Value of unlisted real estate 2023?",
|
159 |
+
"Fund currency basket returns 2023",
|
160 |
+
"Employees per office site?",
|
|
|
161 |
]
|
162 |
|
163 |
query_badges = []
|
|
|
187 |
return Div(
|
188 |
H1(
|
189 |
"Vespa.ai + ColPali",
|
190 |
+
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-gray-700 dark:from-white dark:to-gray-300 animate-fade-in",
|
191 |
),
|
192 |
P(
|
193 |
"Efficient Document Retrieval with Vision Language Models",
|
194 |
cls="text-lg md:text-2xl text-muted-foreground md:tracking-wide",
|
195 |
),
|
196 |
+
cls="grid gap-5 text-center",
|
197 |
)
|
198 |
|
199 |
|
|
|
203 |
Hero(),
|
204 |
SearchBox(with_border=True),
|
205 |
SampleQueries(),
|
206 |
+
cls="grid gap-8 -mt-[21vh]",
|
207 |
),
|
208 |
cls="grid w-full h-full max-w-screen-md items-center gap-4 mx-auto",
|
209 |
)
|
|
|
239 |
)
|
240 |
|
241 |
|
242 |
+
def LoadingSkeleton():
|
243 |
+
return Div(
|
244 |
+
Div(cls="h-5 bg-muted"),
|
245 |
+
Div(cls="h-5 bg-muted"),
|
246 |
+
Div(cls="h-5 bg-muted"),
|
247 |
+
cls="grid gap-2 animate-pulse",
|
248 |
+
)
|
249 |
+
|
250 |
+
|
251 |
def SimMapButtonReady(query_id, idx, token, img_src):
|
252 |
return Button(
|
253 |
token,
|
|
|
339 |
Div(
|
340 |
Div(
|
341 |
Div(
|
342 |
+
Lucide(icon="file-text"),
|
343 |
+
H2(fields["title"], cls="text-xl md:text-2xl font-semibold"),
|
344 |
+
cls="flex items-center gap-2",
|
|
|
345 |
),
|
346 |
Div(
|
347 |
+
Button(
|
348 |
+
"Show Text",
|
349 |
+
size="sm",
|
350 |
+
id=f"toggle-button-{idx}",
|
351 |
+
onclick=f"toggleTextContent({idx})",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
),
|
|
|
353 |
),
|
354 |
+
cls="flex flex-wrap items-center justify-between bg-background px-3 py-4",
|
355 |
),
|
356 |
Div(
|
357 |
Div(
|
358 |
+
Div(
|
359 |
+
tokens_button,
|
360 |
+
*sim_map_buttons,
|
361 |
+
reset_button,
|
362 |
+
cls="flex flex-wrap gap-px w-full pointer-events-none",
|
363 |
+
),
|
364 |
+
Div(
|
365 |
+
Div(
|
366 |
+
Div(
|
367 |
+
Img(
|
368 |
+
src=blur_image_base64,
|
369 |
+
hx_get=f"/full_image?docid={fields['id']}&query_id={query_id}&idx={idx}",
|
370 |
+
style="backdrop-filter: blur(5px);",
|
371 |
+
hx_trigger="load",
|
372 |
+
hx_swap="outerHTML",
|
373 |
+
alt=fields["title"],
|
374 |
+
cls="result-image w-full h-full object-contain",
|
375 |
+
),
|
376 |
+
Div(
|
377 |
+
cls="overlay-container absolute top-0 left-0 w-full h-full pointer-events-none"
|
378 |
+
),
|
379 |
+
cls="relative w-full h-full",
|
380 |
+
),
|
381 |
+
cls="grid bg-border p-2",
|
382 |
+
),
|
383 |
+
cls="block",
|
384 |
),
|
385 |
+
id=f"image-column-{idx}",
|
386 |
+
cls="image-column relative bg-background px-3 py-5 grid-image-column",
|
387 |
+
),
|
388 |
+
Div(
|
389 |
Div(
|
390 |
+
P(
|
391 |
+
"Page " + str(fields["page_number"]),
|
392 |
+
cls="text-foreground font-mono bold text-sm",
|
393 |
+
),
|
394 |
Badge(
|
395 |
f"Relevance score: {result['relevance']:.4f}",
|
396 |
cls="flex gap-1.5 items-center justify-center",
|
397 |
),
|
398 |
+
cls="flex items-center justify-between",
|
399 |
),
|
400 |
+
Div(
|
401 |
+
Div(
|
402 |
+
Div(
|
403 |
+
P(
|
404 |
+
NotStr(fields.get("snippet", "")),
|
405 |
+
cls="text-highlight text-muted-foreground",
|
406 |
+
),
|
407 |
+
P(
|
408 |
+
NotStr(fields.get("text", "")),
|
409 |
+
cls="text-highlight text-muted-foreground",
|
410 |
+
),
|
411 |
+
cls="grid gap-y-3 p-5 text-sm",
|
412 |
+
),
|
413 |
+
cls="grid bg-background content-start ",
|
414 |
+
),
|
415 |
+
cls="grid bg-border p-2",
|
416 |
),
|
417 |
+
id=f"text-column-{idx}",
|
418 |
+
cls="text-column relative bg-background px-3 py-5 hidden",
|
419 |
),
|
420 |
+
id=f"image-text-columns-{idx}",
|
421 |
+
cls="relative grid grid-cols-1 border-t",
|
422 |
),
|
423 |
+
cls="grid grid-cols-1 grid-rows-[auto_1fr]",
|
424 |
+
),
|
425 |
)
|
426 |
|
427 |
return Div(
|
428 |
*result_items,
|
429 |
image_swapping,
|
430 |
+
toggle_text_content,
|
431 |
id="search-results",
|
432 |
+
cls="grid grid-cols-1 gap-px bg-border",
|
433 |
)
|
434 |
|
435 |
|
436 |
def ChatResult(query_id: str, query: str):
|
437 |
return Div(
|
438 |
+
Div("LLM Response", cls="text-xl font-semibold p-3"),
|
439 |
Div(
|
440 |
Div(
|
441 |
Div(
|
442 |
+
LoadingSkeleton(),
|
|
|
443 |
hx_ext="sse",
|
444 |
sse_connect=f"/get-message?query_id={query_id}&query={quote_plus(query)}",
|
445 |
sse_swap="message",
|
frontend/layout.py
CHANGED
@@ -3,7 +3,7 @@ from fasthtml.xtend import A, Script
|
|
3 |
from lucide_fasthtml import Lucide
|
4 |
from shad4fast import Button, Separator
|
5 |
|
6 |
-
|
7 |
"""
|
8 |
document.addEventListener("DOMContentLoaded", function () {
|
9 |
const main = document.querySelector('main');
|
@@ -11,12 +11,12 @@ script = Script(
|
|
11 |
const body = document.body;
|
12 |
|
13 |
if (main && aside && main.nextElementSibling === aside) {
|
14 |
-
//
|
15 |
-
body.classList.
|
16 |
-
|
17 |
} else if (main) {
|
18 |
-
//
|
19 |
-
body.classList.add('grid-cols-
|
20 |
}
|
21 |
});
|
22 |
"""
|
@@ -136,6 +136,6 @@ def Layout(*c, **kwargs):
|
|
136 |
**kwargs,
|
137 |
cls="grid grid-rows-[55px_1fr] min-h-0",
|
138 |
),
|
139 |
-
|
140 |
overlay_scrollbars,
|
141 |
)
|
|
|
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');
|
|
|
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 |
"""
|
|
|
136 |
**kwargs,
|
137 |
cls="grid grid-rows-[55px_1fr] min-h-0",
|
138 |
),
|
139 |
+
layout_script,
|
140 |
overlay_scrollbars,
|
141 |
)
|
globals.css
CHANGED
@@ -205,3 +205,15 @@ aside {
|
|
205 |
background-color: #61D790;
|
206 |
color: #2E2F27;
|
207 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
background-color: #61D790;
|
206 |
color: #2E2F27;
|
207 |
}
|
208 |
+
|
209 |
+
.grid-image-text-columns {
|
210 |
+
@apply md:grid-cols-2 md:col-span-2
|
211 |
+
}
|
212 |
+
|
213 |
+
.grid-image-column {
|
214 |
+
@apply grid grid-rows-subgrid row-span-2 content-start;
|
215 |
+
}
|
216 |
+
|
217 |
+
.md-grid-text-column {
|
218 |
+
@apply md:grid md:grid-rows-subgrid md:row-span-2 md:content-start;
|
219 |
+
}
|
icons.py
CHANGED
@@ -1 +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>"}
|
|
|
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>"}
|
main.py
CHANGED
@@ -1,11 +1,15 @@
|
|
1 |
import asyncio
|
|
|
2 |
import hashlib
|
|
|
|
|
3 |
import time
|
4 |
from concurrent.futures import ThreadPoolExecutor
|
5 |
from functools import partial
|
6 |
-
import os
|
7 |
|
|
|
8 |
from fasthtml.common import *
|
|
|
9 |
from shad4fast import *
|
10 |
from vespa.application import Vespa
|
11 |
|
@@ -27,10 +31,6 @@ from frontend.app import (
|
|
27 |
SimMapButtonReady,
|
28 |
)
|
29 |
from frontend.layout import Layout
|
30 |
-
import google.generativeai as genai
|
31 |
-
from PIL import Image
|
32 |
-
import io
|
33 |
-
import base64
|
34 |
|
35 |
highlight_js_theme_link = Link(id="highlight-theme", rel="stylesheet", href="")
|
36 |
highlight_js_theme = Script(src="/static/js/highlightjs-theme.js")
|
@@ -73,7 +73,9 @@ thread_pool = ThreadPoolExecutor()
|
|
73 |
|
74 |
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
|
75 |
GEMINI_SYSTEM_PROMPT = """If the user query is a question, try your best to answer it based on the provided images.
|
76 |
-
If the user query
|
|
|
|
|
77 |
This means that newlines will be replaced with <br> tags, bold text will be enclosed in <b> tags, and so on.
|
78 |
But, you should NOT include backticks (`) or HTML tags in your response.
|
79 |
"""
|
@@ -88,6 +90,12 @@ def load_model_on_startup():
|
|
88 |
return
|
89 |
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
def generate_query_id(query):
|
92 |
return hashlib.md5(query.encode("utf-8")).hexdigest()
|
93 |
|
@@ -139,7 +147,8 @@ def get(request):
|
|
139 |
return Layout(
|
140 |
Main(Search(request), data_overlayscrollbars_initialize=True, cls="border-t"),
|
141 |
Aside(
|
142 |
-
ChatResult(query_id=query_id, query=query_value),
|
|
|
143 |
),
|
144 |
) # Show SearchBox and Loading message initially
|
145 |
|
@@ -207,6 +216,13 @@ def get_results_children(result):
|
|
207 |
return search_results
|
208 |
|
209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
async def generate_similarity_map(
|
211 |
model, processor, query, q_embs, token_to_idx, result, query_id
|
212 |
):
|
@@ -290,7 +306,9 @@ async def message_generator(query_id: str, query: str):
|
|
290 |
images = []
|
291 |
result = None
|
292 |
all_images_ready = False
|
293 |
-
|
|
|
|
|
294 |
result = result_cache.get(query_id)
|
295 |
if result is None:
|
296 |
await asyncio.sleep(0.1)
|
@@ -308,6 +326,10 @@ async def message_generator(query_id: str, query: str):
|
|
308 |
|
309 |
# from b64 to PIL image
|
310 |
images = [Image.open(io.BytesIO(base64.b64decode(img))) for img in images]
|
|
|
|
|
|
|
|
|
311 |
|
312 |
# If newlines are present in the response, the connection will be closed.
|
313 |
def replace_newline_with_br(text):
|
@@ -321,7 +343,7 @@ async def message_generator(query_id: str, query: str):
|
|
321 |
response_text += chunk.text
|
322 |
response_text = replace_newline_with_br(response_text)
|
323 |
yield f"event: message\ndata: {response_text}\n\n"
|
324 |
-
await asyncio.sleep(0.
|
325 |
yield "event: close\ndata: \n\n"
|
326 |
|
327 |
|
|
|
1 |
import asyncio
|
2 |
+
import base64
|
3 |
import hashlib
|
4 |
+
import io
|
5 |
+
import os
|
6 |
import time
|
7 |
from concurrent.futures import ThreadPoolExecutor
|
8 |
from functools import partial
|
|
|
9 |
|
10 |
+
import google.generativeai as genai
|
11 |
from fasthtml.common import *
|
12 |
+
from PIL import Image
|
13 |
from shad4fast import *
|
14 |
from vespa.application import Vespa
|
15 |
|
|
|
31 |
SimMapButtonReady,
|
32 |
)
|
33 |
from frontend.layout import Layout
|
|
|
|
|
|
|
|
|
34 |
|
35 |
highlight_js_theme_link = Link(id="highlight-theme", rel="stylesheet", href="")
|
36 |
highlight_js_theme = Script(src="/static/js/highlightjs-theme.js")
|
|
|
73 |
|
74 |
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
|
75 |
GEMINI_SYSTEM_PROMPT = """If the user query is a question, try your best to answer it based on the provided images.
|
76 |
+
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,
|
77 |
+
answer with the exact phrase "I am sorry, I do not have enough information in the image to answer your question.".
|
78 |
+
Your response should be HTML formatted, but only simple tags, such as <b>. <p>, <i>, <br> <ul> and <li> are allowed. No HTML tables.
|
79 |
This means that newlines will be replaced with <br> tags, bold text will be enclosed in <b> tags, and so on.
|
80 |
But, you should NOT include backticks (`) or HTML tags in your response.
|
81 |
"""
|
|
|
90 |
return
|
91 |
|
92 |
|
93 |
+
@app.on_event("startup")
|
94 |
+
async def keepalive():
|
95 |
+
asyncio.create_task(poll_vespa_keepalive())
|
96 |
+
return
|
97 |
+
|
98 |
+
|
99 |
def generate_query_id(query):
|
100 |
return hashlib.md5(query.encode("utf-8")).hexdigest()
|
101 |
|
|
|
147 |
return Layout(
|
148 |
Main(Search(request), data_overlayscrollbars_initialize=True, cls="border-t"),
|
149 |
Aside(
|
150 |
+
ChatResult(query_id=query_id, query=query_value),
|
151 |
+
cls="border-t border-l hidden md:block",
|
152 |
),
|
153 |
) # Show SearchBox and Loading message initially
|
154 |
|
|
|
216 |
return search_results
|
217 |
|
218 |
|
219 |
+
async def poll_vespa_keepalive():
|
220 |
+
while True:
|
221 |
+
await asyncio.sleep(5)
|
222 |
+
await vespa_app.keepalive()
|
223 |
+
print(f"Vespa keepalive: {time.time()}")
|
224 |
+
|
225 |
+
|
226 |
async def generate_similarity_map(
|
227 |
model, processor, query, q_embs, token_to_idx, result, query_id
|
228 |
):
|
|
|
306 |
images = []
|
307 |
result = None
|
308 |
all_images_ready = False
|
309 |
+
max_wait = 10 # seconds
|
310 |
+
start_time = time.time()
|
311 |
+
while not all_images_ready and time.time() - start_time < max_wait:
|
312 |
result = result_cache.get(query_id)
|
313 |
if result is None:
|
314 |
await asyncio.sleep(0.1)
|
|
|
326 |
|
327 |
# from b64 to PIL image
|
328 |
images = [Image.open(io.BytesIO(base64.b64decode(img))) for img in images]
|
329 |
+
if not images:
|
330 |
+
yield "event: message\ndata: I am sorry, I do not have enough information in the image to answer your question.\n\n"
|
331 |
+
yield "event: close\ndata: \n\n"
|
332 |
+
return
|
333 |
|
334 |
# If newlines are present in the response, the connection will be closed.
|
335 |
def replace_newline_with_br(text):
|
|
|
343 |
response_text += chunk.text
|
344 |
response_text = replace_newline_with_br(response_text)
|
345 |
yield f"event: message\ndata: {response_text}\n\n"
|
346 |
+
await asyncio.sleep(0.1)
|
347 |
yield "event: close\ndata: \n\n"
|
348 |
|
349 |
|
output.css
CHANGED
@@ -766,10 +766,6 @@ body {
|
|
766 |
z-index: 100;
|
767 |
}
|
768 |
|
769 |
-
.col-span-2 {
|
770 |
-
grid-column: span 2 / span 2;
|
771 |
-
}
|
772 |
-
|
773 |
.-mx-1 {
|
774 |
margin-left: -0.25rem;
|
775 |
margin-right: -0.25rem;
|
@@ -793,8 +789,8 @@ body {
|
|
793 |
margin-top: -1rem;
|
794 |
}
|
795 |
|
796 |
-
.-mt-\[
|
797 |
-
margin-top: -
|
798 |
}
|
799 |
|
800 |
.mb-1 {
|
@@ -1056,6 +1052,16 @@ body {
|
|
1056 |
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));
|
1057 |
}
|
1058 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1059 |
@keyframes spin {
|
1060 |
to {
|
1061 |
transform: rotate(360deg);
|
@@ -1096,22 +1102,14 @@ body {
|
|
1096 |
grid-template-columns: repeat(1, minmax(0, 1fr));
|
1097 |
}
|
1098 |
|
1099 |
-
.grid-cols-2 {
|
1100 |
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
1101 |
-
}
|
1102 |
-
|
1103 |
-
.grid-cols-\[1fr\] {
|
1104 |
-
grid-template-columns: 1fr;
|
1105 |
-
}
|
1106 |
-
|
1107 |
-
.grid-cols-\[minmax\(0\2c _4fr\)_minmax\(0\2c _1fr\)\] {
|
1108 |
-
grid-template-columns: minmax(0, 4fr) minmax(0, 1fr);
|
1109 |
-
}
|
1110 |
-
|
1111 |
.grid-rows-\[55px_1fr\] {
|
1112 |
grid-template-rows: 55px 1fr;
|
1113 |
}
|
1114 |
|
|
|
|
|
|
|
|
|
1115 |
.grid-rows-\[auto_1fr_auto\] {
|
1116 |
grid-template-rows: auto 1fr auto;
|
1117 |
}
|
@@ -1140,10 +1138,6 @@ body {
|
|
1140 |
align-items: center;
|
1141 |
}
|
1142 |
|
1143 |
-
.justify-end {
|
1144 |
-
justify-content: flex-end;
|
1145 |
-
}
|
1146 |
-
|
1147 |
.justify-center {
|
1148 |
justify-content: center;
|
1149 |
}
|
@@ -1198,8 +1192,8 @@ body {
|
|
1198 |
column-gap: 1.25rem;
|
1199 |
}
|
1200 |
|
1201 |
-
.gap-y-
|
1202 |
-
row-gap:
|
1203 |
}
|
1204 |
|
1205 |
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
@@ -1232,10 +1226,6 @@ body {
|
|
1232 |
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
1233 |
}
|
1234 |
|
1235 |
-
.self-end {
|
1236 |
-
align-self: flex-end;
|
1237 |
-
}
|
1238 |
-
|
1239 |
.self-stretch {
|
1240 |
align-self: stretch;
|
1241 |
}
|
@@ -1288,11 +1278,6 @@ body {
|
|
1288 |
border-width: 2px;
|
1289 |
}
|
1290 |
|
1291 |
-
.border-x {
|
1292 |
-
border-left-width: 1px;
|
1293 |
-
border-right-width: 1px;
|
1294 |
-
}
|
1295 |
-
|
1296 |
.border-b {
|
1297 |
border-bottom-width: 1px;
|
1298 |
}
|
@@ -1448,6 +1433,10 @@ body {
|
|
1448 |
padding: 1rem;
|
1449 |
}
|
1450 |
|
|
|
|
|
|
|
|
|
1451 |
.p-6 {
|
1452 |
padding: 1.5rem;
|
1453 |
}
|
@@ -1501,6 +1490,11 @@ body {
|
|
1501 |
padding-bottom: 0.5rem;
|
1502 |
}
|
1503 |
|
|
|
|
|
|
|
|
|
|
|
1504 |
.py-5 {
|
1505 |
padding-top: 1.25rem;
|
1506 |
padding-bottom: 1.25rem;
|
@@ -1534,10 +1528,6 @@ body {
|
|
1534 |
padding-top: 1rem;
|
1535 |
}
|
1536 |
|
1537 |
-
.pr-3 {
|
1538 |
-
padding-right: 0.75rem;
|
1539 |
-
}
|
1540 |
-
|
1541 |
.text-left {
|
1542 |
text-align: left;
|
1543 |
}
|
@@ -1622,11 +1612,6 @@ body {
|
|
1622 |
letter-spacing: 0.025em;
|
1623 |
}
|
1624 |
|
1625 |
-
.text-black {
|
1626 |
-
--tw-text-opacity: 1;
|
1627 |
-
color: rgb(0 0 0 / var(--tw-text-opacity));
|
1628 |
-
}
|
1629 |
-
|
1630 |
.text-card-foreground {
|
1631 |
color: hsl(var(--card-foreground));
|
1632 |
}
|
@@ -1770,6 +1755,11 @@ body {
|
|
1770 |
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);
|
1771 |
}
|
1772 |
|
|
|
|
|
|
|
|
|
|
|
1773 |
.transition {
|
1774 |
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
1775 |
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
@@ -2064,6 +2054,29 @@ aside {
|
|
2064 |
color: #2E2F27;
|
2065 |
}
|
2066 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2067 |
:root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
|
2068 |
overflow: hidden;
|
2069 |
}
|
@@ -2555,8 +2568,8 @@ aside {
|
|
2555 |
max-width: 420px;
|
2556 |
}
|
2557 |
|
2558 |
-
.md\:grid-cols
|
2559 |
-
grid-template-columns:
|
2560 |
}
|
2561 |
|
2562 |
.md\:text-2xl {
|
@@ -2608,11 +2621,6 @@ aside {
|
|
2608 |
--tw-gradient-to: #d1d5db var(--tw-gradient-to-position);
|
2609 |
}
|
2610 |
|
2611 |
-
.dark\:text-white:where(.dark, .dark *) {
|
2612 |
-
--tw-text-opacity: 1;
|
2613 |
-
color: rgb(255 255 255 / var(--tw-text-opacity));
|
2614 |
-
}
|
2615 |
-
|
2616 |
.dark\:hover\:border-white:hover:where(.dark, .dark *) {
|
2617 |
--tw-border-opacity: 1;
|
2618 |
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
|
|
766 |
z-index: 100;
|
767 |
}
|
768 |
|
|
|
|
|
|
|
|
|
769 |
.-mx-1 {
|
770 |
margin-left: -0.25rem;
|
771 |
margin-right: -0.25rem;
|
|
|
789 |
margin-top: -1rem;
|
790 |
}
|
791 |
|
792 |
+
.-mt-\[21vh\] {
|
793 |
+
margin-top: -21vh;
|
794 |
}
|
795 |
|
796 |
.mb-1 {
|
|
|
1052 |
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));
|
1053 |
}
|
1054 |
|
1055 |
+
@keyframes pulse {
|
1056 |
+
50% {
|
1057 |
+
opacity: .5;
|
1058 |
+
}
|
1059 |
+
}
|
1060 |
+
|
1061 |
+
.animate-pulse {
|
1062 |
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
1063 |
+
}
|
1064 |
+
|
1065 |
@keyframes spin {
|
1066 |
to {
|
1067 |
transform: rotate(360deg);
|
|
|
1102 |
grid-template-columns: repeat(1, minmax(0, 1fr));
|
1103 |
}
|
1104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1105 |
.grid-rows-\[55px_1fr\] {
|
1106 |
grid-template-rows: 55px 1fr;
|
1107 |
}
|
1108 |
|
1109 |
+
.grid-rows-\[auto_1fr\] {
|
1110 |
+
grid-template-rows: auto 1fr;
|
1111 |
+
}
|
1112 |
+
|
1113 |
.grid-rows-\[auto_1fr_auto\] {
|
1114 |
grid-template-rows: auto 1fr auto;
|
1115 |
}
|
|
|
1138 |
align-items: center;
|
1139 |
}
|
1140 |
|
|
|
|
|
|
|
|
|
1141 |
.justify-center {
|
1142 |
justify-content: center;
|
1143 |
}
|
|
|
1192 |
column-gap: 1.25rem;
|
1193 |
}
|
1194 |
|
1195 |
+
.gap-y-3 {
|
1196 |
+
row-gap: 0.75rem;
|
1197 |
}
|
1198 |
|
1199 |
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
|
|
1226 |
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
1227 |
}
|
1228 |
|
|
|
|
|
|
|
|
|
1229 |
.self-stretch {
|
1230 |
align-self: stretch;
|
1231 |
}
|
|
|
1278 |
border-width: 2px;
|
1279 |
}
|
1280 |
|
|
|
|
|
|
|
|
|
|
|
1281 |
.border-b {
|
1282 |
border-bottom-width: 1px;
|
1283 |
}
|
|
|
1433 |
padding: 1rem;
|
1434 |
}
|
1435 |
|
1436 |
+
.p-5 {
|
1437 |
+
padding: 1.25rem;
|
1438 |
+
}
|
1439 |
+
|
1440 |
.p-6 {
|
1441 |
padding: 1.5rem;
|
1442 |
}
|
|
|
1490 |
padding-bottom: 0.5rem;
|
1491 |
}
|
1492 |
|
1493 |
+
.py-4 {
|
1494 |
+
padding-top: 1rem;
|
1495 |
+
padding-bottom: 1rem;
|
1496 |
+
}
|
1497 |
+
|
1498 |
.py-5 {
|
1499 |
padding-top: 1.25rem;
|
1500 |
padding-bottom: 1.25rem;
|
|
|
1528 |
padding-top: 1rem;
|
1529 |
}
|
1530 |
|
|
|
|
|
|
|
|
|
1531 |
.text-left {
|
1532 |
text-align: left;
|
1533 |
}
|
|
|
1612 |
letter-spacing: 0.025em;
|
1613 |
}
|
1614 |
|
|
|
|
|
|
|
|
|
|
|
1615 |
.text-card-foreground {
|
1616 |
color: hsl(var(--card-foreground));
|
1617 |
}
|
|
|
1755 |
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);
|
1756 |
}
|
1757 |
|
1758 |
+
.backdrop-filter {
|
1759 |
+
-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);
|
1760 |
+
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);
|
1761 |
+
}
|
1762 |
+
|
1763 |
.transition {
|
1764 |
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
1765 |
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
|
|
2054 |
color: #2E2F27;
|
2055 |
}
|
2056 |
|
2057 |
+
@media (min-width: 768px) {
|
2058 |
+
.grid-image-text-columns {
|
2059 |
+
grid-column: span 2 / span 2;
|
2060 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
2061 |
+
}
|
2062 |
+
}
|
2063 |
+
|
2064 |
+
.grid-image-column {
|
2065 |
+
grid-row: span 2 / span 2;
|
2066 |
+
display: grid;
|
2067 |
+
grid-template-rows: subgrid;
|
2068 |
+
align-content: flex-start;
|
2069 |
+
}
|
2070 |
+
|
2071 |
+
@media (min-width: 768px) {
|
2072 |
+
.md-grid-text-column {
|
2073 |
+
grid-row: span 2 / span 2;
|
2074 |
+
display: grid;
|
2075 |
+
grid-template-rows: subgrid;
|
2076 |
+
align-content: flex-start;
|
2077 |
+
}
|
2078 |
+
}
|
2079 |
+
|
2080 |
:root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
|
2081 |
overflow: hidden;
|
2082 |
}
|
|
|
2568 |
max-width: 420px;
|
2569 |
}
|
2570 |
|
2571 |
+
.md\:grid-cols-\[minmax\(0\2c _45fr\)_minmax\(0\2c _15fr\)\] {
|
2572 |
+
grid-template-columns: minmax(0, 45fr) minmax(0, 15fr);
|
2573 |
}
|
2574 |
|
2575 |
.md\:text-2xl {
|
|
|
2621 |
--tw-gradient-to: #d1d5db var(--tw-gradient-to-position);
|
2622 |
}
|
2623 |
|
|
|
|
|
|
|
|
|
|
|
2624 |
.dark\:hover\:border-white:hover:where(.dark, .dark *) {
|
2625 |
--tw-border-opacity: 1;
|
2626 |
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|