thomasht86 commited on
Commit
b08a991
·
verified ·
1 Parent(s): b62467a

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. backend/vespa_app.py +34 -1
  2. frontend/app.py +28 -2
  3. globals.css +56 -0
  4. main.py +24 -2
  5. output.css +67 -0
backend/vespa_app.py CHANGED
@@ -1,6 +1,6 @@
1
  import os
2
  import time
3
- from typing import Dict, Any, Tuple
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(icon="search", cls="absolute left-2 top-2 text-muted-foreground"),
 
 
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/htmx-ext-sse@2.2.1/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(STATIC_DIR, exist_ok=True)
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/htmx-ext-sse@2.2.1/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
+