Spaces:
Running
on
L4
Running
on
L4
Upload folder using huggingface_hub
Browse files- backend/vespa_app.py +34 -1
- frontend/app.py +28 -2
- globals.css +56 -0
- main.py +24 -2
- output.css +67 -0
backend/vespa_app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
-
from typing import
|
| 4 |
|
| 5 |
import numpy as np
|
| 6 |
import torch
|
|
@@ -276,6 +276,39 @@ class VespaQueryClient:
|
|
| 276 |
)
|
| 277 |
return response.json["root"]["children"][0]["fields"]["full_image"]
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
async def query_vespa_nearest_neighbor(
|
| 280 |
self,
|
| 281 |
query: str,
|
|
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
+
from typing import Any, Dict, Tuple
|
| 4 |
|
| 5 |
import numpy as np
|
| 6 |
import torch
|
|
|
|
| 276 |
)
|
| 277 |
return response.json["root"]["children"][0]["fields"]["full_image"]
|
| 278 |
|
| 279 |
+
async def get_suggestions(self, query: str) -> list:
|
| 280 |
+
async with self.app.asyncio(connections=1) as session:
|
| 281 |
+
start = time.perf_counter()
|
| 282 |
+
yql = f'select questions from {self.VESPA_SCHEMA_NAME} where questions matches "{query}" limit 3'
|
| 283 |
+
print(yql)
|
| 284 |
+
response: VespaQueryResponse = await session.query(
|
| 285 |
+
body={
|
| 286 |
+
"yql": yql,
|
| 287 |
+
"ranking": "unranked",
|
| 288 |
+
"presentation.timing": True,
|
| 289 |
+
},
|
| 290 |
+
)
|
| 291 |
+
assert response.is_successful(), response.json
|
| 292 |
+
stop = time.perf_counter()
|
| 293 |
+
print(
|
| 294 |
+
f"Getting suggestions from Vespa took: {stop - start} s, Vespa reported searchtime was "
|
| 295 |
+
f"{response.json.get('timing', {}).get('searchtime', -1)} s"
|
| 296 |
+
)
|
| 297 |
+
search_results = (
|
| 298 |
+
response.json["root"]["children"]
|
| 299 |
+
if "root" in response.json and "children" in response.json["root"]
|
| 300 |
+
else []
|
| 301 |
+
)
|
| 302 |
+
print(response.json)
|
| 303 |
+
|
| 304 |
+
questions = [
|
| 305 |
+
result["fields"]["questions"]
|
| 306 |
+
for result in search_results
|
| 307 |
+
if "questions" in result["fields"]
|
| 308 |
+
]
|
| 309 |
+
flat_questions = [item for sublist in questions for item in sublist]
|
| 310 |
+
return flat_questions
|
| 311 |
+
|
| 312 |
async def query_vespa_nearest_neighbor(
|
| 313 |
self,
|
| 314 |
query: str,
|
frontend/app.py
CHANGED
|
@@ -84,6 +84,28 @@ toggle_text_content = Script(
|
|
| 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"
|
|
@@ -93,13 +115,16 @@ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
|
| 93 |
|
| 94 |
return Form(
|
| 95 |
Div(
|
| 96 |
-
Lucide(
|
|
|
|
|
|
|
| 97 |
Input(
|
| 98 |
placeholder="Enter your search query...",
|
| 99 |
name="query",
|
| 100 |
value=query_value,
|
| 101 |
id="search-input",
|
| 102 |
-
cls="text-base pl-10 border-transparent ring-offset-transparent ring-0 focus-visible:ring-transparent",
|
|
|
|
| 103 |
style="font-size: 1rem",
|
| 104 |
autofocus=True,
|
| 105 |
),
|
|
@@ -140,6 +165,7 @@ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
|
| 140 |
cls="flex justify-between",
|
| 141 |
),
|
| 142 |
check_input_script,
|
|
|
|
| 143 |
action=f"/search?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
|
| 144 |
method="GET",
|
| 145 |
hx_get=f"/fetch_results?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
|
|
|
|
| 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 |
|
| 110 |
def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
|
| 111 |
grid_cls = "grid gap-2 items-center p-3 bg-muted/80 dark:bg-muted/40 w-full"
|
|
|
|
| 115 |
|
| 116 |
return Form(
|
| 117 |
Div(
|
| 118 |
+
Lucide(
|
| 119 |
+
icon="search", cls="absolute left-2 top-2 text-muted-foreground z-10"
|
| 120 |
+
),
|
| 121 |
Input(
|
| 122 |
placeholder="Enter your search query...",
|
| 123 |
name="query",
|
| 124 |
value=query_value,
|
| 125 |
id="search-input",
|
| 126 |
+
cls="text-base pl-10 border-transparent ring-offset-transparent ring-0 focus-visible:ring-transparent awesomplete",
|
| 127 |
+
data_list="#suggestions",
|
| 128 |
style="font-size: 1rem",
|
| 129 |
autofocus=True,
|
| 130 |
),
|
|
|
|
| 165 |
cls="flex justify-between",
|
| 166 |
),
|
| 167 |
check_input_script,
|
| 168 |
+
autocomplete_script,
|
| 169 |
action=f"/search?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
|
| 170 |
method="GET",
|
| 171 |
hx_get=f"/fetch_results?query={quote_plus(query_value)}&ranking={quote_plus(ranking_value)}",
|
globals.css
CHANGED
|
@@ -217,3 +217,59 @@ aside {
|
|
| 217 |
.md-grid-text-column {
|
| 218 |
@apply md:grid md:grid-rows-subgrid md:row-span-2 md:content-start;
|
| 219 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
.md-grid-text-column {
|
| 218 |
@apply md:grid md:grid-rows-subgrid md:row-span-2 md:content-start;
|
| 219 |
}
|
| 220 |
+
|
| 221 |
+
#search-input[aria-expanded="true"] {
|
| 222 |
+
border-top: 1px solid hsl(var(--input));
|
| 223 |
+
border-left: 1px solid hsl(var(--input));
|
| 224 |
+
border-right: 1px solid hsl(var(--input));
|
| 225 |
+
border-bottom: none;
|
| 226 |
+
border-bottom-left-radius: 0;
|
| 227 |
+
border-bottom-right-radius: 0;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.awesomplete {
|
| 231 |
+
width: 100%;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.awesomplete > ul {
|
| 235 |
+
@apply text-sm space-y-0.5;
|
| 236 |
+
margin: 0;
|
| 237 |
+
border-top: none;
|
| 238 |
+
border-left: 1px solid hsl(var(--input));
|
| 239 |
+
border-right: 1px solid hsl(var(--input));
|
| 240 |
+
border-bottom: 1px solid hsl(var(--input));
|
| 241 |
+
border-radius: 0 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px);
|
| 242 |
+
background: hsl(var(--background));
|
| 243 |
+
box-shadow: none;
|
| 244 |
+
text-shadow: none;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.awesomplete > ul:before {
|
| 248 |
+
display: none;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.awesomplete > ul > li:hover {
|
| 252 |
+
background-color: #B7E2F1;
|
| 253 |
+
color: #2E2F27;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
.awesomplete > ul > li[aria-selected="true"] {
|
| 257 |
+
background-color: #B7E2F1;
|
| 258 |
+
color: #2E2F27;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.awesomplete mark {
|
| 262 |
+
background-color: #61D790;
|
| 263 |
+
color: #2E2F27;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
.awesomplete li:hover mark {
|
| 267 |
+
background-color: #61D790;
|
| 268 |
+
color: #2E2F27;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.awesomplete li[aria-selected="true"] mark {
|
| 272 |
+
background-color: #61D790;
|
| 273 |
+
color: #2E2F27;
|
| 274 |
+
}
|
| 275 |
+
|
main.py
CHANGED
|
@@ -49,19 +49,29 @@ overlayscrollbars_link = Link(
|
|
| 49 |
overlayscrollbars_js = Script(
|
| 50 |
src="https://cdnjs.cloudflare.com/ajax/libs/overlayscrollbars/2.10.0/browser/overlayscrollbars.browser.es5.min.js"
|
| 51 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
sselink = Script(src="https://unpkg.com/[email protected]/sse.js")
|
| 53 |
|
| 54 |
app, rt = fast_app(
|
| 55 |
htmlkw={"cls": "grid h-full"},
|
| 56 |
pico=False,
|
| 57 |
hdrs=(
|
| 58 |
-
ShadHead(tw_cdn=False, theme_handle=True),
|
| 59 |
highlight_js,
|
| 60 |
highlight_js_theme_link,
|
| 61 |
highlight_js_theme,
|
| 62 |
overlayscrollbars_link,
|
| 63 |
overlayscrollbars_js,
|
|
|
|
|
|
|
| 64 |
sselink,
|
|
|
|
| 65 |
),
|
| 66 |
)
|
| 67 |
vespa_app: Vespa = VespaQueryClient()
|
|
@@ -85,7 +95,7 @@ gemini_model = genai.GenerativeModel(
|
|
| 85 |
)
|
| 86 |
STATIC_DIR = Path(__file__).parent / "static"
|
| 87 |
IMG_DIR = STATIC_DIR / "saved"
|
| 88 |
-
os.makedirs(
|
| 89 |
|
| 90 |
|
| 91 |
@app.on_event("startup")
|
|
@@ -310,6 +320,18 @@ async def full_image(docid: str, query_id: str, idx: int):
|
|
| 310 |
)
|
| 311 |
|
| 312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
async def message_generator(query_id: str, query: str):
|
| 314 |
images = []
|
| 315 |
result = None
|
|
|
|
| 49 |
overlayscrollbars_js = Script(
|
| 50 |
src="https://cdnjs.cloudflare.com/ajax/libs/overlayscrollbars/2.10.0/browser/overlayscrollbars.browser.es5.min.js"
|
| 51 |
)
|
| 52 |
+
awesomplete_link = Link(
|
| 53 |
+
rel="stylesheet",
|
| 54 |
+
href="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.7/awesomplete.min.css",
|
| 55 |
+
type="text/css",
|
| 56 |
+
)
|
| 57 |
+
awesomplete_js = Script(
|
| 58 |
+
src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.7/awesomplete.min.js"
|
| 59 |
+
)
|
| 60 |
sselink = Script(src="https://unpkg.com/[email protected]/sse.js")
|
| 61 |
|
| 62 |
app, rt = fast_app(
|
| 63 |
htmlkw={"cls": "grid h-full"},
|
| 64 |
pico=False,
|
| 65 |
hdrs=(
|
|
|
|
| 66 |
highlight_js,
|
| 67 |
highlight_js_theme_link,
|
| 68 |
highlight_js_theme,
|
| 69 |
overlayscrollbars_link,
|
| 70 |
overlayscrollbars_js,
|
| 71 |
+
awesomplete_link,
|
| 72 |
+
awesomplete_js,
|
| 73 |
sselink,
|
| 74 |
+
ShadHead(tw_cdn=False, theme_handle=True),
|
| 75 |
),
|
| 76 |
)
|
| 77 |
vespa_app: Vespa = VespaQueryClient()
|
|
|
|
| 95 |
)
|
| 96 |
STATIC_DIR = Path(__file__).parent / "static"
|
| 97 |
IMG_DIR = STATIC_DIR / "saved"
|
| 98 |
+
os.makedirs(IMG_DIR, exist_ok=True)
|
| 99 |
|
| 100 |
|
| 101 |
@app.on_event("startup")
|
|
|
|
| 320 |
)
|
| 321 |
|
| 322 |
|
| 323 |
+
@rt("/suggestions")
|
| 324 |
+
async def get_suggestions(request):
|
| 325 |
+
query = request.query_params.get("query", "").lower().strip()
|
| 326 |
+
|
| 327 |
+
if query:
|
| 328 |
+
suggestions = await vespa_app.get_suggestions(query)
|
| 329 |
+
if len(suggestions) > 0:
|
| 330 |
+
return JSONResponse({"suggestions": suggestions})
|
| 331 |
+
|
| 332 |
+
return JSONResponse({"suggestions": []})
|
| 333 |
+
|
| 334 |
+
|
| 335 |
async def message_generator(query_id: str, query: str):
|
| 336 |
images = []
|
| 337 |
result = None
|
output.css
CHANGED
|
@@ -758,6 +758,10 @@ body {
|
|
| 758 |
top: 50%;
|
| 759 |
}
|
| 760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
.z-50 {
|
| 762 |
z-index: 50;
|
| 763 |
}
|
|
@@ -2077,6 +2081,68 @@ aside {
|
|
| 2077 |
}
|
| 2078 |
}
|
| 2079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2080 |
:root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
|
| 2081 |
overflow: hidden;
|
| 2082 |
}
|
|
@@ -2695,3 +2761,4 @@ aside {
|
|
| 2695 |
.\[\&_tr\]\:border-b tr {
|
| 2696 |
border-bottom-width: 1px;
|
| 2697 |
}
|
|
|
|
|
|
| 758 |
top: 50%;
|
| 759 |
}
|
| 760 |
|
| 761 |
+
.z-10 {
|
| 762 |
+
z-index: 10;
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
.z-50 {
|
| 766 |
z-index: 50;
|
| 767 |
}
|
|
|
|
| 2081 |
}
|
| 2082 |
}
|
| 2083 |
|
| 2084 |
+
#search-input[aria-expanded="true"] {
|
| 2085 |
+
border-top: 1px solid hsl(var(--input));
|
| 2086 |
+
border-left: 1px solid hsl(var(--input));
|
| 2087 |
+
border-right: 1px solid hsl(var(--input));
|
| 2088 |
+
border-bottom: none;
|
| 2089 |
+
border-bottom-left-radius: 0;
|
| 2090 |
+
border-bottom-right-radius: 0;
|
| 2091 |
+
}
|
| 2092 |
+
|
| 2093 |
+
.awesomplete {
|
| 2094 |
+
width: 100%;
|
| 2095 |
+
}
|
| 2096 |
+
|
| 2097 |
+
.awesomplete > ul > :not([hidden]) ~ :not([hidden]) {
|
| 2098 |
+
--tw-space-y-reverse: 0;
|
| 2099 |
+
margin-top: calc(0.125rem * calc(1 - var(--tw-space-y-reverse)));
|
| 2100 |
+
margin-bottom: calc(0.125rem * var(--tw-space-y-reverse));
|
| 2101 |
+
}
|
| 2102 |
+
|
| 2103 |
+
.awesomplete > ul {
|
| 2104 |
+
font-size: 0.875rem;
|
| 2105 |
+
line-height: 1.25rem;
|
| 2106 |
+
margin: 0;
|
| 2107 |
+
border-top: none;
|
| 2108 |
+
border-left: 1px solid hsl(var(--input));
|
| 2109 |
+
border-right: 1px solid hsl(var(--input));
|
| 2110 |
+
border-bottom: 1px solid hsl(var(--input));
|
| 2111 |
+
border-radius: 0 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px);
|
| 2112 |
+
background: hsl(var(--background));
|
| 2113 |
+
box-shadow: none;
|
| 2114 |
+
text-shadow: none;
|
| 2115 |
+
}
|
| 2116 |
+
|
| 2117 |
+
.awesomplete > ul:before {
|
| 2118 |
+
display: none;
|
| 2119 |
+
}
|
| 2120 |
+
|
| 2121 |
+
.awesomplete > ul > li:hover {
|
| 2122 |
+
background-color: #B7E2F1;
|
| 2123 |
+
color: #2E2F27;
|
| 2124 |
+
}
|
| 2125 |
+
|
| 2126 |
+
.awesomplete > ul > li[aria-selected="true"] {
|
| 2127 |
+
background-color: #B7E2F1;
|
| 2128 |
+
color: #2E2F27;
|
| 2129 |
+
}
|
| 2130 |
+
|
| 2131 |
+
.awesomplete mark {
|
| 2132 |
+
background-color: #61D790;
|
| 2133 |
+
color: #2E2F27;
|
| 2134 |
+
}
|
| 2135 |
+
|
| 2136 |
+
.awesomplete li:hover mark {
|
| 2137 |
+
background-color: #61D790;
|
| 2138 |
+
color: #2E2F27;
|
| 2139 |
+
}
|
| 2140 |
+
|
| 2141 |
+
.awesomplete li[aria-selected="true"] mark {
|
| 2142 |
+
background-color: #61D790;
|
| 2143 |
+
color: #2E2F27;
|
| 2144 |
+
}
|
| 2145 |
+
|
| 2146 |
:root:has(.data-\[state\=open\]\:no-bg-scroll[data-state="open"]) {
|
| 2147 |
overflow: hidden;
|
| 2148 |
}
|
|
|
|
| 2761 |
.\[\&_tr\]\:border-b tr {
|
| 2762 |
border-bottom-width: 1px;
|
| 2763 |
}
|
| 2764 |
+
|