Spaces:
Running
Running
Commit
Β·
792eeb2
1
Parent(s):
9d65a12
Update app configuration and enhance search functionality
Browse files- Refactor search interface to toggle between basic and advanced modes
- Improve callback functions for handling search queries and results
- Add new features for advanced search with multiple text and image queries
- Update UI components for better user experience and accessibility
- app.py +1 -5
- src/callbacks.py +150 -89
- src/components.py +143 -97
app.py
CHANGED
|
@@ -94,7 +94,7 @@ def main():
|
|
| 94 |
parser.add_argument(
|
| 95 |
'--checkpoint',
|
| 96 |
type=str,
|
| 97 |
-
default='
|
| 98 |
help='Path to CLIP model checkpoint'
|
| 99 |
)
|
| 100 |
parser.add_argument(
|
|
@@ -129,9 +129,5 @@ def main():
|
|
| 129 |
)
|
| 130 |
|
| 131 |
|
| 132 |
-
# For gunicorn/production deployment (Hugging Face Spaces)
|
| 133 |
-
app = create_app('aionsearchmodel.pt')
|
| 134 |
-
server = app.server
|
| 135 |
-
|
| 136 |
if __name__ == "__main__":
|
| 137 |
main()
|
|
|
|
| 94 |
parser.add_argument(
|
| 95 |
'--checkpoint',
|
| 96 |
type=str,
|
| 97 |
+
default='checkpoint_epoch_20.pt',
|
| 98 |
help='Path to CLIP model checkpoint'
|
| 99 |
)
|
| 100 |
parser.add_argument(
|
|
|
|
| 129 |
)
|
| 130 |
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if __name__ == "__main__":
|
| 133 |
main()
|
src/callbacks.py
CHANGED
|
@@ -38,22 +38,31 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 38 |
def update_galaxy_count(_):
|
| 39 |
"""Update the galaxy count display."""
|
| 40 |
if search_service and config.TOTAL_GALAXIES > 0:
|
| 41 |
-
return f"{config.TOTAL_GALAXIES:,}
|
| 42 |
else:
|
| 43 |
return "loading..."
|
| 44 |
|
| 45 |
@app.callback(
|
| 46 |
-
[Output("
|
| 47 |
-
Output("
|
|
|
|
|
|
|
| 48 |
Input("vector-toggle", "n_clicks"),
|
| 49 |
-
State("
|
|
|
|
| 50 |
prevent_initial_call=True
|
| 51 |
)
|
| 52 |
-
def
|
| 53 |
-
"""Toggle
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
@app.callback(
|
| 59 |
Output("vector-inputs", "children", allow_duplicate=True),
|
|
@@ -118,6 +127,19 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 118 |
|
| 119 |
return dash.no_update, dash.no_update
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
@app.callback(
|
| 122 |
[Output({"type": "text-input-container", "index": dash.dependencies.ALL}, "style"),
|
| 123 |
Output({"type": "image-input-container", "index": dash.dependencies.ALL}, "style")],
|
|
@@ -141,57 +163,88 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 141 |
|
| 142 |
@app.callback(
|
| 143 |
[Output("search-button", "n_clicks"),
|
| 144 |
-
Output("search-input", "value")
|
|
|
|
| 145 |
[Input("example-1", "n_clicks"),
|
| 146 |
Input("example-2", "n_clicks"),
|
| 147 |
Input("example-3", "n_clicks"),
|
| 148 |
-
Input("example-4", "n_clicks"),
|
| 149 |
Input("example-5", "n_clicks"),
|
| 150 |
Input("example-6", "n_clicks"),
|
| 151 |
Input("example-7", "n_clicks")],
|
| 152 |
[State("search-button", "n_clicks")],
|
| 153 |
prevent_initial_call=True
|
| 154 |
)
|
| 155 |
-
def trigger_search_from_examples(click1, click2, click3,
|
| 156 |
"""Trigger search when example buttons are clicked."""
|
| 157 |
ctx = callback_context
|
| 158 |
if not ctx.triggered:
|
| 159 |
-
return dash.no_update, dash.no_update
|
| 160 |
|
| 161 |
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
|
| 162 |
|
| 163 |
example_queries = {
|
| 164 |
"example-1": "Merging edge-on galaxy",
|
| 165 |
"example-2": "A peculiar interacting galaxy system featuring plenty of tidal tails and a disturbed morphology",
|
| 166 |
-
"example-3": "
|
| 167 |
-
"example-4": "Strong gravitational lens",
|
| 168 |
"example-5": "A violent merger in progress with visible tidal features",
|
| 169 |
"example-6": "Low surface brightness",
|
| 170 |
-
"example-7": "
|
| 171 |
}
|
| 172 |
|
| 173 |
search_query = example_queries.get(button_id, "")
|
| 174 |
|
| 175 |
if search_query:
|
| 176 |
-
return (current_clicks or 0) + 1, search_query
|
| 177 |
|
| 178 |
-
return dash.no_update, dash.no_update
|
| 179 |
|
| 180 |
@app.callback(
|
| 181 |
[Output("search-time", "children"),
|
| 182 |
Output("search-results", "children"),
|
| 183 |
Output("search-data", "data"),
|
| 184 |
-
Output("download-button", "disabled")
|
|
|
|
| 185 |
[Input("search-button", "n_clicks"),
|
| 186 |
-
Input("search-input", "n_submit")
|
|
|
|
| 187 |
[State("search-input", "value"),
|
| 188 |
-
State("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
prevent_initial_call=True
|
| 190 |
)
|
| 191 |
-
def perform_search(
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
if not query or not query.strip():
|
| 194 |
-
return "", dbc.Alert("Please enter a search query", color="warning"), None, True
|
| 195 |
|
| 196 |
try:
|
| 197 |
# Extract min and max from slider range
|
|
@@ -236,13 +289,13 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 236 |
load_more_button
|
| 237 |
])
|
| 238 |
|
| 239 |
-
return "", results_container, search_data, False
|
| 240 |
|
| 241 |
except Exception as e:
|
| 242 |
error_msg = dbc.Alert(f"Search failed: {str(e)}", color="danger")
|
| 243 |
logger.error(f"Search error: {e}")
|
| 244 |
logger.error(f"Full traceback:\n{traceback.format_exc()}")
|
| 245 |
-
return "", error_msg, None, True
|
| 246 |
|
| 247 |
@app.callback(
|
| 248 |
[Output("galaxy-modal", "is_open"),
|
|
@@ -360,9 +413,10 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 360 |
return dash.no_update, dash.no_update
|
| 361 |
|
| 362 |
@app.callback(
|
| 363 |
-
[Output("
|
|
|
|
|
|
|
| 364 |
Output("vector-inputs-count", "data", allow_duplicate=True),
|
| 365 |
-
Output("vector-collapse", "is_open", allow_duplicate=True),
|
| 366 |
Output("galaxy-modal", "is_open", allow_duplicate=True)],
|
| 367 |
Input("add-to-advanced-search", "n_clicks"),
|
| 368 |
[State("current-galaxy-data", "data"),
|
|
@@ -371,16 +425,16 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 371 |
prevent_initial_call=True
|
| 372 |
)
|
| 373 |
def add_galaxy_to_advanced_search(n_clicks, galaxy_data, current_children, count):
|
| 374 |
-
"""Add the current galaxy's RA/Dec
|
| 375 |
if not n_clicks or not galaxy_data:
|
| 376 |
-
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
| 377 |
|
| 378 |
# Extract galaxy coordinates
|
| 379 |
ra = galaxy_data.get('ra')
|
| 380 |
dec = galaxy_data.get('dec')
|
| 381 |
|
| 382 |
if ra is None or dec is None:
|
| 383 |
-
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
| 384 |
|
| 385 |
# Create a new image input row with the galaxy's RA/Dec pre-filled
|
| 386 |
new_row = create_vector_input_row(
|
|
@@ -393,29 +447,49 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 393 |
|
| 394 |
current_children.append(new_row)
|
| 395 |
|
| 396 |
-
#
|
| 397 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
@app.callback(
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
Input("vector-search-button", "n_clicks"),
|
| 405 |
-
[State({"type": "vector-query-type", "index": dash.dependencies.ALL}, "value"),
|
| 406 |
-
State({"type": "vector-text", "index": dash.dependencies.ALL}, "value"),
|
| 407 |
-
State({"type": "vector-ra", "index": dash.dependencies.ALL}, "value"),
|
| 408 |
-
State({"type": "vector-dec", "index": dash.dependencies.ALL}, "value"),
|
| 409 |
-
State({"type": "vector-fov", "index": dash.dependencies.ALL}, "value"),
|
| 410 |
-
State({"type": "vector-operation", "index": dash.dependencies.ALL}, "value"),
|
| 411 |
-
State("rmag-slider", "value")],
|
| 412 |
prevent_initial_call=True
|
| 413 |
)
|
| 414 |
-
def
|
| 415 |
-
"""
|
| 416 |
-
if
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
def operation_to_weight(op_str):
|
| 420 |
"""Convert operation string to float weight."""
|
| 421 |
if op_str == "+":
|
|
@@ -423,7 +497,6 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 423 |
elif op_str == "-":
|
| 424 |
return -1.0
|
| 425 |
else:
|
| 426 |
-
# For magnitude values like "+2", "-5", etc.
|
| 427 |
return float(op_str)
|
| 428 |
|
| 429 |
def weight_to_display(weight):
|
|
@@ -437,37 +510,52 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 437 |
else:
|
| 438 |
return str(int(weight))
|
| 439 |
|
| 440 |
-
# Parse
|
| 441 |
text_queries = []
|
| 442 |
text_weights = []
|
| 443 |
image_queries = []
|
| 444 |
image_weights = []
|
| 445 |
|
| 446 |
-
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
weight = operation_to_weight(operation)
|
| 449 |
|
| 450 |
if query_type == "text":
|
| 451 |
-
text_value =
|
| 452 |
if text_value and text_value.strip():
|
| 453 |
text_queries.append(text_value.strip())
|
| 454 |
text_weights.append(weight)
|
| 455 |
else: # image
|
| 456 |
-
ra =
|
| 457 |
-
dec =
|
| 458 |
-
fov = fov_values[i] if fov_values[i] else 0.025
|
| 459 |
|
| 460 |
if ra is not None and dec is not None:
|
| 461 |
image_queries.append({
|
| 462 |
'ra': float(ra),
|
| 463 |
'dec': float(dec),
|
| 464 |
-
'fov':
|
| 465 |
})
|
| 466 |
image_weights.append(weight)
|
| 467 |
|
| 468 |
# Validate that we have at least one query
|
| 469 |
if not text_queries and not image_queries:
|
| 470 |
-
return "", dbc.Alert("Please enter at least one text or image query", color="warning"), None, True
|
| 471 |
|
| 472 |
try:
|
| 473 |
# Extract min and max from slider range
|
|
@@ -569,40 +657,13 @@ def register_callbacks(app, search_service: SearchService):
|
|
| 569 |
load_more_button
|
| 570 |
])
|
| 571 |
|
| 572 |
-
return "", results_container, search_data, False
|
| 573 |
|
| 574 |
except Exception as e:
|
| 575 |
error_msg = dbc.Alert(f"Advanced search failed: {str(e)}", color="danger")
|
| 576 |
logger.error(f"Advanced search error: {e}")
|
| 577 |
logger.error(f"Full traceback:\n{traceback.format_exc()}")
|
| 578 |
-
return "", error_msg, None, True
|
| 579 |
-
|
| 580 |
-
@app.callback(
|
| 581 |
-
Output("download-csv", "data"),
|
| 582 |
-
Input("download-button", "n_clicks"),
|
| 583 |
-
State("search-data", "data"),
|
| 584 |
-
prevent_initial_call=True
|
| 585 |
-
)
|
| 586 |
-
def download_csv(n_clicks, search_data):
|
| 587 |
-
"""Download search results as CSV."""
|
| 588 |
-
if n_clicks and search_data:
|
| 589 |
-
# Create DataFrame with the search results
|
| 590 |
-
df = pd.DataFrame({
|
| 591 |
-
ZILLIZ_PRIMARY_KEY: search_data[ZILLIZ_PRIMARY_KEY],
|
| 592 |
-
'ra': search_data['ra'],
|
| 593 |
-
'dec': search_data['dec'],
|
| 594 |
-
'r_mag': search_data['r_mag'],
|
| 595 |
-
'distance': search_data['distance'],
|
| 596 |
-
'cutout_url': search_data['cutout_url']
|
| 597 |
-
})
|
| 598 |
-
|
| 599 |
-
# Create CSV string
|
| 600 |
-
csv_string = df.to_csv(index=False)
|
| 601 |
-
|
| 602 |
-
# Return download data
|
| 603 |
-
return dict(content=csv_string, filename="galaxy_search_results.csv")
|
| 604 |
-
|
| 605 |
-
return dash.no_update
|
| 606 |
|
| 607 |
|
| 608 |
# Helper functions for callbacks
|
|
@@ -745,7 +806,7 @@ def build_modal_content(galaxy_info: dict) -> tuple:
|
|
| 745 |
html.Span(f"r_mag: {galaxy_info['r_mag']:.2f}", className="d-inline-block mb-0",
|
| 746 |
style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
|
| 747 |
html.Span(" β’ ", className="mx-2", style={"color": "rgba(245, 245, 247, 0.5)"}),
|
| 748 |
-
html.Span(f"
|
| 749 |
style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
|
| 750 |
], className="mb-3"),
|
| 751 |
])
|
|
|
|
| 38 |
def update_galaxy_count(_):
|
| 39 |
"""Update the galaxy count display."""
|
| 40 |
if search_service and config.TOTAL_GALAXIES > 0:
|
| 41 |
+
return f"{config.TOTAL_GALAXIES:,} GALAXIES FROM LEGACY SURVEY DR10"
|
| 42 |
else:
|
| 43 |
return "loading..."
|
| 44 |
|
| 45 |
@app.callback(
|
| 46 |
+
[Output("basic-search-bar", "style"),
|
| 47 |
+
Output("advanced-search-interface", "style"),
|
| 48 |
+
Output("vector-arrow", "className"),
|
| 49 |
+
Output("search-input-advanced", "value")],
|
| 50 |
Input("vector-toggle", "n_clicks"),
|
| 51 |
+
[State("advanced-search-interface", "style"),
|
| 52 |
+
State("search-input", "value")],
|
| 53 |
prevent_initial_call=True
|
| 54 |
)
|
| 55 |
+
def toggle_search_mode(n_clicks, advanced_style, basic_text):
|
| 56 |
+
"""Toggle between basic and advanced search interfaces."""
|
| 57 |
+
# Check if advanced mode is currently shown
|
| 58 |
+
is_advanced_shown = advanced_style.get("display") == "block"
|
| 59 |
+
|
| 60 |
+
if is_advanced_shown:
|
| 61 |
+
# Switch to basic mode
|
| 62 |
+
return {"display": "block"}, {"display": "none"}, "fas fa-chevron-down me-2", dash.no_update
|
| 63 |
+
else:
|
| 64 |
+
# Switch to advanced mode - copy text from basic to advanced
|
| 65 |
+
return {"display": "none"}, {"display": "block"}, "fas fa-chevron-up me-2", basic_text or ""
|
| 66 |
|
| 67 |
@app.callback(
|
| 68 |
Output("vector-inputs", "children", allow_duplicate=True),
|
|
|
|
| 127 |
|
| 128 |
return dash.no_update, dash.no_update
|
| 129 |
|
| 130 |
+
@app.callback(
|
| 131 |
+
[Output("main-text-input-container", "style"),
|
| 132 |
+
Output("main-image-input-container", "style")],
|
| 133 |
+
Input("main-query-type", "value"),
|
| 134 |
+
prevent_initial_call=False
|
| 135 |
+
)
|
| 136 |
+
def toggle_main_query_type(query_type):
|
| 137 |
+
"""Toggle visibility of main query inputs based on query type."""
|
| 138 |
+
if query_type == "image":
|
| 139 |
+
return {"display": "none"}, {"display": "block"}
|
| 140 |
+
else:
|
| 141 |
+
return {"display": "block"}, {"display": "none"}
|
| 142 |
+
|
| 143 |
@app.callback(
|
| 144 |
[Output({"type": "text-input-container", "index": dash.dependencies.ALL}, "style"),
|
| 145 |
Output({"type": "image-input-container", "index": dash.dependencies.ALL}, "style")],
|
|
|
|
| 163 |
|
| 164 |
@app.callback(
|
| 165 |
[Output("search-button", "n_clicks"),
|
| 166 |
+
Output("search-input", "value"),
|
| 167 |
+
Output("search-input-advanced", "value", allow_duplicate=True)],
|
| 168 |
[Input("example-1", "n_clicks"),
|
| 169 |
Input("example-2", "n_clicks"),
|
| 170 |
Input("example-3", "n_clicks"),
|
|
|
|
| 171 |
Input("example-5", "n_clicks"),
|
| 172 |
Input("example-6", "n_clicks"),
|
| 173 |
Input("example-7", "n_clicks")],
|
| 174 |
[State("search-button", "n_clicks")],
|
| 175 |
prevent_initial_call=True
|
| 176 |
)
|
| 177 |
+
def trigger_search_from_examples(click1, click2, click3, click5, click6, click7, current_clicks):
|
| 178 |
"""Trigger search when example buttons are clicked."""
|
| 179 |
ctx = callback_context
|
| 180 |
if not ctx.triggered:
|
| 181 |
+
return dash.no_update, dash.no_update, dash.no_update
|
| 182 |
|
| 183 |
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
|
| 184 |
|
| 185 |
example_queries = {
|
| 186 |
"example-1": "Merging edge-on galaxy",
|
| 187 |
"example-2": "A peculiar interacting galaxy system featuring plenty of tidal tails and a disturbed morphology",
|
| 188 |
+
"example-3": "galaxy with stream",
|
|
|
|
| 189 |
"example-5": "A violent merger in progress with visible tidal features",
|
| 190 |
"example-6": "Low surface brightness",
|
| 191 |
+
"example-7": "face-on ring galaxy"
|
| 192 |
}
|
| 193 |
|
| 194 |
search_query = example_queries.get(button_id, "")
|
| 195 |
|
| 196 |
if search_query:
|
| 197 |
+
return (current_clicks or 0) + 1, search_query, search_query
|
| 198 |
|
| 199 |
+
return dash.no_update, dash.no_update, dash.no_update
|
| 200 |
|
| 201 |
@app.callback(
|
| 202 |
[Output("search-time", "children"),
|
| 203 |
Output("search-results", "children"),
|
| 204 |
Output("search-data", "data"),
|
| 205 |
+
Output("download-button", "disabled"),
|
| 206 |
+
Output("download-button-advanced", "disabled")],
|
| 207 |
[Input("search-button", "n_clicks"),
|
| 208 |
+
Input("search-input", "n_submit"),
|
| 209 |
+
Input("search-button-advanced", "n_clicks")],
|
| 210 |
[State("search-input", "value"),
|
| 211 |
+
State("search-input-advanced", "value"),
|
| 212 |
+
State("rmag-slider", "value"),
|
| 213 |
+
State("advanced-search-interface", "style"),
|
| 214 |
+
State("main-vector-operation", "value"),
|
| 215 |
+
State("main-query-type", "value"),
|
| 216 |
+
State("main-vector-ra", "value"),
|
| 217 |
+
State("main-vector-dec", "value"),
|
| 218 |
+
State({"type": "vector-query-type", "index": dash.dependencies.ALL}, "value"),
|
| 219 |
+
State({"type": "vector-text", "index": dash.dependencies.ALL}, "value"),
|
| 220 |
+
State({"type": "vector-ra", "index": dash.dependencies.ALL}, "value"),
|
| 221 |
+
State({"type": "vector-dec", "index": dash.dependencies.ALL}, "value"),
|
| 222 |
+
State({"type": "vector-operation", "index": dash.dependencies.ALL}, "value")],
|
| 223 |
prevent_initial_call=True
|
| 224 |
)
|
| 225 |
+
def perform_search(n_clicks_basic, n_submit, n_clicks_advanced, query_basic, query_advanced,
|
| 226 |
+
rmag_range, advanced_style, main_operation, main_query_type,
|
| 227 |
+
main_ra, main_dec,
|
| 228 |
+
additional_query_types, additional_text_values, additional_ra_values,
|
| 229 |
+
additional_dec_values, additional_operations):
|
| 230 |
+
"""Perform text search or advanced search based on mode."""
|
| 231 |
+
# Check if advanced mode is active
|
| 232 |
+
is_advanced_mode = advanced_style.get("display") == "block"
|
| 233 |
+
|
| 234 |
+
# If advanced mode is active, perform advanced search
|
| 235 |
+
if is_advanced_mode:
|
| 236 |
+
return perform_advanced_search_logic(
|
| 237 |
+
query_advanced, rmag_range, main_operation, main_query_type,
|
| 238 |
+
main_ra, main_dec,
|
| 239 |
+
additional_query_types, additional_text_values,
|
| 240 |
+
additional_ra_values, additional_dec_values,
|
| 241 |
+
additional_operations
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# Otherwise perform basic search
|
| 245 |
+
query = query_basic
|
| 246 |
if not query or not query.strip():
|
| 247 |
+
return "", dbc.Alert("Please enter a search query", color="warning"), None, True, True
|
| 248 |
|
| 249 |
try:
|
| 250 |
# Extract min and max from slider range
|
|
|
|
| 289 |
load_more_button
|
| 290 |
])
|
| 291 |
|
| 292 |
+
return "", results_container, search_data, False, False
|
| 293 |
|
| 294 |
except Exception as e:
|
| 295 |
error_msg = dbc.Alert(f"Search failed: {str(e)}", color="danger")
|
| 296 |
logger.error(f"Search error: {e}")
|
| 297 |
logger.error(f"Full traceback:\n{traceback.format_exc()}")
|
| 298 |
+
return "", error_msg, None, True, True
|
| 299 |
|
| 300 |
@app.callback(
|
| 301 |
[Output("galaxy-modal", "is_open"),
|
|
|
|
| 413 |
return dash.no_update, dash.no_update
|
| 414 |
|
| 415 |
@app.callback(
|
| 416 |
+
[Output("advanced-search-interface", "style", allow_duplicate=True),
|
| 417 |
+
Output("basic-search-bar", "style", allow_duplicate=True),
|
| 418 |
+
Output("vector-inputs", "children", allow_duplicate=True),
|
| 419 |
Output("vector-inputs-count", "data", allow_duplicate=True),
|
|
|
|
| 420 |
Output("galaxy-modal", "is_open", allow_duplicate=True)],
|
| 421 |
Input("add-to-advanced-search", "n_clicks"),
|
| 422 |
[State("current-galaxy-data", "data"),
|
|
|
|
| 425 |
prevent_initial_call=True
|
| 426 |
)
|
| 427 |
def add_galaxy_to_advanced_search(n_clicks, galaxy_data, current_children, count):
|
| 428 |
+
"""Add the current galaxy's RA/Dec as a new query in advanced search."""
|
| 429 |
if not n_clicks or not galaxy_data:
|
| 430 |
+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
| 431 |
|
| 432 |
# Extract galaxy coordinates
|
| 433 |
ra = galaxy_data.get('ra')
|
| 434 |
dec = galaxy_data.get('dec')
|
| 435 |
|
| 436 |
if ra is None or dec is None:
|
| 437 |
+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
| 438 |
|
| 439 |
# Create a new image input row with the galaxy's RA/Dec pre-filled
|
| 440 |
new_row = create_vector_input_row(
|
|
|
|
| 447 |
|
| 448 |
current_children.append(new_row)
|
| 449 |
|
| 450 |
+
# Switch to advanced mode and add the new query
|
| 451 |
+
return (
|
| 452 |
+
{"display": "block"}, # Show advanced interface
|
| 453 |
+
{"display": "none"}, # Hide basic search bar
|
| 454 |
+
current_children, # Updated children with new query
|
| 455 |
+
count + 1, # Incremented count
|
| 456 |
+
False # Close modal
|
| 457 |
+
)
|
| 458 |
|
| 459 |
@app.callback(
|
| 460 |
+
Output("download-csv", "data"),
|
| 461 |
+
[Input("download-button", "n_clicks"),
|
| 462 |
+
Input("download-button-advanced", "n_clicks")],
|
| 463 |
+
State("search-data", "data"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
prevent_initial_call=True
|
| 465 |
)
|
| 466 |
+
def download_csv(n_clicks_basic, n_clicks_advanced, search_data):
|
| 467 |
+
"""Download search results as CSV."""
|
| 468 |
+
if (n_clicks_basic or n_clicks_advanced) and search_data:
|
| 469 |
+
# Create DataFrame with the search results
|
| 470 |
+
df = pd.DataFrame({
|
| 471 |
+
ZILLIZ_PRIMARY_KEY: search_data[ZILLIZ_PRIMARY_KEY],
|
| 472 |
+
'ra': search_data['ra'],
|
| 473 |
+
'dec': search_data['dec'],
|
| 474 |
+
'r_mag': search_data['r_mag'],
|
| 475 |
+
'distance': search_data['distance'],
|
| 476 |
+
'cutout_url': search_data['cutout_url']
|
| 477 |
+
})
|
| 478 |
+
|
| 479 |
+
# Create CSV string
|
| 480 |
+
csv_string = df.to_csv(index=False)
|
| 481 |
|
| 482 |
+
# Return download data
|
| 483 |
+
return dict(content=csv_string, filename="galaxy_search_results.csv")
|
| 484 |
+
|
| 485 |
+
return dash.no_update
|
| 486 |
+
|
| 487 |
+
def perform_advanced_search_logic(query, rmag_range, main_operation, main_query_type,
|
| 488 |
+
main_ra, main_dec,
|
| 489 |
+
additional_query_types, additional_text_values,
|
| 490 |
+
additional_ra_values, additional_dec_values,
|
| 491 |
+
additional_operations):
|
| 492 |
+
"""Perform advanced search combining main query with additional queries."""
|
| 493 |
def operation_to_weight(op_str):
|
| 494 |
"""Convert operation string to float weight."""
|
| 495 |
if op_str == "+":
|
|
|
|
| 497 |
elif op_str == "-":
|
| 498 |
return -1.0
|
| 499 |
else:
|
|
|
|
| 500 |
return float(op_str)
|
| 501 |
|
| 502 |
def weight_to_display(weight):
|
|
|
|
| 510 |
else:
|
| 511 |
return str(int(weight))
|
| 512 |
|
| 513 |
+
# Parse main query
|
| 514 |
text_queries = []
|
| 515 |
text_weights = []
|
| 516 |
image_queries = []
|
| 517 |
image_weights = []
|
| 518 |
|
| 519 |
+
main_weight = operation_to_weight(main_operation)
|
| 520 |
+
|
| 521 |
+
if main_query_type == "text":
|
| 522 |
+
if query and query.strip():
|
| 523 |
+
text_queries.append(query.strip())
|
| 524 |
+
text_weights.append(main_weight)
|
| 525 |
+
else: # image
|
| 526 |
+
if main_ra is not None and main_dec is not None:
|
| 527 |
+
image_queries.append({
|
| 528 |
+
'ra': float(main_ra),
|
| 529 |
+
'dec': float(main_dec),
|
| 530 |
+
'fov': 0.025
|
| 531 |
+
})
|
| 532 |
+
image_weights.append(main_weight)
|
| 533 |
+
|
| 534 |
+
# Parse additional queries
|
| 535 |
+
for i, query_type in enumerate(additional_query_types):
|
| 536 |
+
operation = additional_operations[i]
|
| 537 |
weight = operation_to_weight(operation)
|
| 538 |
|
| 539 |
if query_type == "text":
|
| 540 |
+
text_value = additional_text_values[i]
|
| 541 |
if text_value and text_value.strip():
|
| 542 |
text_queries.append(text_value.strip())
|
| 543 |
text_weights.append(weight)
|
| 544 |
else: # image
|
| 545 |
+
ra = additional_ra_values[i]
|
| 546 |
+
dec = additional_dec_values[i]
|
|
|
|
| 547 |
|
| 548 |
if ra is not None and dec is not None:
|
| 549 |
image_queries.append({
|
| 550 |
'ra': float(ra),
|
| 551 |
'dec': float(dec),
|
| 552 |
+
'fov': 0.025
|
| 553 |
})
|
| 554 |
image_weights.append(weight)
|
| 555 |
|
| 556 |
# Validate that we have at least one query
|
| 557 |
if not text_queries and not image_queries:
|
| 558 |
+
return "", dbc.Alert("Please enter at least one text or image query", color="warning"), None, True, True
|
| 559 |
|
| 560 |
try:
|
| 561 |
# Extract min and max from slider range
|
|
|
|
| 657 |
load_more_button
|
| 658 |
])
|
| 659 |
|
| 660 |
+
return "", results_container, search_data, False, False
|
| 661 |
|
| 662 |
except Exception as e:
|
| 663 |
error_msg = dbc.Alert(f"Advanced search failed: {str(e)}", color="danger")
|
| 664 |
logger.error(f"Advanced search error: {e}")
|
| 665 |
logger.error(f"Full traceback:\n{traceback.format_exc()}")
|
| 666 |
+
return "", error_msg, None, True, True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
|
| 668 |
|
| 669 |
# Helper functions for callbacks
|
|
|
|
| 806 |
html.Span(f"r_mag: {galaxy_info['r_mag']:.2f}", className="d-inline-block mb-0",
|
| 807 |
style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
|
| 808 |
html.Span(" β’ ", className="mx-2", style={"color": "rgba(245, 245, 247, 0.5)"}),
|
| 809 |
+
html.Span(f"Cosine Similarity to Query: {galaxy_info['distance']:.4f}", className="d-inline-block mb-0",
|
| 810 |
style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
|
| 811 |
], className="mb-3"),
|
| 812 |
])
|
src/components.py
CHANGED
|
@@ -483,6 +483,9 @@ def create_header():
|
|
| 483 |
dbc.Col([
|
| 484 |
html.Div([
|
| 485 |
html.H1("galaxy semantic search", className="galaxy-title text-center mb-1"),
|
|
|
|
|
|
|
|
|
|
| 486 |
html.Div(id="galaxy-count", className="galaxy-count text-center")
|
| 487 |
], className="text-center mb-3")
|
| 488 |
])
|
|
@@ -545,8 +548,6 @@ def create_search_container():
|
|
| 545 |
id="example-2", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 546 |
dbc.Button([html.I(className="fas fa-stream me-2"), "Stream"],
|
| 547 |
id="example-3", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 548 |
-
dbc.Button([html.I(className="fas fa-glasses me-2"), "Gravitational lens"],
|
| 549 |
-
id="example-4", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 550 |
dbc.Button([html.I(className="fas fa-explosion me-2"), "A violent merger"],
|
| 551 |
id="example-5", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 552 |
dbc.Button([html.I(className="fas fa-moon me-2"), "Low surface brightness"],
|
|
@@ -556,76 +557,146 @@ def create_search_container():
|
|
| 556 |
], className="text-center")
|
| 557 |
], className="mb-3"),
|
| 558 |
|
| 559 |
-
#
|
| 560 |
-
|
| 561 |
-
dbc.
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
dbc.Button([
|
| 571 |
-
html.I(className="fas fa-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
])
|
| 576 |
], className="search-container", style={"position": "relative"}),
|
| 577 |
|
| 578 |
# r_mag filter
|
| 579 |
-
create_rmag_filter_panel()
|
| 580 |
-
|
| 581 |
-
# Vector Addition toggle button
|
| 582 |
-
dbc.Row([
|
| 583 |
-
dbc.Col([
|
| 584 |
-
html.Button([
|
| 585 |
-
html.I(className="fas fa-chevron-down", id="vector-arrow"),
|
| 586 |
-
"Advanced Search (Vector Addition / Images)"
|
| 587 |
-
], id="vector-toggle", className="refinement-toggle w-100")
|
| 588 |
-
], width=12)
|
| 589 |
-
], className="mt-3"),
|
| 590 |
-
|
| 591 |
-
# Vector Addition UI - Collapsible section
|
| 592 |
-
create_vector_addition_panel()
|
| 593 |
], width=12, lg=11, className="mx-auto")
|
| 594 |
], className="mb-3")
|
| 595 |
|
| 596 |
|
| 597 |
-
def create_vector_addition_panel():
|
| 598 |
-
"""Create the advanced search (vector addition) collapsible panel."""
|
| 599 |
-
return dbc.Collapse([
|
| 600 |
-
html.Div([
|
| 601 |
-
html.P("Advanced Search: Combine multiple text and/or image queries using vector addition/subtraction:", className="refinement-label"),
|
| 602 |
-
html.Div(id="vector-inputs", children=[
|
| 603 |
-
# Initial input
|
| 604 |
-
create_vector_input_row(0)
|
| 605 |
-
]),
|
| 606 |
-
dbc.Row([
|
| 607 |
-
dbc.Col([
|
| 608 |
-
dbc.Button(
|
| 609 |
-
[html.I(className="fas fa-plus me-2"), "Add Query"],
|
| 610 |
-
id="add-vector-input",
|
| 611 |
-
color="secondary",
|
| 612 |
-
size="sm",
|
| 613 |
-
className="me-2 btn-add-vector"
|
| 614 |
-
)
|
| 615 |
-
], width=6),
|
| 616 |
-
dbc.Col([
|
| 617 |
-
dbc.Button(
|
| 618 |
-
"Advanced Search",
|
| 619 |
-
id="vector-search-button",
|
| 620 |
-
className="btn-primary w-100",
|
| 621 |
-
n_clicks=0
|
| 622 |
-
)
|
| 623 |
-
], width=6)
|
| 624 |
-
], className="mt-3")
|
| 625 |
-
], className="refinement-container")
|
| 626 |
-
], id="vector-collapse", is_open=False)
|
| 627 |
-
|
| 628 |
-
|
| 629 |
def create_vector_input_row(index: int, query_type: str = "text", ra: float = None, dec: float = None, fov: float = 0.025):
|
| 630 |
"""Create a single vector input row with operation selector, query type toggle, and conditional inputs.
|
| 631 |
|
|
@@ -692,30 +763,21 @@ def create_vector_input_row(index: int, query_type: str = "text", ra: float = No
|
|
| 692 |
dbc.Col([
|
| 693 |
dbc.Input(
|
| 694 |
id={"type": "vector-ra", "index": index},
|
| 695 |
-
placeholder="
|
| 696 |
type="number",
|
| 697 |
step="any",
|
| 698 |
value=ra
|
| 699 |
)
|
| 700 |
-
], width=
|
| 701 |
dbc.Col([
|
| 702 |
dbc.Input(
|
| 703 |
id={"type": "vector-dec", "index": index},
|
| 704 |
-
placeholder="
|
| 705 |
type="number",
|
| 706 |
step="any",
|
| 707 |
value=dec
|
| 708 |
)
|
| 709 |
-
], width=
|
| 710 |
-
dbc.Col([
|
| 711 |
-
dbc.Input(
|
| 712 |
-
id={"type": "vector-fov", "index": index},
|
| 713 |
-
placeholder="fov:",
|
| 714 |
-
type="number",
|
| 715 |
-
value=fov,
|
| 716 |
-
step="any"
|
| 717 |
-
)
|
| 718 |
-
], width=4)
|
| 719 |
])
|
| 720 |
], id={"type": "image-input-container", "index": index}, style=image_display)
|
| 721 |
], width=8),
|
|
@@ -778,26 +840,10 @@ def create_info_modal():
|
|
| 778 |
return dbc.Modal([
|
| 779 |
dbc.ModalHeader(dbc.ModalTitle([html.I(className="fas fa-info-circle me-2"), "About Galaxy Search"])),
|
| 780 |
dbc.ModalBody([
|
| 781 |
-
html.P("This app
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
"The model was trained on galaxy descriptions and can understand various astronomical features and characteristics.",
|
| 786 |
-
style={"margin-bottom": "1rem", "color": "rgba(245, 245, 247, 0.7)"}),
|
| 787 |
-
|
| 788 |
-
html.H6("Search Tips:", style={"color": "#F5F5F7", "font-weight": "500", "margin-bottom": "0.5rem"}),
|
| 789 |
-
html.Ul([
|
| 790 |
-
html.Li("Describe morphological features (spiral, elliptical, irregular, merging)",
|
| 791 |
-
style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
|
| 792 |
-
html.Li("Mention specific features (tidal tails, dust lanes, star-forming regions)",
|
| 793 |
-
style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
|
| 794 |
-
html.Li("Use color descriptions or brightness characteristics",
|
| 795 |
-
style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
|
| 796 |
-
html.Li("Combine multiple features for more specific results",
|
| 797 |
-
style={"color": "rgba(245, 245, 247, 0.6)"}),
|
| 798 |
-
], style={"margin-left": "1rem"}),
|
| 799 |
-
], style={"background": "rgba(255, 255, 255, 0.05)", "padding": "1.5rem", "border-radius": "12px",
|
| 800 |
-
"border": "0.5px solid rgba(255, 255, 255, 0.1)", "color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"})
|
| 801 |
]),
|
| 802 |
dbc.ModalFooter(
|
| 803 |
dbc.Button("Close", id="close-info-modal", className="ms-auto")
|
|
|
|
| 483 |
dbc.Col([
|
| 484 |
html.Div([
|
| 485 |
html.H1("galaxy semantic search", className="galaxy-title text-center mb-1"),
|
| 486 |
+
html.P("powered by AION-Search", className="text-center mb-2",
|
| 487 |
+
style={"color": "rgba(245, 245, 247, 0.5)", "font-weight": "300",
|
| 488 |
+
"font-size": "0.8rem", "letter-spacing": "0.05em"}),
|
| 489 |
html.Div(id="galaxy-count", className="galaxy-count text-center")
|
| 490 |
], className="text-center mb-3")
|
| 491 |
])
|
|
|
|
| 548 |
id="example-2", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 549 |
dbc.Button([html.I(className="fas fa-stream me-2"), "Stream"],
|
| 550 |
id="example-3", className="example-button me-2 mb-2", size="sm", color="light"),
|
|
|
|
|
|
|
| 551 |
dbc.Button([html.I(className="fas fa-explosion me-2"), "A violent merger"],
|
| 552 |
id="example-5", className="example-button me-2 mb-2", size="sm", color="light"),
|
| 553 |
dbc.Button([html.I(className="fas fa-moon me-2"), "Low surface brightness"],
|
|
|
|
| 557 |
], className="text-center")
|
| 558 |
], className="mb-3"),
|
| 559 |
|
| 560 |
+
# Basic search input (shown when NOT in advanced mode)
|
| 561 |
+
html.Div([
|
| 562 |
+
dbc.InputGroup([
|
| 563 |
+
dbc.InputGroupText(html.I(className="fas fa-search")),
|
| 564 |
+
dbc.Input(
|
| 565 |
+
id="search-input",
|
| 566 |
+
placeholder="Describe the galaxy you're looking for...",
|
| 567 |
+
type="text",
|
| 568 |
+
n_submit=0
|
| 569 |
+
),
|
| 570 |
+
dbc.Button("Search",
|
| 571 |
+
id="search-button", color="primary", n_clicks=0),
|
| 572 |
+
dbc.Button([
|
| 573 |
+
html.I(className="fas fa-download")
|
| 574 |
+
], id="download-button", color="secondary", n_clicks=0,
|
| 575 |
+
className="download-button", size="sm",
|
| 576 |
+
disabled=True)
|
| 577 |
+
])
|
| 578 |
+
], id="basic-search-bar"),
|
| 579 |
+
|
| 580 |
+
# Advanced search interface (shown when in advanced mode)
|
| 581 |
+
html.Div([
|
| 582 |
+
html.P("Combine multiple text and/or image queries using vector addition/subtraction:",
|
| 583 |
+
className="refinement-label mb-3"),
|
| 584 |
+
|
| 585 |
+
# Main query row
|
| 586 |
+
dbc.Row([
|
| 587 |
+
# Operation selector
|
| 588 |
+
dbc.Col([
|
| 589 |
+
dbc.Select(
|
| 590 |
+
id="main-vector-operation",
|
| 591 |
+
options=[
|
| 592 |
+
{"label": "+10", "value": "+10"},
|
| 593 |
+
{"label": "+5", "value": "+5"},
|
| 594 |
+
{"label": "+2", "value": "+2"},
|
| 595 |
+
{"label": "+", "value": "+"},
|
| 596 |
+
{"label": "-", "value": "-"},
|
| 597 |
+
{"label": "-2", "value": "-2"},
|
| 598 |
+
{"label": "-5", "value": "-5"},
|
| 599 |
+
{"label": "-10", "value": "-10"}
|
| 600 |
+
],
|
| 601 |
+
value="+",
|
| 602 |
+
style={"width": "70px"},
|
| 603 |
+
className="d-inline-block vector-operation-select"
|
| 604 |
+
)
|
| 605 |
+
], width=1),
|
| 606 |
+
# Query type selector
|
| 607 |
+
dbc.Col([
|
| 608 |
+
dbc.Select(
|
| 609 |
+
id="main-query-type",
|
| 610 |
+
options=[
|
| 611 |
+
{"label": "Text", "value": "text"},
|
| 612 |
+
{"label": "Image", "value": "image"}
|
| 613 |
+
],
|
| 614 |
+
value="text",
|
| 615 |
+
style={"width": "100px"},
|
| 616 |
+
className="d-inline-block vector-query-type-select"
|
| 617 |
+
)
|
| 618 |
+
], width=2),
|
| 619 |
+
# Input area
|
| 620 |
+
dbc.Col([
|
| 621 |
+
# Text input (shown when type is "text")
|
| 622 |
+
html.Div([
|
| 623 |
+
dbc.Input(
|
| 624 |
+
id="search-input-advanced",
|
| 625 |
+
placeholder="Enter text query...",
|
| 626 |
+
type="text"
|
| 627 |
+
)
|
| 628 |
+
], id="main-text-input-container", style={"display": "block"}),
|
| 629 |
+
# Image inputs (shown when type is "image")
|
| 630 |
+
html.Div([
|
| 631 |
+
dbc.Row([
|
| 632 |
+
dbc.Col([
|
| 633 |
+
dbc.Input(
|
| 634 |
+
id="main-vector-ra",
|
| 635 |
+
placeholder="RA:",
|
| 636 |
+
type="number",
|
| 637 |
+
step="any"
|
| 638 |
+
)
|
| 639 |
+
], width=6),
|
| 640 |
+
dbc.Col([
|
| 641 |
+
dbc.Input(
|
| 642 |
+
id="main-vector-dec",
|
| 643 |
+
placeholder="Dec:",
|
| 644 |
+
type="number",
|
| 645 |
+
step="any"
|
| 646 |
+
)
|
| 647 |
+
], width=6)
|
| 648 |
+
])
|
| 649 |
+
], id="main-image-input-container", style={"display": "none"})
|
| 650 |
+
], width=9)
|
| 651 |
+
], className="mb-2"),
|
| 652 |
+
|
| 653 |
+
# Additional queries container
|
| 654 |
+
html.Div(id="vector-inputs", children=[]),
|
| 655 |
+
|
| 656 |
+
# Buttons row
|
| 657 |
+
dbc.Row([
|
| 658 |
+
dbc.Col([
|
| 659 |
+
dbc.Button(
|
| 660 |
+
[html.I(className="fas fa-plus me-2"), "Add Query"],
|
| 661 |
+
id="add-vector-input",
|
| 662 |
+
color="secondary",
|
| 663 |
+
size="sm",
|
| 664 |
+
className="btn-add-vector"
|
| 665 |
+
)
|
| 666 |
+
], width=6),
|
| 667 |
+
dbc.Col([
|
| 668 |
+
dbc.Button(
|
| 669 |
+
"Search",
|
| 670 |
+
id="search-button-advanced",
|
| 671 |
+
className="btn-primary",
|
| 672 |
+
n_clicks=0,
|
| 673 |
+
style={"margin-right": "0.5rem"}
|
| 674 |
+
),
|
| 675 |
+
dbc.Button([
|
| 676 |
+
html.I(className="fas fa-download")
|
| 677 |
+
], id="download-button-advanced", color="secondary", n_clicks=0,
|
| 678 |
+
className="download-button", size="sm",
|
| 679 |
+
disabled=True)
|
| 680 |
+
], width=6, className="text-end")
|
| 681 |
+
], className="mt-3")
|
| 682 |
+
], id="advanced-search-interface", style={"display": "none"}),
|
| 683 |
+
|
| 684 |
+
# Toggle button (below search bar)
|
| 685 |
+
html.Div([
|
| 686 |
dbc.Button([
|
| 687 |
+
html.I(className="fas fa-chevron-down me-2", id="vector-arrow"),
|
| 688 |
+
"Text + Image search with vector addition"
|
| 689 |
+
], id="vector-toggle", color="link", className="refinement-toggle",
|
| 690 |
+
style={"white-space": "nowrap", "padding": "0.5rem 0.75rem", "margin-top": "0.5rem"})
|
| 691 |
+
], className="d-flex")
|
| 692 |
], className="search-container", style={"position": "relative"}),
|
| 693 |
|
| 694 |
# r_mag filter
|
| 695 |
+
create_rmag_filter_panel()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
], width=12, lg=11, className="mx-auto")
|
| 697 |
], className="mb-3")
|
| 698 |
|
| 699 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 700 |
def create_vector_input_row(index: int, query_type: str = "text", ra: float = None, dec: float = None, fov: float = 0.025):
|
| 701 |
"""Create a single vector input row with operation selector, query type toggle, and conditional inputs.
|
| 702 |
|
|
|
|
| 763 |
dbc.Col([
|
| 764 |
dbc.Input(
|
| 765 |
id={"type": "vector-ra", "index": index},
|
| 766 |
+
placeholder="RA:",
|
| 767 |
type="number",
|
| 768 |
step="any",
|
| 769 |
value=ra
|
| 770 |
)
|
| 771 |
+
], width=6),
|
| 772 |
dbc.Col([
|
| 773 |
dbc.Input(
|
| 774 |
id={"type": "vector-dec", "index": index},
|
| 775 |
+
placeholder="Dec:",
|
| 776 |
type="number",
|
| 777 |
step="any",
|
| 778 |
value=dec
|
| 779 |
)
|
| 780 |
+
], width=6)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
])
|
| 782 |
], id={"type": "image-input-container", "index": index}, style=image_display)
|
| 783 |
], width=8),
|
|
|
|
| 840 |
return dbc.Modal([
|
| 841 |
dbc.ModalHeader(dbc.ModalTitle([html.I(className="fas fa-info-circle me-2"), "About Galaxy Search"])),
|
| 842 |
dbc.ModalBody([
|
| 843 |
+
html.P("This app searches ~19 million Legacy Survey galaxies with r-mag brighter than 20 magnitude. "
|
| 844 |
+
"These galaxy images were converted to a text-searchable space using AION-Search, which was trained on "
|
| 845 |
+
"~275k text descriptions of galaxy images generated by GPT-4.1-mini.",
|
| 846 |
+
style={"color": "rgba(245, 245, 247, 0.8)", "margin-bottom": "1rem", "font-size": "0.9rem"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
]),
|
| 848 |
dbc.ModalFooter(
|
| 849 |
dbc.Button("Close", id="close-info-modal", className="ms-auto")
|