search-api-webui 0.1.8__py3-none-any.whl → 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- search_api_webui/app.py +49 -27
- search_api_webui/providers/__init__.py +4 -1
- search_api_webui/providers/base.py +33 -0
- search_api_webui/providers/generic.py +62 -9
- search_api_webui/providers/querit.py +6 -3
- search_api_webui/providers.yaml +4 -4
- search_api_webui/static/assets/index-B2AadzJS.css +1 -0
- search_api_webui/static/assets/index-DxStlxhi.js +14 -0
- search_api_webui/static/index.html +2 -2
- {search_api_webui-0.1.8.dist-info → search_api_webui-0.1.9.dist-info}/METADATA +48 -8
- search_api_webui-0.1.9.dist-info/RECORD +18 -0
- search_api_webui/static/assets/index-7iEn12Q9.js +0 -196
- search_api_webui/static/assets/index-DKxNoLrm.css +0 -1
- search_api_webui-0.1.8.dist-info/RECORD +0 -18
- {search_api_webui-0.1.8.dist-info → search_api_webui-0.1.9.dist-info}/WHEEL +0 -0
- {search_api_webui-0.1.8.dist-info → search_api_webui-0.1.9.dist-info}/entry_points.txt +0 -0
- {search_api_webui-0.1.8.dist-info → search_api_webui-0.1.9.dist-info}/licenses/LICENSE +0 -0
search_api_webui/app.py
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
# DEALINGS IN THE SOFTWARE.
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
|
+
import logging
|
|
22
23
|
import socket
|
|
23
24
|
import sys
|
|
24
25
|
import threading
|
|
@@ -38,6 +39,14 @@ except ImportError:
|
|
|
38
39
|
WEBVIEW_AVAILABLE = False
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
# Configure logging
|
|
43
|
+
logging.basicConfig(
|
|
44
|
+
level=logging.INFO,
|
|
45
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
46
|
+
)
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
41
50
|
def get_resource_path(relative_path):
|
|
42
51
|
'''Get absolute path to resource, works for dev and for PyInstaller.'''
|
|
43
52
|
try:
|
|
@@ -77,7 +86,7 @@ if not USER_CONFIG_DIR.exists():
|
|
|
77
86
|
if PROVIDERS_YAML.exists():
|
|
78
87
|
provider_map = load_providers(str(PROVIDERS_YAML))
|
|
79
88
|
else:
|
|
80
|
-
|
|
89
|
+
logger.error(f'Configuration file not found at {PROVIDERS_YAML}')
|
|
81
90
|
provider_map = {}
|
|
82
91
|
|
|
83
92
|
|
|
@@ -88,7 +97,7 @@ def get_stored_config():
|
|
|
88
97
|
with open(USER_CONFIG_JSON, encoding='utf-8') as f:
|
|
89
98
|
return json.load(f)
|
|
90
99
|
except Exception as e:
|
|
91
|
-
|
|
100
|
+
logger.error(f'Error reading config: {e}')
|
|
92
101
|
return {}
|
|
93
102
|
|
|
94
103
|
|
|
@@ -97,7 +106,7 @@ def save_stored_config(config_dict):
|
|
|
97
106
|
with open(USER_CONFIG_JSON, 'w', encoding='utf-8') as f:
|
|
98
107
|
json.dump(config_dict, f, indent=2)
|
|
99
108
|
except Exception as e:
|
|
100
|
-
|
|
109
|
+
logger.error(f'Error saving config: {e}')
|
|
101
110
|
|
|
102
111
|
|
|
103
112
|
@app.route('/api/providers', methods=['GET'])
|
|
@@ -123,7 +132,7 @@ def get_providers_list():
|
|
|
123
132
|
'user_settings': {
|
|
124
133
|
'api_url': user_conf.get('api_url', ''),
|
|
125
134
|
'limit': user_conf.get('limit', '10'),
|
|
126
|
-
'language': user_conf.get('language'
|
|
135
|
+
'language': user_conf.get('language'),
|
|
127
136
|
},
|
|
128
137
|
},
|
|
129
138
|
)
|
|
@@ -138,31 +147,44 @@ def update_config():
|
|
|
138
147
|
if not provider_name:
|
|
139
148
|
return jsonify({'error': 'Provider name is required'}), 400
|
|
140
149
|
|
|
141
|
-
if 'api_key' not in data:
|
|
142
|
-
return jsonify({'error': 'API Key field is missing'}), 400
|
|
143
|
-
|
|
144
150
|
api_key = data.get('api_key')
|
|
145
151
|
|
|
146
152
|
api_url = data.get('api_url', '').strip()
|
|
147
153
|
limit = data.get('limit', '10')
|
|
148
|
-
language = data.get('language'
|
|
154
|
+
language = data.get('language')
|
|
149
155
|
|
|
150
156
|
all_config = get_stored_config()
|
|
151
157
|
|
|
152
158
|
if provider_name in all_config and isinstance(all_config[provider_name], str):
|
|
153
159
|
all_config[provider_name] = {'api_key': all_config[provider_name]}
|
|
154
160
|
|
|
155
|
-
if not
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
else:
|
|
159
|
-
if provider_name not in all_config:
|
|
160
|
-
all_config[provider_name] = {}
|
|
161
|
+
# Initialize provider config if not exists
|
|
162
|
+
if provider_name not in all_config:
|
|
163
|
+
all_config[provider_name] = {}
|
|
161
164
|
|
|
162
|
-
|
|
165
|
+
# Update advanced settings, skip empty values
|
|
166
|
+
if api_url:
|
|
163
167
|
all_config[provider_name]['api_url'] = api_url
|
|
168
|
+
elif 'api_url' in all_config[provider_name]:
|
|
169
|
+
del all_config[provider_name]['api_url']
|
|
170
|
+
|
|
171
|
+
if limit:
|
|
164
172
|
all_config[provider_name]['limit'] = limit
|
|
173
|
+
elif 'limit' in all_config[provider_name]:
|
|
174
|
+
del all_config[provider_name]['limit']
|
|
175
|
+
|
|
176
|
+
if language:
|
|
165
177
|
all_config[provider_name]['language'] = language
|
|
178
|
+
elif 'language' in all_config[provider_name]:
|
|
179
|
+
del all_config[provider_name]['language']
|
|
180
|
+
|
|
181
|
+
# Only update api_key if explicitly provided
|
|
182
|
+
if api_key is not None:
|
|
183
|
+
all_config[provider_name]['api_key'] = api_key
|
|
184
|
+
|
|
185
|
+
# Clean up empty provider config
|
|
186
|
+
if not all_config[provider_name]:
|
|
187
|
+
del all_config[provider_name]
|
|
166
188
|
|
|
167
189
|
save_stored_config(all_config)
|
|
168
190
|
return jsonify({'status': 'success'})
|
|
@@ -231,20 +253,20 @@ def main():
|
|
|
231
253
|
|
|
232
254
|
parser = argparse.ArgumentParser(description='Search API WebUI')
|
|
233
255
|
parser.add_argument('--port', type=int, default=8889, help='Port to run the server on')
|
|
234
|
-
parser.add_argument('--host', type=str, default='
|
|
256
|
+
parser.add_argument('--host', type=str, default='localhost', help='Host to run the server on')
|
|
235
257
|
parser.add_argument('-w', '--webview', action='store_true', help='Use webview to open the application')
|
|
236
258
|
args = parser.parse_args()
|
|
237
259
|
|
|
238
260
|
url = f'http://{args.host}:{args.port}'
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
261
|
+
logger.info('Starting Search API WebUI...')
|
|
262
|
+
logger.info(f' - Config Storage: {USER_CONFIG_JSON}')
|
|
263
|
+
logger.info(f' - Serving on: {url}')
|
|
242
264
|
if args.webview:
|
|
243
|
-
|
|
265
|
+
logger.info(' - Mode: webview')
|
|
244
266
|
|
|
245
267
|
if args.webview:
|
|
246
268
|
if not WEBVIEW_AVAILABLE:
|
|
247
|
-
|
|
269
|
+
logger.warning('webview library not installed. Falling back to webbrowser.')
|
|
248
270
|
# Start server in background thread and wait for it to be ready
|
|
249
271
|
server_thread = threading.Thread(
|
|
250
272
|
target=lambda: app.run(
|
|
@@ -254,10 +276,10 @@ def main():
|
|
|
254
276
|
)
|
|
255
277
|
server_thread.start()
|
|
256
278
|
if wait_for_server_ready(args.host, args.port):
|
|
257
|
-
|
|
279
|
+
logger.info(f'Server is ready! Opening browser: {url}')
|
|
258
280
|
webbrowser.open(url)
|
|
259
281
|
else:
|
|
260
|
-
|
|
282
|
+
logger.error('Server took too long to start. Browser not opened.')
|
|
261
283
|
else:
|
|
262
284
|
# Start server in background thread and wait for it to be ready, then start webview
|
|
263
285
|
server_thread = threading.Thread(
|
|
@@ -268,19 +290,19 @@ def main():
|
|
|
268
290
|
)
|
|
269
291
|
server_thread.start()
|
|
270
292
|
if wait_for_server_ready(args.host, args.port):
|
|
271
|
-
|
|
293
|
+
logger.info('Server is ready! Using webview mode...')
|
|
272
294
|
webview.create_window('Search API WebUI', url, width=1200, height=800)
|
|
273
295
|
webview.start()
|
|
274
296
|
else:
|
|
275
|
-
|
|
297
|
+
logger.error('Server took too long to start. Webview not opened.')
|
|
276
298
|
else:
|
|
277
299
|
# Start a background thread to check server status and open the browser automatically
|
|
278
300
|
def open_browser():
|
|
279
301
|
if wait_for_server_ready(args.host, args.port):
|
|
280
|
-
|
|
302
|
+
logger.info(f'Server is ready! Opening browser: {url}')
|
|
281
303
|
webbrowser.open(url)
|
|
282
304
|
else:
|
|
283
|
-
|
|
305
|
+
logger.error('Server took too long to start. Browser not opened.')
|
|
284
306
|
threading.Thread(target=open_browser, daemon=True).start()
|
|
285
307
|
app.run(host=args.host, port=args.port)
|
|
286
308
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
19
19
|
# DEALINGS IN THE SOFTWARE.
|
|
20
20
|
|
|
21
|
+
import logging
|
|
21
22
|
import os
|
|
22
23
|
|
|
23
24
|
import yaml
|
|
@@ -25,6 +26,8 @@ import yaml
|
|
|
25
26
|
from .generic import GenericProvider
|
|
26
27
|
from .querit import QueritSdkProvider
|
|
27
28
|
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
def load_providers(file_path='providers.yaml'):
|
|
30
33
|
'''
|
|
@@ -37,7 +40,7 @@ def load_providers(file_path='providers.yaml'):
|
|
|
37
40
|
dict: A dictionary mapping provider names to their initialized instances.
|
|
38
41
|
'''
|
|
39
42
|
if not os.path.exists(file_path):
|
|
40
|
-
|
|
43
|
+
logger.warning(f'Provider config file not found at {file_path}')
|
|
41
44
|
return {}
|
|
42
45
|
|
|
43
46
|
with open(file_path, encoding='utf-8') as f:
|
|
@@ -19,6 +19,39 @@
|
|
|
19
19
|
# DEALINGS IN THE SOFTWARE.
|
|
20
20
|
|
|
21
21
|
from abc import ABC, abstractmethod
|
|
22
|
+
from urllib.parse import urlparse
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def extract_domain_from_url(url):
|
|
26
|
+
'''
|
|
27
|
+
Extract domain name from URL, removing 'www.' prefix.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
url (str): The URL to extract domain from.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str | None: The domain name without 'www.' prefix
|
|
34
|
+
(e.g., 'example.com' from 'https://www.example.com/path'),
|
|
35
|
+
or None if the URL is invalid or has no netloc.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
>>> extract_domain_from_url('https://www.example.com/path')
|
|
39
|
+
'example.com'
|
|
40
|
+
>>> extract_domain_from_url('http://example.com')
|
|
41
|
+
'example.com'
|
|
42
|
+
>>> extract_domain_from_url('invalid-url')
|
|
43
|
+
None
|
|
44
|
+
'''
|
|
45
|
+
if not url or not isinstance(url, str):
|
|
46
|
+
return None
|
|
47
|
+
try:
|
|
48
|
+
parsed = urlparse(url)
|
|
49
|
+
domain = parsed.netloc if parsed.netloc else None
|
|
50
|
+
if domain and domain.startswith('www.'):
|
|
51
|
+
domain = domain[4:]
|
|
52
|
+
return domain
|
|
53
|
+
except Exception:
|
|
54
|
+
return None
|
|
22
55
|
|
|
23
56
|
|
|
24
57
|
def parse_server_latency(latency_value):
|
|
@@ -25,7 +25,7 @@ import time
|
|
|
25
25
|
import jmespath
|
|
26
26
|
import requests
|
|
27
27
|
|
|
28
|
-
from .base import BaseProvider, parse_server_latency
|
|
28
|
+
from .base import BaseProvider, extract_domain_from_url, parse_server_latency
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
31
31
|
|
|
@@ -59,17 +59,36 @@ class GenericProvider(BaseProvider):
|
|
|
59
59
|
|
|
60
60
|
Returns:
|
|
61
61
|
The structure with placeholders replaced by actual values.
|
|
62
|
+
Dict entries with empty values are removed.
|
|
63
|
+
Pure numeric placeholders (e.g., "{limit}") are converted to int/float.
|
|
62
64
|
'''
|
|
63
65
|
if isinstance(template_obj, str):
|
|
64
66
|
# Treat None values as empty strings to prevent "None" appearing in URLs
|
|
65
67
|
safe_kwargs = {k: (v if v is not None else '') for k, v in kwargs.items()}
|
|
66
68
|
try:
|
|
67
|
-
|
|
69
|
+
result = template_obj.format(**safe_kwargs)
|
|
70
|
+
# Convert to number if the result is a numeric string
|
|
71
|
+
# and the original template was a pure placeholder like "{limit}"
|
|
72
|
+
if template_obj.strip().startswith('{') and template_obj.strip().endswith('}'):
|
|
73
|
+
try:
|
|
74
|
+
# Try int first, then float
|
|
75
|
+
if '.' in result:
|
|
76
|
+
return float(result)
|
|
77
|
+
return int(result)
|
|
78
|
+
except (ValueError, AttributeError):
|
|
79
|
+
pass
|
|
80
|
+
return result
|
|
68
81
|
except KeyError:
|
|
69
82
|
# Return original string if a placeholder key is missing in kwargs
|
|
70
83
|
return template_obj
|
|
71
84
|
elif isinstance(template_obj, dict):
|
|
72
|
-
|
|
85
|
+
result = {}
|
|
86
|
+
for k, v in template_obj.items():
|
|
87
|
+
filled = self._fill_template(v, **kwargs)
|
|
88
|
+
# Skip entries with empty values (e.g., empty strings after template fill)
|
|
89
|
+
if filled != '':
|
|
90
|
+
result[k] = filled
|
|
91
|
+
return result
|
|
73
92
|
return template_obj
|
|
74
93
|
|
|
75
94
|
def _ensure_connection(self, url, headers):
|
|
@@ -111,11 +130,12 @@ class GenericProvider(BaseProvider):
|
|
|
111
130
|
'''
|
|
112
131
|
# 1. Extract parameters with defaults
|
|
113
132
|
limit = kwargs.get('limit', '10')
|
|
114
|
-
language = kwargs.get('language'
|
|
115
|
-
custom_url = kwargs.get('api_url'
|
|
133
|
+
language = kwargs.get('language')
|
|
134
|
+
custom_url = kwargs.get('api_url')
|
|
116
135
|
|
|
117
136
|
# 2. Determine configuration
|
|
118
|
-
|
|
137
|
+
# Use custom api_url if provided, otherwise fallback to config url
|
|
138
|
+
url = custom_url.strip() if custom_url else self.config.get('url')
|
|
119
139
|
method = self.config.get('method', 'GET')
|
|
120
140
|
|
|
121
141
|
# 3. Prepare context for template injection
|
|
@@ -123,8 +143,10 @@ class GenericProvider(BaseProvider):
|
|
|
123
143
|
'query': query,
|
|
124
144
|
'api_key': api_key,
|
|
125
145
|
'limit': limit,
|
|
126
|
-
'language': language,
|
|
127
146
|
}
|
|
147
|
+
# Only add language to context if provided
|
|
148
|
+
if language:
|
|
149
|
+
context['language'] = language
|
|
128
150
|
|
|
129
151
|
# 4. construct request components
|
|
130
152
|
headers = self._fill_template(self.config.get('headers', {}), **context)
|
|
@@ -163,7 +185,7 @@ class GenericProvider(BaseProvider):
|
|
|
163
185
|
|
|
164
186
|
response.raise_for_status()
|
|
165
187
|
except Exception as e:
|
|
166
|
-
logger.error('Request Error: %s', e)
|
|
188
|
+
logger.error('Request Error: %s', e, f"args: {req_args}")
|
|
167
189
|
return {
|
|
168
190
|
'error': str(e),
|
|
169
191
|
'results': [],
|
|
@@ -185,18 +207,49 @@ class GenericProvider(BaseProvider):
|
|
|
185
207
|
|
|
186
208
|
normalized_results = []
|
|
187
209
|
field_map = mapping.get('fields', {})
|
|
210
|
+
# Define common fields that should be extracted as-is
|
|
211
|
+
common_fields = {'title', 'url', 'site_name', 'site_icon', 'page_age'}
|
|
212
|
+
|
|
213
|
+
# Collect all JMESPath source paths that are already mapped
|
|
214
|
+
mapped_paths = set(field_map.values())
|
|
188
215
|
|
|
189
216
|
for item in root_list:
|
|
190
217
|
entry = {}
|
|
218
|
+
snippet_fields = {} # Collect unmapped fields from raw API response
|
|
219
|
+
|
|
191
220
|
# Map specific fields (title, url, etc.) based on config
|
|
192
221
|
for std_key, source_path in field_map.items():
|
|
193
222
|
val = jmespath.search(source_path, item)
|
|
194
223
|
# Decode HTML entities for site_name
|
|
195
224
|
if std_key == 'site_name' and val:
|
|
196
225
|
val = html.unescape(val)
|
|
197
|
-
|
|
226
|
+
|
|
227
|
+
# Only store common fields in entry
|
|
228
|
+
if std_key in common_fields:
|
|
229
|
+
entry[std_key] = val if val else ''
|
|
230
|
+
|
|
231
|
+
# Find all unmapped fields in the raw item
|
|
232
|
+
# These are fields that exist in the API response but are not in field_map
|
|
233
|
+
if isinstance(item, dict):
|
|
234
|
+
for key, value in item.items():
|
|
235
|
+
# Check if this key is already mapped in config
|
|
236
|
+
# Skip common nested objects and arrays for cleaner output
|
|
237
|
+
if key not in mapped_paths and value and not isinstance(value, (dict, list)):
|
|
238
|
+
snippet_fields[key] = value
|
|
239
|
+
|
|
240
|
+
# Store snippet fields as JSON structure in snippet
|
|
241
|
+
if snippet_fields:
|
|
242
|
+
entry['snippet'] = snippet_fields
|
|
243
|
+
else:
|
|
244
|
+
entry['snippet'] = ''
|
|
245
|
+
|
|
198
246
|
normalized_results.append(entry)
|
|
199
247
|
|
|
248
|
+
# Post-process: extract domain from URL if site_name is empty
|
|
249
|
+
for entry in normalized_results:
|
|
250
|
+
if not entry.get('site_name') and entry.get('url'):
|
|
251
|
+
entry['site_name'] = extract_domain_from_url(entry['url']) or ''
|
|
252
|
+
|
|
200
253
|
# Extract server latency from response if configured
|
|
201
254
|
server_latency_path = mapping.get('server_latency_path')
|
|
202
255
|
server_latency_ms = parse_server_latency(
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
# DEALINGS IN THE SOFTWARE.
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
|
+
import logging
|
|
22
23
|
import time
|
|
23
24
|
|
|
24
25
|
from querit import QueritClient
|
|
@@ -27,6 +28,8 @@ from querit.models.request import SearchRequest
|
|
|
27
28
|
|
|
28
29
|
from .base import BaseProvider, parse_server_latency
|
|
29
30
|
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
class QueritSdkProvider(BaseProvider):
|
|
32
35
|
'''
|
|
@@ -52,7 +55,7 @@ class QueritSdkProvider(BaseProvider):
|
|
|
52
55
|
count=limit,
|
|
53
56
|
)
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
logger.debug(f'[Querit SDK] Searching: {query} (Limit: {limit})')
|
|
56
59
|
|
|
57
60
|
start_time = time.time()
|
|
58
61
|
|
|
@@ -94,14 +97,14 @@ class QueritSdkProvider(BaseProvider):
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
except QueritError as e:
|
|
97
|
-
|
|
100
|
+
logger.error(f'Querit SDK Error: {e}')
|
|
98
101
|
return {
|
|
99
102
|
'error': f'Querit SDK Error: {str(e)}',
|
|
100
103
|
'results': [],
|
|
101
104
|
'metrics': {'latency_ms': 0, 'server_latency_ms': None, 'size_bytes': 0},
|
|
102
105
|
}
|
|
103
106
|
except Exception as e:
|
|
104
|
-
|
|
107
|
+
logger.exception(f'Unexpected Error: {e}')
|
|
105
108
|
return {
|
|
106
109
|
'error': f'Error: {str(e)}',
|
|
107
110
|
'results': [],
|
search_api_webui/providers.yaml
CHANGED
|
@@ -7,13 +7,13 @@ querit:
|
|
|
7
7
|
"Content-Type": "application/json"
|
|
8
8
|
payload:
|
|
9
9
|
query: "{query}"
|
|
10
|
+
count: "{limit}"
|
|
10
11
|
response_mapping:
|
|
11
12
|
root_path: "results.result"
|
|
12
13
|
server_latency_path: "took"
|
|
13
14
|
fields:
|
|
14
15
|
title: "title"
|
|
15
16
|
url: "url"
|
|
16
|
-
snippet: "snippet"
|
|
17
17
|
site_name: "site_name"
|
|
18
18
|
site_icon: "site_icon"
|
|
19
19
|
page_age: "page_age"
|
|
@@ -25,6 +25,7 @@ you:
|
|
|
25
25
|
X-API-Key: "{api_key}"
|
|
26
26
|
params:
|
|
27
27
|
query: "{query}"
|
|
28
|
+
count: "{limit}"
|
|
28
29
|
payload: {}
|
|
29
30
|
response_mapping:
|
|
30
31
|
root_path: "results.web"
|
|
@@ -32,7 +33,6 @@ you:
|
|
|
32
33
|
fields:
|
|
33
34
|
title: "title"
|
|
34
35
|
url: "url"
|
|
35
|
-
snippet: "snippets[0] || description"
|
|
36
36
|
site_icon: "favicon_url"
|
|
37
37
|
page_age: "page_age"
|
|
38
38
|
|
|
@@ -44,13 +44,13 @@ brave:
|
|
|
44
44
|
Accept: "application/json"
|
|
45
45
|
params:
|
|
46
46
|
q: "{query}"
|
|
47
|
+
count: "{limit}"
|
|
47
48
|
payload: {}
|
|
48
49
|
response_mapping:
|
|
49
50
|
root_path: "web.results"
|
|
50
51
|
fields:
|
|
51
52
|
title: "title"
|
|
52
53
|
url: "url"
|
|
53
|
-
snippet: "description"
|
|
54
54
|
page_age: "page_age"
|
|
55
55
|
|
|
56
56
|
exa:
|
|
@@ -62,12 +62,12 @@ exa:
|
|
|
62
62
|
params: {}
|
|
63
63
|
payload:
|
|
64
64
|
query: "{query}"
|
|
65
|
+
numResults: "{limit}"
|
|
65
66
|
response_mapping:
|
|
66
67
|
root_path: "results"
|
|
67
68
|
fields:
|
|
68
69
|
title: "title"
|
|
69
70
|
url: "url"
|
|
70
|
-
snippet: "text"
|
|
71
71
|
page_age: "publishedDate"
|
|
72
72
|
site_icon: "favicon"
|
|
73
73
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.right-3{right:.75rem}.top-0{top:0}.top-3\.5{top:.875rem}.z-10{z-index:10}.col-span-2{grid-column:span 2 / span 2}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-2{margin-left:.5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.min-h-\[500px\]{min-height:500px}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-0{border-width:0px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-t-4{border-top-width:4px}.border-dashed{border-style:dashed}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-purple-200{--tw-border-opacity: 1;border-color:rgb(233 213 255 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-100\/50{background-color:#f3f4f680}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-200{--tw-bg-opacity: 1;background-color:rgb(187 247 208 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-purple-600{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity, 1))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-blue-600{--tw-gradient-from: #2563eb var(--tw-gradient-from-position);--tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-gray-50{--tw-gradient-from: #f9fafb var(--tw-gradient-from-position);--tw-gradient-to: rgb(249 250 251 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-600{--tw-gradient-from: #9333ea var(--tw-gradient-from-position);--tw-gradient-to: rgb(147 51 234 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-blue-50{--tw-gradient-to: #eff6ff var(--tw-gradient-to-position)}.to-blue-600{--tw-gradient-to: #2563eb var(--tw-gradient-to-position)}.to-indigo-600{--tw-gradient-to: #4f46e5 var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pb-2{padding-bottom:.5rem}.pl-0{padding-left:0}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.tracking-widest{letter-spacing:.1em}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-gray-950{--tw-text-opacity: 1;color:rgb(3 7 18 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-purple-600{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-gray-200{--tw-ring-opacity: 1;--tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity, 1))}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-500{transition-duration:.5s}body{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-gray-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.placeholder\:text-gray-500::placeholder{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-green-300:hover{--tw-bg-opacity: 1;background-color:rgb(134 239 172 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-50:hover{--tw-bg-opacity: 1;background-color:rgb(250 245 255 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-700:hover{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:text-blue-800:hover{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.hover\:text-purple-700:hover{--tw-text-opacity: 1;color:rgb(126 34 206 / var(--tw-text-opacity, 1))}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity, 1))}.focus\:ring-purple-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(168 85 247 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-blue-600:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:underline{text-decoration-line:underline}.group:hover .group-hover\:opacity-50{opacity:.5}.data-\[side\=Left\]\:border-t-blue-500[data-side=Left]{--tw-border-opacity: 1;border-top-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.data-\[side\=Right\]\:border-t-orange-500[data-side=Right]{--tw-border-opacity: 1;border-top-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}@media(min-width:768px){.md\:w-64{width:16rem}.md\:w-auto{width:auto}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:flex-row{flex-direction:row}}
|