search-api-webui 0.1.10__py3-none-any.whl → 0.2.1__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 +41 -2
- search_api_webui/providers/generic.py +51 -16
- search_api_webui/providers.yaml +93 -12
- search_api_webui/static/AppIcon.ico +0 -0
- search_api_webui/static/assets/index-BWsitD55.css +1 -0
- search_api_webui/static/assets/index-CEkM4WD5.js +14 -0
- search_api_webui/static/index.html +2 -2
- {search_api_webui-0.1.10.dist-info → search_api_webui-0.2.1.dist-info}/METADATA +54 -19
- search_api_webui-0.2.1.dist-info/RECORD +19 -0
- search_api_webui/static/assets/index-B2AadzJS.css +0 -1
- search_api_webui/static/assets/index-DxStlxhi.js +0 -14
- search_api_webui-0.1.10.dist-info/RECORD +0 -18
- {search_api_webui-0.1.10.dist-info → search_api_webui-0.2.1.dist-info}/WHEEL +0 -0
- {search_api_webui-0.1.10.dist-info → search_api_webui-0.2.1.dist-info}/entry_points.txt +0 -0
- {search_api_webui-0.1.10.dist-info → search_api_webui-0.2.1.dist-info}/licenses/LICENSE +0 -0
search_api_webui/app.py
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
22
|
import logging
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
23
25
|
import socket
|
|
24
26
|
import sys
|
|
25
27
|
import threading
|
|
@@ -39,9 +41,21 @@ except ImportError:
|
|
|
39
41
|
WEBVIEW_AVAILABLE = False
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
#
|
|
44
|
+
# Auto-enable webview mode when running as packaged executable
|
|
45
|
+
# This replaces the need for a separate PyInstaller runtime hook
|
|
46
|
+
if (
|
|
47
|
+
getattr(sys, 'frozen', False)
|
|
48
|
+
and platform.system() in ('Windows', 'Darwin')
|
|
49
|
+
and '-w' not in sys.argv
|
|
50
|
+
and '--webview' not in sys.argv
|
|
51
|
+
):
|
|
52
|
+
sys.argv.append('-w')
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Configure logging based on Flask debug mode or environment variable
|
|
56
|
+
log_level = logging.DEBUG if os.getenv('FLASK_DEBUG') or os.getenv('FLASK_ENV') == 'development' else logging.INFO
|
|
43
57
|
logging.basicConfig(
|
|
44
|
-
level=
|
|
58
|
+
level=log_level,
|
|
45
59
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
46
60
|
)
|
|
47
61
|
logger = logging.getLogger(__name__)
|
|
@@ -133,6 +147,9 @@ def get_providers_list():
|
|
|
133
147
|
'api_url': user_conf.get('api_url', ''),
|
|
134
148
|
'limit': user_conf.get('limit', '10'),
|
|
135
149
|
'language': user_conf.get('language'),
|
|
150
|
+
'use_proxy': user_conf.get('use_proxy', False),
|
|
151
|
+
'proxy_url': user_conf.get('proxy_url', ''),
|
|
152
|
+
'skip_warmup': user_conf.get('skip_warmup', False),
|
|
136
153
|
},
|
|
137
154
|
},
|
|
138
155
|
)
|
|
@@ -152,6 +169,9 @@ def update_config():
|
|
|
152
169
|
api_url = data.get('api_url', '').strip()
|
|
153
170
|
limit = data.get('limit', '10')
|
|
154
171
|
language = data.get('language')
|
|
172
|
+
use_proxy = data.get('use_proxy', False)
|
|
173
|
+
proxy_url = data.get('proxy_url', '').strip()
|
|
174
|
+
skip_warmup = data.get('skip_warmup', False)
|
|
155
175
|
|
|
156
176
|
all_config = get_stored_config()
|
|
157
177
|
|
|
@@ -178,6 +198,23 @@ def update_config():
|
|
|
178
198
|
elif 'language' in all_config[provider_name]:
|
|
179
199
|
del all_config[provider_name]['language']
|
|
180
200
|
|
|
201
|
+
# Save proxy settings
|
|
202
|
+
if use_proxy:
|
|
203
|
+
all_config[provider_name]['use_proxy'] = True
|
|
204
|
+
if proxy_url:
|
|
205
|
+
all_config[provider_name]['proxy_url'] = proxy_url
|
|
206
|
+
else:
|
|
207
|
+
if 'use_proxy' in all_config[provider_name]:
|
|
208
|
+
del all_config[provider_name]['use_proxy']
|
|
209
|
+
if 'proxy_url' in all_config[provider_name]:
|
|
210
|
+
del all_config[provider_name]['proxy_url']
|
|
211
|
+
|
|
212
|
+
# Save warmup settings
|
|
213
|
+
if skip_warmup:
|
|
214
|
+
all_config[provider_name]['skip_warmup'] = True
|
|
215
|
+
elif 'skip_warmup' in all_config[provider_name]:
|
|
216
|
+
del all_config[provider_name]['skip_warmup']
|
|
217
|
+
|
|
181
218
|
# Only update api_key if explicitly provided
|
|
182
219
|
if api_key is not None:
|
|
183
220
|
all_config[provider_name]['api_key'] = api_key
|
|
@@ -221,6 +258,8 @@ def search_api():
|
|
|
221
258
|
'api_url': provider_config.get('api_url'),
|
|
222
259
|
'limit': provider_config.get('limit'),
|
|
223
260
|
'language': provider_config.get('language'),
|
|
261
|
+
'proxy_url': provider_config.get('proxy_url') if provider_config.get('use_proxy') else None,
|
|
262
|
+
'skip_warmup': provider_config.get('skip_warmup', False),
|
|
224
263
|
}
|
|
225
264
|
|
|
226
265
|
result = provider.search(query, api_key, **search_kwargs)
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import html
|
|
22
22
|
import logging
|
|
23
|
+
import os
|
|
23
24
|
import time
|
|
24
25
|
|
|
25
26
|
import jmespath
|
|
@@ -81,6 +82,9 @@ class GenericProvider(BaseProvider):
|
|
|
81
82
|
except KeyError:
|
|
82
83
|
# Return original string if a placeholder key is missing in kwargs
|
|
83
84
|
return template_obj
|
|
85
|
+
elif isinstance(template_obj, list):
|
|
86
|
+
# Handle list/array - recursively process each element
|
|
87
|
+
return [self._fill_template(item, **kwargs) for item in template_obj]
|
|
84
88
|
elif isinstance(template_obj, dict):
|
|
85
89
|
result = {}
|
|
86
90
|
for k, v in template_obj.items():
|
|
@@ -123,7 +127,7 @@ class GenericProvider(BaseProvider):
|
|
|
123
127
|
Args:
|
|
124
128
|
query: Search query string
|
|
125
129
|
api_key: API key for authentication
|
|
126
|
-
**kwargs: Additional parameters (limit, language, api_url)
|
|
130
|
+
**kwargs: Additional parameters (limit, language, api_url, proxy_url, skip_warmup)
|
|
127
131
|
|
|
128
132
|
Returns:
|
|
129
133
|
dict: Search results with 'results' and 'metrics' keys
|
|
@@ -132,13 +136,46 @@ class GenericProvider(BaseProvider):
|
|
|
132
136
|
limit = kwargs.get('limit', '10')
|
|
133
137
|
language = kwargs.get('language')
|
|
134
138
|
custom_url = kwargs.get('api_url')
|
|
139
|
+
proxy_url = kwargs.get('proxy_url')
|
|
140
|
+
skip_warmup = kwargs.get('skip_warmup', False)
|
|
141
|
+
|
|
142
|
+
# 2. Configure proxy for this request if provided
|
|
143
|
+
if proxy_url:
|
|
144
|
+
self.session.proxies = {
|
|
145
|
+
'http': proxy_url,
|
|
146
|
+
'https': proxy_url,
|
|
147
|
+
}
|
|
148
|
+
logger.info('Using proxy: %s', proxy_url)
|
|
149
|
+
|
|
150
|
+
# Disable SSL verification when using proxy
|
|
151
|
+
self.session.verify = False
|
|
152
|
+
# Suppress InsecureRequestWarning
|
|
153
|
+
import urllib3
|
|
154
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
155
|
+
else:
|
|
156
|
+
# Check environment variables if no user proxy configured
|
|
157
|
+
env_proxy = os.environ.get('https_proxy') or os.environ.get('HTTPS_PROXY') or \
|
|
158
|
+
os.environ.get('http_proxy') or os.environ.get('HTTP_PROXY')
|
|
159
|
+
if env_proxy:
|
|
160
|
+
self.session.proxies = {
|
|
161
|
+
'http': env_proxy,
|
|
162
|
+
'https': env_proxy,
|
|
163
|
+
}
|
|
164
|
+
logger.info('Using proxy from environment: %s', env_proxy)
|
|
165
|
+
self.session.verify = False
|
|
166
|
+
import urllib3
|
|
167
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
168
|
+
else:
|
|
169
|
+
# No proxy, ensure normal SSL verification
|
|
170
|
+
self.session.proxies = {}
|
|
171
|
+
self.session.verify = True
|
|
135
172
|
|
|
136
|
-
#
|
|
173
|
+
# 3. Determine configuration
|
|
137
174
|
# Use custom api_url if provided, otherwise fallback to config url
|
|
138
175
|
url = custom_url.strip() if custom_url else self.config.get('url')
|
|
139
176
|
method = self.config.get('method', 'GET')
|
|
140
177
|
|
|
141
|
-
#
|
|
178
|
+
# 4. Prepare context for template injection
|
|
142
179
|
context = {
|
|
143
180
|
'query': query,
|
|
144
181
|
'api_key': api_key,
|
|
@@ -148,7 +185,7 @@ class GenericProvider(BaseProvider):
|
|
|
148
185
|
if language:
|
|
149
186
|
context['language'] = language
|
|
150
187
|
|
|
151
|
-
#
|
|
188
|
+
# 5. construct request components
|
|
152
189
|
headers = self._fill_template(self.config.get('headers', {}), **context)
|
|
153
190
|
params = self._fill_template(self.config.get('params', {}), **context)
|
|
154
191
|
json_body = self._fill_template(self.config.get('payload', {}), **context)
|
|
@@ -158,15 +195,13 @@ class GenericProvider(BaseProvider):
|
|
|
158
195
|
|
|
159
196
|
# Ensure connection is pre-warmed (use HEAD request to verify availability)
|
|
160
197
|
# Pre-warming is not counted in request latency, only verifies connection
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'metrics': {'latency_ms': 0, 'server_latency_ms': None, 'size_bytes': 0},
|
|
169
|
-
}
|
|
198
|
+
# Skip connection warm-up if disabled in config or by user
|
|
199
|
+
if not skip_warmup and not self.config.get('skip_connection_warmup', False):
|
|
200
|
+
try:
|
|
201
|
+
self._ensure_connection(url, headers)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.warning('Connection Warm-up Warning: %s (continuing anyway)', e)
|
|
204
|
+
# Don't return error, continue with the actual request
|
|
170
205
|
|
|
171
206
|
try:
|
|
172
207
|
req_args = {'headers': headers, 'timeout': 30}
|
|
@@ -233,8 +268,8 @@ class GenericProvider(BaseProvider):
|
|
|
233
268
|
if isinstance(item, dict):
|
|
234
269
|
for key, value in item.items():
|
|
235
270
|
# Check if this key is already mapped in config
|
|
236
|
-
#
|
|
237
|
-
if key not in mapped_paths and value
|
|
271
|
+
# Include all unmapped fields, including nested objects and arrays
|
|
272
|
+
if key not in mapped_paths and value is not None:
|
|
238
273
|
snippet_fields[key] = value
|
|
239
274
|
|
|
240
275
|
# Store snippet fields as JSON structure in snippet
|
|
@@ -242,7 +277,7 @@ class GenericProvider(BaseProvider):
|
|
|
242
277
|
entry['snippet'] = snippet_fields
|
|
243
278
|
else:
|
|
244
279
|
entry['snippet'] = ''
|
|
245
|
-
|
|
280
|
+
logger.debug('snippet_fields: %s', snippet_fields)
|
|
246
281
|
normalized_results.append(entry)
|
|
247
282
|
|
|
248
283
|
# Post-process: extract domain from URL if site_name is empty
|
search_api_webui/providers.yaml
CHANGED
|
@@ -12,8 +12,8 @@ querit:
|
|
|
12
12
|
root_path: "results.result"
|
|
13
13
|
server_latency_path: "took"
|
|
14
14
|
fields:
|
|
15
|
-
title: "title"
|
|
16
15
|
url: "url"
|
|
16
|
+
title: "title"
|
|
17
17
|
site_name: "site_name"
|
|
18
18
|
site_icon: "site_icon"
|
|
19
19
|
page_age: "page_age"
|
|
@@ -31,11 +31,93 @@ you:
|
|
|
31
31
|
root_path: "results.web"
|
|
32
32
|
server_latency_path: "metadata.latency"
|
|
33
33
|
fields:
|
|
34
|
-
title: "title"
|
|
35
34
|
url: "url"
|
|
35
|
+
title: "title"
|
|
36
36
|
site_icon: "favicon_url"
|
|
37
37
|
page_age: "page_age"
|
|
38
38
|
|
|
39
|
+
tavily:
|
|
40
|
+
url: "https://api.tavily.com/search"
|
|
41
|
+
method: "POST"
|
|
42
|
+
headers:
|
|
43
|
+
"Accept": "application/json"
|
|
44
|
+
"Content-Type": "application/json"
|
|
45
|
+
payload:
|
|
46
|
+
api_key: "{api_key}"
|
|
47
|
+
query: "{query}"
|
|
48
|
+
max_results: "{limit}"
|
|
49
|
+
search_depth: "basic"
|
|
50
|
+
include_favicon: "true"
|
|
51
|
+
response_mapping:
|
|
52
|
+
root_path: "results"
|
|
53
|
+
server_latency_path: "response_time"
|
|
54
|
+
fields:
|
|
55
|
+
url: "url"
|
|
56
|
+
title: "title"
|
|
57
|
+
page_age: "published_date"
|
|
58
|
+
site_icon: "favicon"
|
|
59
|
+
|
|
60
|
+
exa:
|
|
61
|
+
url: "https://api.exa.ai/search"
|
|
62
|
+
method: "POST"
|
|
63
|
+
headers:
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
"x-api-key": "{api_key}"
|
|
66
|
+
params: {}
|
|
67
|
+
payload:
|
|
68
|
+
query: "{query}"
|
|
69
|
+
numResults: "{limit}"
|
|
70
|
+
type: "auto"
|
|
71
|
+
response_mapping:
|
|
72
|
+
root_path: "results"
|
|
73
|
+
fields:
|
|
74
|
+
url: "url"
|
|
75
|
+
title: "title"
|
|
76
|
+
site_icon: "favicon"
|
|
77
|
+
page_age: "publishedDate"
|
|
78
|
+
|
|
79
|
+
parallel:
|
|
80
|
+
url: "https://api.parallel.ai/v1beta/search"
|
|
81
|
+
method: "POST"
|
|
82
|
+
headers:
|
|
83
|
+
"Content-Type": "application/json"
|
|
84
|
+
"x-api-key": "{api_key}"
|
|
85
|
+
"parallel-beta": "search-extract-2025-10-10"
|
|
86
|
+
payload:
|
|
87
|
+
mode: "one-shot"
|
|
88
|
+
max_results: "{limit}"
|
|
89
|
+
objective: "{query}"
|
|
90
|
+
response_mapping:
|
|
91
|
+
root_path: "results"
|
|
92
|
+
fields:
|
|
93
|
+
url: "url"
|
|
94
|
+
title: "title"
|
|
95
|
+
page_age: "publish_date"
|
|
96
|
+
|
|
97
|
+
baidu:
|
|
98
|
+
url: "https://qianfan.baidubce.com/v2/ai_search/web_search"
|
|
99
|
+
method: "POST"
|
|
100
|
+
headers:
|
|
101
|
+
"Accept": "application/json"
|
|
102
|
+
"Authorization": "Bearer {api_key}"
|
|
103
|
+
"Content-Type": "application/json"
|
|
104
|
+
payload:
|
|
105
|
+
messages:
|
|
106
|
+
- content: "{query}"
|
|
107
|
+
role: "user"
|
|
108
|
+
search_source: "baidu_search_v2"
|
|
109
|
+
resource_type_filter:
|
|
110
|
+
- type: "web"
|
|
111
|
+
top_k: "{limit}"
|
|
112
|
+
response_mapping:
|
|
113
|
+
root_path: "references"
|
|
114
|
+
fields:
|
|
115
|
+
url: "url"
|
|
116
|
+
title: "title"
|
|
117
|
+
site_name: "website"
|
|
118
|
+
site_icon: "icon"
|
|
119
|
+
page_age: "date"
|
|
120
|
+
|
|
39
121
|
brave:
|
|
40
122
|
url: "https://api.search.brave.com/res/v1/web/search"
|
|
41
123
|
method: "GET"
|
|
@@ -49,27 +131,26 @@ brave:
|
|
|
49
131
|
response_mapping:
|
|
50
132
|
root_path: "web.results"
|
|
51
133
|
fields:
|
|
52
|
-
title: "title"
|
|
53
134
|
url: "url"
|
|
135
|
+
title: "title"
|
|
54
136
|
page_age: "page_age"
|
|
55
137
|
|
|
56
|
-
|
|
57
|
-
url: "https://
|
|
138
|
+
serper:
|
|
139
|
+
url: "https://google.serper.dev/search"
|
|
58
140
|
method: "POST"
|
|
59
141
|
headers:
|
|
142
|
+
"X-API-KEY": "{api_key}"
|
|
60
143
|
"Content-Type": "application/json"
|
|
61
|
-
"x-api-key": "{api_key}"
|
|
62
144
|
params: {}
|
|
63
145
|
payload:
|
|
64
|
-
|
|
65
|
-
|
|
146
|
+
q: "{query}"
|
|
147
|
+
num: "{limit}"
|
|
66
148
|
response_mapping:
|
|
67
|
-
root_path: "
|
|
149
|
+
root_path: "organic"
|
|
68
150
|
fields:
|
|
151
|
+
url: "link"
|
|
69
152
|
title: "title"
|
|
70
|
-
|
|
71
|
-
page_age: "publishedDate"
|
|
72
|
-
site_icon: "favicon"
|
|
153
|
+
snippet: "snippet"
|
|
73
154
|
|
|
74
155
|
querit_sdk:
|
|
75
156
|
type: "querit_sdk"
|
|
Binary file
|
|
@@ -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}.ml-4{margin-left:1rem}.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}.block{display:block}.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-20{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-auto{width:auto}.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}.flex-wrap{flex-wrap:wrap}.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{border-radius:.25rem}.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-l-2{border-left-width:2px}.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}.object-cover{-o-object-fit:cover;object-fit:cover}.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}.pl-3{padding-left:.75rem}.pl-6{padding-left:1.5rem}.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\:border-blue-400:hover{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-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-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.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}}
|