KingNish commited on
Commit
4183433
·
verified ·
1 Parent(s): 5f3d908

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +334 -251
templates/index.html CHANGED
@@ -1,279 +1,362 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
- <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>HelpingAI Search</title>
 
 
 
 
 
7
  <style>
8
- body, html { margin: 0; padding: 0; font-family: 'Arial', sans-serif; height: 100%; background: #f2f2f2; }
9
- .container { display: flex; flex-direction: column; min-height: 100%; }
10
- header { padding: 30px; display: flex; justify-content: space-between; align-items: center; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
11
- .main-content { flex-grow: 1; display: flex; flex-direction: column; align-items: center; padding: 40px 20px; transition: margin-left 0.3s; }
12
- .logo { font-size: 40px; font-weight: bold; margin-bottom: 20px; color: #4285f4; position: absolute; left: 43vw; top: 6px; }
13
- .logo span:nth-child(2) { color: #ea4335; }
14
- .logo span:nth-child(3) { color: #fbbc05; }
15
- .logo span:nth-child(4) { color: #34a853; }
16
- .logo span:nth-child(5) { color: #ea4335; }
17
- .search-container { width: 100%; max-width: 700px; margin-bottom: 20px; position: relative; }
18
- .search-box { display: flex; border: 1px solid #dfe1e5; border-radius: 24px; padding: 5px 8px; transition: box-shadow 0.3s; background: #fff; }
19
- .search-box:hover, .search-box:focus-within { box-shadow: 0 1px 6px rgba(32, 33, 36, 0.28); border-color: rgba(223, 225, 229, 0); }
20
- #search-query { flex-grow: 1; border: none; outline: none; font-size: 16px; padding: 10px 15px; }
21
- button { background: none; border: none; cursor: pointer; padding: 0 15px; }
22
- #search-form button svg { width: 20px; height: 20px; }
23
- #suggestions { width: 100%; max-width: 700px; border: 1px solid #dfe1e5; border-top: none; border-radius: 0 0 24px 24px; box-shadow: 0 4px 6px rgba(32, 33, 36, 0.28); display: none; position: absolute; background: white; z-index: 1000; }
24
- #suggestions ul { list-style-type: none; padding: 0; margin: 0; }
25
- #suggestions li { padding: 10px 15px; cursor: pointer; border-bottom: 1px solid #eee; }
26
- #suggestions li:hover { background-color: #f1f3f4; }
27
- #results { width: 100%; max-width: 700px; padding: 20px 0; }
28
- .result { margin-bottom: 20px; padding: 15px; border-radius: 8px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
29
- .result h3 { margin: 0; font-weight: normal; }
30
- .result h3 a { color: #1a0dab; text-decoration: none; font-size: 18px; }
31
- .result h3 a:hover { text-decoration: underline; }
32
- .result .url { color: #006621; font-size: 14px; margin-bottom: 3px; }
33
- .result p { color: #545454; font-size: 14px; line-height: 1.58; margin: 0; }
34
- .settings-icon { background: none; border: none; cursor: pointer; padding: 0; position: absolute; right: 5vw; }
35
- .settings-icon svg { width: 24px; height: 24px; fill: #333; }
36
- .settings-menu { display: none; position: fixed; top: 60px; right: 20px; background: #fff; border: 1px solid #dfe1e5; border-radius: 4px; box-shadow: 0 4px 6px rgba(32, 33, 36, 0.28); padding: 15px; z-index: 1001; width: 300px; }
37
- .settings-menu select { display: block; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; border: 1.6px solid #2b14fa; appearance: none; background: #ffffff url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M4 7l6 6 6-6z'></path></svg>") no-repeat right 10px center; font-size: 14px; }
38
- .settings-menu select:focus { outline: none; box-shadow: 0 0 0 5px #4285f4; }
39
- .settings-menu h4 { margin-top: 0; font-weight: bold; color: #333; }
40
-
41
- /* Sidebar Styling */
42
- .sidebar { width: 300px; padding: 20px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); position: fixed; top: 60px; right: 20px; height: calc(100vh - 60px); overflow-y: auto; display: none; }
43
- .sidebar h2, .sidebar h3 { color: #333; margin-bottom: 10px; }
44
- .sidebar p { color: #545454; font-size: 14px; line-height: 1.58; margin: 0; }
45
- .sidebar ul { list-style: none; padding: 0; margin: 0; }
46
- .sidebar li { padding: 5px 0; cursor: pointer; color: #545454; font-size: 14px; }
47
- .sidebar li a { text-decoration: none; color: #1a0dab; }
48
- .sidebar li a:hover { text-decoration: underline; }
49
-
50
- /* Responsive Adjustments - You can add more as needed */
51
- @media (max-width: 768px) {
52
- .main-content { margin-right: 0; /* Hide sidebar on smaller screens */ }
53
- .sidebar { display: none !important; /* Ensure sidebar is hidden */ }
54
- }
55
  </style>
56
- </head>
57
- <body>
58
- <div class="container">
59
- <header>
60
- <div class="logo">
61
- <span>H</span><span>e</span><span>l</span><span>p</span><span>i</span
62
- ><span>ng</span><span>AI</span>
63
- </div>
64
- <button class="settings-icon" onclick="toggleSettings()">
65
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings">
66
- <circle cx="12" cy="12" r="3"></circle>
67
- <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
68
- </svg>
69
- </button>
70
- <div class="settings-menu" id="settings-menu">
71
- <h4>Time Limit</h4>
72
- <select id="timelimit">
73
- <option value="none">All Time</option>
74
- <option value="d">Day</option>
75
- <option value="w">Week</option>
76
- <option value="m">Month</option>
77
- <option value="y">Year</option>
78
- </select>
79
- <h4>Safe Search</h4>
80
- <select id="safesearch">
81
- <option value="off">Off</option>
82
- <option value="moderate">Moderate</option>
83
- <option value="on">On</option>
84
- </select>
85
- </div>
86
- </header>
87
-
88
- <div class="sidebar" id="sidebar">
89
- <div id="about-section" style="display: none">
90
- <h2>About</h2>
91
- <p id="about-description"></p>
92
- </div>
93
- <div id="people-also-search-section" style="display: none">
94
- <h3>People Also Search For</h3>
95
- <ul id="people-also-search-list"></ul>
96
- </div>
97
- </div>
98
-
99
- <div class="main-content">
100
- <div class="search-container">
101
- <form id="search-form">
102
- <div class="search-box">
103
- <input type="text" id="search-query" placeholder="Search the web" autocomplete="off" />
104
- <button type="submit">
105
- <svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px">
106
- <path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
107
- </svg>
108
- </button>
109
- </div>
110
- </form>
111
- <div id="suggestions"></div>
112
- </div>
113
- <div id="results"></div>
114
- </div>
115
  </div>
116
-
117
  <script>
118
- const BASE_URL = "https://oevortex-webscout-api.hf.space";
119
- const searchForm = document.getElementById("search-form");
120
- const searchQueryInput = document.getElementById("search-query");
121
- const timelimitSelect = document.getElementById("timelimit");
122
- const safesearchSelect = document.getElementById("safesearch");
123
- const resultsContainer = document.getElementById("results");
124
- const suggestionsContainer = document.getElementById("suggestions");
125
- const peopleAlsoSearchList = document.getElementById("people-also-search-list");
126
- const peopleAlsoSearchSection = document.getElementById("people-also-search-section");
127
- const aboutSection = document.getElementById("about-section");
128
- const sidebar = document.getElementById("sidebar");
 
 
 
 
 
 
 
129
 
130
- // Function to fetch search suggestions
131
- async function fetchSuggestions(query) {
132
- const response = await fetch(`${BASE_URL}/api/suggestions?q=${encodeURIComponent(query)}`);
133
- return response.ok ? response.json() : [];
 
 
 
 
 
 
 
134
  }
135
-
136
- // Function to display search suggestions
137
- function displaySuggestions(suggestions) {
138
- suggestionsContainer.innerHTML = "";
139
- if (suggestions.length === 0) {
140
- suggestionsContainer.style.display = "none";
141
- return;
142
- }
143
- const suggestionList = document.createElement("ul");
144
- suggestions.forEach((suggestion) => {
145
- const listItem = document.createElement("li");
146
- listItem.textContent = suggestion.phrase;
147
- listItem.addEventListener("click", () => {
148
- searchQueryInput.value = suggestion.phrase;
149
- suggestionsContainer.style.display = "none";
150
- performSearch(suggestion.phrase);
151
- });
152
- suggestionList.appendChild(listItem);
153
- });
154
- suggestionsContainer.appendChild(suggestionList);
155
- suggestionsContainer.style.display = "block";
156
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- // Function to handle search form submission
159
- async function performSearch(query, timelimit, safesearch) {
160
- const timelimitValue = timelimitSelect.value;
161
- const safesearchValue = safesearchSelect.value;
162
- const response = await fetch(`${BASE_URL}/api/search?q=${encodeURIComponent(query)}&max_results=100&timelimit=${timelimitValue}&safesearch=${safesearchValue}`);
163
- const searchResults = await response.json();
164
- displayResults(searchResults);
 
 
 
 
 
165
  suggestionsContainer.style.display = "none";
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- // Fetch and display "People Also Search For" and "About"
168
- const peopleAlsoSearchData = await getPeopleAlsoSearch(query);
169
- displayPeopleAlsoSearch(peopleAlsoSearchData.filter(item => item.topic === "See also").slice(0, 6));
170
- const aboutDescription = peopleAlsoSearchData.find(item => item.topic === null);
171
- if (aboutDescription) {
172
- document.getElementById("about-description").textContent = aboutDescription.text;
173
- aboutSection.style.display = "block";
174
- } else {
175
- aboutSection.style.display = "none";
176
- }
177
 
178
- // Show/hide sidebar based on content
179
- sidebar.style.display = aboutSection.style.display === "block" || peopleAlsoSearchList.children.length > 0 ? "block" : "none";
180
- document.querySelector(".main-content").style.marginLeft = sidebar.style.display === "block" ? "320px" : "0px";
181
- }
182
 
183
- // Function to fetch "People Also Search For" data
184
- async function getPeopleAlsoSearch(query) {
185
- const response = await fetch(`${BASE_URL}/api/answers?q=${encodeURIComponent(query)}`);
186
- return response.ok ? response.json() : [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
 
188
 
189
- // Function to display "People Also Search For" results
190
- function displayPeopleAlsoSearch(data) {
191
- peopleAlsoSearchList.innerHTML = "";
192
- data.forEach((item) => {
193
- const listItem = document.createElement("li");
194
- const link = document.createElement("a");
195
- link.href = item.url;
196
- link.textContent = item.text;
197
- link.target = "_blank";
198
- link.rel = "noopener noreferrer";
199
- listItem.appendChild(link);
200
- peopleAlsoSearchList.appendChild(listItem);
201
- });
202
- peopleAlsoSearchSection.style.display = data.length > 0 ? "block" : "none";
203
- }
204
 
205
- // Function to display search results
206
- function displayResults(results) {
207
- resultsContainer.innerHTML = "";
208
- if (results.length === 0) {
209
- displayError("No results found.");
210
- return;
211
- }
212
- results.forEach((result) => {
213
- const resultElement = document.createElement("div");
214
- resultElement.classList.add("result");
215
- const titleElement = document.createElement("h3");
216
- const titleLink = document.createElement("a");
217
- titleLink.href = result.href;
218
- titleLink.textContent = result.title;
219
- titleLink.target = "_blank";
220
- titleLink.rel = "noopener noreferrer";
221
- titleElement.appendChild(titleLink);
222
- const urlElement = document.createElement("div");
223
- urlElement.classList.add("url");
224
- urlElement.textContent = result.href;
225
- const descriptionElement = document.createElement("p");
226
- descriptionElement.textContent = result.body;
227
- resultElement.appendChild(titleElement);
228
- resultElement.appendChild(urlElement);
229
- resultElement.appendChild(descriptionElement);
230
- resultsContainer.appendChild(resultElement);
231
- });
232
  }
233
-
234
- // Function to display an error message
235
- function displayError(message) {
236
- resultsContainer.innerHTML = "";
237
- const errorElement = document.createElement("p");
238
- errorElement.textContent = message;
239
- errorElement.style.color = "red";
240
- resultsContainer.appendChild(errorElement);
241
  }
 
 
 
242
 
243
- // Function to toggle the settings menu
244
- function toggleSettings() {
245
- const settingsMenu = document.getElementById("settings-menu");
246
- settingsMenu.style.display = settingsMenu.style.display === "block" ? "none" : "block";
247
  }
248
-
249
- // Event listeners for search input and form submission
250
- searchQueryInput.addEventListener("input", async () => {
251
- const searchQuery = searchQueryInput.value;
252
- if (searchQuery.trim() === "") {
253
- suggestionsContainer.style.display = "none";
254
- return;
255
- }
256
- const suggestions = await fetchSuggestions(searchQuery);
257
- displaySuggestions(suggestions);
258
- });
259
-
260
- searchQueryInput.addEventListener("focus", () => {
261
- if (searchQueryInput.value.trim() !== "") {
262
- suggestionsContainer.style.display = "block";
263
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  });
 
 
 
 
 
 
 
265
 
266
- document.addEventListener("click", (event) => {
267
- if (!searchForm.contains(event.target)) {
268
- suggestionsContainer.style.display = "none";
269
- }
270
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- searchForm.addEventListener("submit", async (event) => {
273
- event.preventDefault();
274
- const searchQuery = searchQueryInput.value;
275
- performSearch(searchQuery);
276
- });
 
 
 
 
 
 
277
  </script>
278
- </body>
279
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+ <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <title>HelpingAI Search</title>
7
+ <link rel="icon" href="https://www.gstatic.com/favicon/favicon.ico" type="image/x-icon">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
12
  <style>
13
+ *{box-sizing:border-box}
14
+ body{margin:0;padding:0;font-family:"Poppins",sans-serif;background-color:#f8f9fa}
15
+ a{text-decoration:none;color:#1a0dab}
16
+ a:hover{text-decoration:underline}
17
+ .main-content{display:flex;flex-direction:column;align-items:center;padding:50px 20px}
18
+ .search-container{width:100%;max-width:700px;position:relative;width:60%}
19
+ .search-box{width:100%;padding:12px 16px;border:2px solid #4285f4;border-radius:24px;box-shadow:0 2px 4px rgba(0,0,0,0.1);transition:box-shadow .2s ease-in-out,width .3s ease,border-color .3s ease;display:flex;align-items:center}
20
+ .search-box:focus-within{box-shadow:0 4px 8px rgba(32,33,36,0.35);border-color:#ea4335}
21
+ #search-query{width:calc(100% - 40px);border:none;outline:0;font-size:16px;padding:4px 0;transition:font-size .2s ease}
22
+ #search-query::placeholder{color:#9aa0a6;transition:color .2s ease}
23
+ #search-query:focus{font-size:18px}
24
+ #search-query:focus::placeholder{color:transparent}
25
+ #search-form button{background:0 0;border:none;cursor:pointer;padding:8px;margin-left:10px;transition:transform .2s ease}
26
+ #search-form button:hover{transform:scale(1.1)}
27
+ #search-form button svg{display:none}
28
+ #search-form button::after{content:"\f002";font-family:"Font Awesome 5 Free";font-weight:900;font-size:1.2em;color:#9aa0a6;transition:color .2s ease,transform .2s ease}
29
+ #search-form button:hover::after{color:#4285f4;transform:scale(1.1)}
30
+ #suggestions{width:calc(80% - 32px);background-color:#fff;border:none;border-radius:8px;box-shadow:0 4px 6px rgba(32,33,36,0.28);display:none;position:absolute;top:100%;left:0;z-index:10;opacity:0;transform:translateY(10px);transition:opacity .3s ease,transform .3s ease;padding:10px 0}
31
+ @keyframes spin {
32
+ 0%{transform:rotate(0)}
33
+ 100%{transform:rotate(360deg)}
34
+ }
35
+ #suggestions ul{list-style-type:none;padding:0;margin:0}
36
+ #suggestions li{padding:8px 12px;cursor:pointer;border-bottom:1px solid #eee;transition:background-color .2s ease}
37
+ #suggestions li:hover{background-color:#e9e9e9}
38
+ #suggestions li.selected{background-color:#f0f0f0}
39
+ .search-box:focus-within + #suggestions,.search-box:hover + #suggestions{display:block;opacity:1;transform:translateY(0)}
40
+ #results{width:100%;max-width:700px;margin-top:20px}
41
+ .result{margin-bottom:20px;padding:15px;border-radius:8px;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,0.1);opacity:0;transform:translateY(10px);transition:opacity .3s ease,transform .3s ease;animation:fadeInUp .5s ease forwards}
42
+ @keyframes fadeInUp {
43
+ from{opacity:0;transform:translateY(20px)}
44
+ to{opacity:1;transform:translateY(0)}
45
+ }
46
+ .result.show{opacity:1;transform:translateY(0)}
47
+ .result:hover{box-shadow:0 4px 8px rgba(0,0,0,0.2)}
48
+ .result h3{margin:0 0 5px;font-size:1.2rem;color:#222}
49
+ .result .url{color:#202124;font-size:.9rem;margin-bottom:8px;display:block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
50
+ .result p{color:#555;font-size:.9rem;line-height:1.6em;margin:0}
51
+ .loading-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0);z-index:1000}
52
+ .loading-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:80px;height:80px;border-radius:50%;border:5px solid #f3f3f3;border-top:5px solid #3498db;animation:spin 1.2s linear infinite}
53
+ #no-results{display:none;text-align:center;padding:20px;font-size:1.1em;color:#555}
54
+ #loading-more{display:none;text-align:center;padding:10px}
55
+ #loading-more.active{display:block}
 
 
 
 
56
  </style>
57
+ </head>
58
+ <body>
59
+ <div class="main-content">
60
+ <div class="search-container">
61
+ <form id="search-form">
62
+ <div class="search-box">
63
+ <input type="text" id="search-query" placeholder="Search the web" autocomplete="off">
64
+ <button type="submit"></button>
65
+ </div>
66
+ <div id="suggestions"></div>
67
+ </form>
68
+ </div>
69
+ <div id="results-info" style="text-align: center;"></div>
70
+ <div id="results"></div>
71
+ <div id="no-results">
72
+ <p>No results found. Try refining your search.</p>
73
+ </div>
74
+ <div id="loading-more">Loading more results...</div>
75
+ <div class="loading-overlay">
76
+ <div class="loading-spinner"></div>
77
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  </div>
 
79
  <script>
80
+ const BASE_URL = "https://oevortex-webscout-api.hf.space";
81
+ const searchForm = document.getElementById("search-form");
82
+ const searchQueryInput = document.getElementById("search-query");
83
+ const resultsContainer = document.getElementById("results");
84
+ const suggestionsContainer = document.getElementById("suggestions");
85
+ const loadingOverlay = document.querySelector('.loading-overlay');
86
+ const noResultsMessage = document.getElementById('no-results');
87
+ const loadingMoreIndicator = document.getElementById('loading-more');
88
+ const INITIAL_RESULTS = 5;
89
+ const CACHED_RESULTS = 50;
90
+ const RESULTS_PER_PAGE = 10;
91
+ let allResultsFetched = false;
92
+ const seenUrls = new Set();
93
+ let selectedSuggestionIndex = -1;
94
+ let suggestionRequestTimeout;
95
+ let cachedSearchResults = [];
96
+ const suggestionCache = {};
97
+ let prefetchTimeout;
98
 
99
+ function debounce(func, delay) {
100
+ return function() {
101
+ clearTimeout(suggestionRequestTimeout);
102
+ suggestionRequestTimeout = setTimeout(() => {
103
+ func.apply(this, arguments);
104
+ }, delay);
105
+ };
106
+ }
107
+ async function fetchSuggestions(query) {
108
+ if (suggestionCache[query]) {
109
+ return suggestionCache[query];
110
  }
111
+ try {
112
+ const response = await fetch(`${BASE_URL}/api/suggestions?q=${encodeURIComponent(query)}`);
113
+ if (response.ok) {
114
+ const suggestions = await response.json();
115
+ suggestionCache[query] = suggestions;
116
+ return suggestions;
117
+ } else {
118
+ console.error("Error fetching suggestions:", response.status);
119
+ return [];
120
+ }
121
+ } catch (error) {
122
+ console.error("Error fetching suggestions:", error);
123
+ return [];
 
 
 
 
 
 
 
 
124
  }
125
+ }
126
+ searchQueryInput.addEventListener("input", () => {
127
+ clearTimeout(prefetchTimeout);
128
+ const searchQuery = searchQueryInput.value.trim();
129
+ if (searchQuery === "") {
130
+ suggestionsContainer.style.display = "none";
131
+ return;
132
+ }
133
+ prefetchTimeout = setTimeout(async () => {
134
+ const suggestions = await fetchSuggestions(searchQuery);
135
+ displaySuggestions(suggestions);
136
+ }, 100);
137
+ });
138
 
139
+ function displaySuggestions(suggestions) {
140
+ suggestionsContainer.innerHTML = "";
141
+ if (suggestions.length === 0 || searchQueryInput.value.trim() === "") {
142
+ suggestionsContainer.style.display = "none";
143
+ return;
144
+ }
145
+ const suggestionList = document.createElement("ul");
146
+ suggestions.forEach((suggestion, index) => {
147
+ const listItem = document.createElement("li");
148
+ listItem.textContent = suggestion.phrase;
149
+ listItem.addEventListener("click", () => {
150
+ searchQueryInput.value = suggestion.phrase;
151
  suggestionsContainer.style.display = "none";
152
+ performSearch(suggestion.phrase);
153
+ });
154
+ listItem.addEventListener("focus", () => {
155
+ selectedSuggestionIndex = index;
156
+ updateSuggestionSelection();
157
+ });
158
+ suggestionList.appendChild(listItem);
159
+ });
160
+ suggestionsContainer.appendChild(suggestionList);
161
+ suggestionsContainer.style.display = "block";
162
+ }
163
 
164
+ function updateSuggestionSelection() {
165
+ const suggestionItems = suggestionsContainer.querySelectorAll("li");
166
+ suggestionItems.forEach((item, index) => {
167
+ item.classList.toggle("selected", index === selectedSuggestionIndex);
168
+ });
169
+ }
 
 
 
 
170
 
171
+ function showLoading() {
172
+ loadingOverlay.style.display = 'block';
173
+ }
 
174
 
175
+ function hideLoading() {
176
+ loadingOverlay.style.display = 'none';
177
+ }
178
+ let startTime;
179
+ async function performSearch(query) {
180
+ showLoading();
181
+ seenUrls.clear();
182
+ allResultsFetched = false;
183
+ resultsContainer.innerHTML = '';
184
+ noResultsMessage.style.display = 'none';
185
+ loadingMoreIndicator.classList.remove('active');
186
+ suggestionsContainer.style.display = 'none';
187
+ startTime = performance.now();
188
+ const initialResults = await fetchResults(query, INITIAL_RESULTS);
189
+ updateURLWithQuery(query);
190
+ displayResults(initialResults);
191
+ hideLoading();
192
+ if (initialResults.length > 0) {
193
+ cachedSearchResults = await fetchResults(query, CACHED_RESULTS);
194
+ cachedSearchResults = removeDuplicateResults(cachedSearchResults);
195
+ } else {
196
+ cachedSearchResults = [];
197
  }
198
+ }
199
 
200
+ function updateURLWithQuery(query) {
201
+ const newURL = `${window.location.pathname}?query=${encodeURIComponent(query)}`;
202
+ window.history.pushState({
203
+ path: newURL
204
+ }, '', newURL);
205
+ }
 
 
 
 
 
 
 
 
 
206
 
207
+ function removeDuplicateResults(results) {
208
+ const uniqueResults = [];
209
+ const seen = new Set();
210
+ for (const result of results) {
211
+ if (!seen.has(result.href)) {
212
+ seen.add(result.href);
213
+ uniqueResults.push(result);
214
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
+ return uniqueResults;
217
+ }
218
+ async function fetchResults(query, resultsPerPage) {
219
+ const response = await fetch(`${BASE_URL}/api/search?q=${encodeURIComponent(query)}&max_results=${resultsPerPage}`);
220
+ if (!response.ok) {
221
+ displayError("An error occurred while fetching results.");
222
+ hideLoading();
223
+ return [];
224
  }
225
+ const searchResults = await response.json();
226
+ return searchResults;
227
+ }
228
 
229
+ function displayResults(results, append = false) {
230
+ if (!append) {
231
+ resultsContainer.innerHTML = '';
 
232
  }
233
+ const newResults = results.filter(result => !seenUrls.has(result.href));
234
+ newResults.forEach((result, index) => {
235
+ seenUrls.add(result.href);
236
+ const resultElement = document.createElement("div");
237
+ resultElement.classList.add("result");
238
+ const titleElement = document.createElement("h3");
239
+ const titleLink = document.createElement("a");
240
+ titleLink.href = result.href;
241
+ titleLink.textContent = result.title;
242
+ titleLink.target = "_blank";
243
+ titleLink.rel = "noopener noreferrer";
244
+ titleElement.appendChild(titleLink);
245
+ const urlElement = document.createElement("div");
246
+ urlElement.classList.add("url");
247
+ const urlLink = document.createElement("a");
248
+ urlLink.href = result.href;
249
+ urlLink.textContent = result.href;
250
+ urlLink.target = "_blank";
251
+ urlLink.rel = "noopener noreferrer";
252
+ urlElement.appendChild(urlLink);
253
+ const descriptionElement = document.createElement("p");
254
+ descriptionElement.textContent = result.body;
255
+ resultElement.appendChild(titleElement);
256
+ resultElement.appendChild(urlElement);
257
+ resultElement.appendChild(descriptionElement);
258
+ resultsContainer.appendChild(resultElement);
259
+ setTimeout(() => {
260
+ resultElement.classList.add("show");
261
+ }, 100 * index);
262
  });
263
+ noResultsMessage.style.display = resultsContainer.children.length === 0 ? 'block' : 'none';
264
+ if (!append) {
265
+ const endTime = performance.now();
266
+ const timeTaken = (endTime - startTime).toFixed(2);
267
+ document.getElementById('results-info').textContent = `About ${timeTaken} milliseconds`;
268
+ }
269
+ }
270
 
271
+ function displayError(message) {
272
+ resultsContainer.innerHTML = "";
273
+ const errorElement = document.createElement("p");
274
+ errorElement.textContent = message;
275
+ errorElement.style.color = "red";
276
+ resultsContainer.appendChild(errorElement);
277
+ }
278
+ searchQueryInput.addEventListener("input", debounce(async () => {
279
+ selectedSuggestionIndex = -1;
280
+ const searchQuery = searchQueryInput.value;
281
+ if (searchQuery.trim() === "") {
282
+ suggestionsContainer.style.display = "none";
283
+ return;
284
+ }
285
+ const suggestions = await fetchSuggestions(searchQuery);
286
+ displaySuggestions(suggestions);
287
+ }, 500));
288
+ searchQueryInput.addEventListener("focus", () => {
289
+ if (searchQueryInput.value.trim() !== "") {
290
+ suggestionsContainer.style.display = "block";
291
+ }
292
+ });
293
+ document.addEventListener("click", (event) => {
294
+ if (!searchForm.contains(event.target)) {
295
+ suggestionsContainer.style.display = "none";
296
+ }
297
+ });
298
+ searchQueryInput.addEventListener("keydown", async (event) => {
299
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
300
+ event.preventDefault();
301
+ const suggestionItems = suggestionsContainer.querySelectorAll("li");
302
+ const numSuggestions = suggestionItems.length;
303
+ if (event.key === "ArrowUp") {
304
+ selectedSuggestionIndex = (selectedSuggestionIndex - 1 + numSuggestions) % numSuggestions;
305
+ } else {
306
+ selectedSuggestionIndex = (selectedSuggestionIndex + 1) % numSuggestions;
307
+ }
308
+ updateSuggestionSelection();
309
+ if (selectedSuggestionIndex !== -1 && suggestionItems[selectedSuggestionIndex]) {
310
+ searchQueryInput.value = suggestionItems[selectedSuggestionIndex].textContent;
311
+ suggestionItems[selectedSuggestionIndex].focus();
312
+ }
313
+ } else if (event.key === "Enter" && selectedSuggestionIndex !== -1) {
314
+ event.preventDefault();
315
+ const selectedSuggestion = suggestionsContainer.querySelectorAll("li")[selectedSuggestionIndex];
316
+ if (selectedSuggestion) {
317
+ searchQueryInput.value = selectedSuggestion.textContent;
318
+ suggestionsContainer.style.display = "none";
319
+ performSearch(searchQueryInput.value);
320
+ }
321
+ }
322
+ });
323
+ searchForm.addEventListener("submit", async (event) => {
324
+ event.preventDefault();
325
+ selectedSuggestionIndex = -1;
326
+ const searchQuery = searchQueryInput.value;
327
+ performSearch(searchQuery);
328
+ });
329
+ window.addEventListener("scroll", () => {
330
+ if (allResultsFetched || cachedSearchResults.length === 0) return;
331
+ const {
332
+ scrollTop,
333
+ scrollHeight,
334
+ clientHeight
335
+ } = document.documentElement;
336
+ if (scrollTop + clientHeight >= scrollHeight - 100 && !loadingMoreIndicator.classList.contains("active")) {
337
+ loadingMoreIndicator.classList.add("active");
338
+ const resultsToDisplay = cachedSearchResults.splice(0, RESULTS_PER_PAGE);
339
+ if (resultsToDisplay.length > 0) {
340
+ displayResults(resultsToDisplay, true);
341
+ }
342
+ if (cachedSearchResults.length === 0) {
343
+ allResultsFetched = true;
344
+ }
345
+ loadingMoreIndicator.classList.remove("active");
346
+ }
347
+ });
348
 
349
+ function getQueryParameter(name) {
350
+ const urlParams = new URLSearchParams(window.location.search);
351
+ return urlParams.get(name);
352
+ }
353
+ window.addEventListener('load', () => {
354
+ const initialQuery = getQueryParameter('query');
355
+ if (initialQuery) {
356
+ searchQueryInput.value = initialQuery;
357
+ performSearch(initialQuery);
358
+ }
359
+ });
360
  </script>
361
+ </body>
362
  </html>