Spaces:
Sleeping
Sleeping
| # __version__ = '31.3.0 - MODULAR' | |
| import gradio as gr | |
| import json | |
| import sys | |
| import os | |
| # --- Assume these modules exist and are correct --- | |
| # You will need to create placeholder files for these to run the app | |
| # For example, create an empty config.py | |
| # --- Local Module Imports --- | |
| from config import ( | |
| COUNTY_CONFIG, APP_HEAD_HTML, JS_MAP_CAPTURE_FUNCTION, APP_CSS, GIS_SCRIPT_PATH | |
| ) | |
| from core_logic import run_gis_and_gemini_query | |
| from api_clients import AVAILABLE_BASEMAPS | |
| from map_utils import handle_map_type_change | |
| from image_utils import load_captured_image, process_fade_with_drop | |
| # --- Hugging Face Secrets Integration --- | |
| # Attempt to get the Gemini API key from Hugging Face secrets (environment variables) | |
| # If the secret is set, it will be used; otherwise, it will be None. | |
| gemini_api_key_from_env = os.environ.get("GEMINI_API_KEY") | |
| # --- UI Helper/Callback Functions (Tightly coupled to the Gradio UI) --- | |
| def populate_products_tab(products_json: str): | |
| """ | |
| Callback function to populate the USGS Products tab when it's selected. | |
| It reads the JSON data of available products and dynamically updates the UI elements. | |
| """ | |
| if not products_json: | |
| return { | |
| products_instructions: gr.update(visible=True, value="Run a query first to see available USGS data products..."), | |
| products_results_group: gr.update(visible=False) | |
| } | |
| all_products = json.loads(products_json) | |
| if not all_products: | |
| return { | |
| products_instructions: gr.update(visible=True, value="**No USGS data products found for this location.**"), | |
| products_results_group: gr.update(visible=False) | |
| } | |
| dataset_choices = sorted(list(all_products.keys())) | |
| first_type = dataset_choices[0] if dataset_choices else None | |
| products_for_type = all_products.get(first_type, []) | |
| item_choices = [p['title'] for p in products_for_type] | |
| first_item = item_choices[0] if item_choices else None | |
| preview_val, link_val = product_details_fn(first_item, products_json) | |
| return { | |
| products_instructions: gr.update(visible=False), | |
| products_results_group: gr.update(visible=True), | |
| product_type_select: gr.update(choices=dataset_choices, value=first_type), | |
| product_item_select: gr.update(choices=item_choices, value=first_item), | |
| product_preview: gr.update(value=preview_val), | |
| product_download: gr.update(value=link_val) | |
| } | |
| def product_list_fn(value, all_products_data_str): | |
| """Updates the product item dropdown when a product type is selected.""" | |
| if not value or not all_products_data_str: return gr.update(choices=[], value=None) | |
| all_products = json.loads(all_products_data_str) | |
| products_for_type = all_products.get(value, []) | |
| new_choices = [p['title'] for p in products_for_type] | |
| return gr.update(choices=new_choices, value=new_choices[0] if new_choices else None) | |
| def product_details_fn(value, all_products_data_str): | |
| """Updates the preview image and download link when a specific product is selected.""" | |
| if not value or not all_products_data_str: return None, "" | |
| all_products = json.loads(all_products_data_str) | |
| if found_product := next((p for prod_list in all_products.values() for p in prod_list if p['title'] == value), None): | |
| dl_url = found_product.get('download') | |
| link = f"**Download Link:** [{dl_url.split('/')[-1]}]({dl_url})" if dl_url else "Download not available." | |
| return found_product.get('preview'), link | |
| return None, "" | |
| def display_diagnostics(json_string): | |
| """Parses and displays the JSON string from the frontend diagnostics tool.""" | |
| if not json_string: return {"status": "No data received from frontend."} | |
| try: return json.loads(json_string) | |
| except json.JSONDecodeError: return {"status": "ERROR", "details": "Failed to parse JSON from frontend.", "raw_data": json_string} | |
| # --- Gradio UI Definition --- | |
| # Define a dark theme for the application | |
| dark_theme = gr.themes.Default(primary_hue="blue").set(body_background_fill="#09090b", body_text_color="#e5e7eb", background_fill_primary="#18181b", background_fill_secondary="#27272a", border_color_accent="#3f3f46", border_color_primary="#3f3f46", button_primary_background_fill="#6366f1", button_primary_text_color="#ffffff") | |
| with gr.Blocks(theme=dark_theme, title="Anamnesis-GIS", css=APP_CSS, head=APP_HEAD_HTML) as iface: | |
| # Set up the order of basemaps for the radio buttons | |
| preferred_order = ["USGS Topo", "USGS ImageryOnly", "USGS Shaded Relief"] | |
| all_basemap_keys = list(AVAILABLE_BASEMAPS.keys()) | |
| ordered_basemap_choices = [p for p in preferred_order if p in all_basemap_keys] | |
| ordered_basemap_choices.extend([k for k in all_basemap_keys if k not in ordered_basemap_choices]) | |
| gr.Markdown("# 🏛️ Anamnesis-GIS: Multi-County Query Engine with AI Insight & Mapping 🗺️") | |
| with gr.Group(): | |
| county_select = gr.Dropdown(label="Select County Lattice", choices=list(COUNTY_CONFIG.keys()), value="Kitsap County (Blueprint)") | |
| address_input = gr.Textbox(label="Enter Address or Parcel ID", placeholder=COUNTY_CONFIG["Kitsap County (Blueprint)"]["example_query"]) | |
| # --- MODIFIED FOR HUGGING FACE SECRETS --- | |
| # This Textbox will be populated by the environment variable if it exists. | |
| # It will be hidden if the key is provided by the environment, | |
| # but will remain visible for local development if the key is not set. | |
| api_key_input = gr.Textbox( | |
| label="Gemini API Key (for text report)", | |
| type="password", | |
| placeholder="Enter key starting with 'AIza...' or set in HF Secrets", | |
| value=gemini_api_key_from_env, | |
| visible=gemini_api_key_from_env is None | |
| ) | |
| submit_btn = gr.Button("Submit Query", variant="primary") | |
| gr.Markdown("### 🗺️ Parcel Map") | |
| map_type_radio = gr.Radio(choices=ordered_basemap_choices, value="USGS Topo", label="Map Type", type="value", interactive=True, show_label=False, elem_id="map-type-radio") | |
| map_view = gr.HTML(elem_id="map-view", show_label=False, value="<div style='width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#1f2937;color:#6b7280;'>Run a query to display the map.</div>", elem_classes=["map-container-wrapper"]) | |
| with gr.Tabs(): | |
| with gr.Tab("🧠 AI Analysis & Raw Data"): | |
| report_output = gr.Markdown("*Awaiting query...*") | |
| with gr.Accordion("Show Raw Data & Search Vector", open=False): | |
| vector_output = gr.Textbox(label="Original LLM Search Vector", interactive=False) | |
| json_output = gr.Json(label="Anamnesis-GIS Query Result (JSON)") | |
| with gr.Tab("USGS Data Products") as products_tab: | |
| products_instructions = gr.Markdown("Run a query first to see available USGS data products...") | |
| with gr.Group(visible=False) as products_results_group: | |
| with gr.Row(): | |
| product_type_select = gr.Dropdown(label="Select Dataset Type", interactive=True) | |
| product_item_select = gr.Dropdown(label="Select Specific Product", interactive=True) | |
| product_preview = gr.Image(label="Product Preview", type="filepath", interactive=False, show_label=False) | |
| product_download = gr.Markdown() | |
| with gr.Tab("🎨 Map Style Editor"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 1. Source Map") | |
| load_button = gr.Button("⬇️ Load High-Res Map into Editor", variant="primary") | |
| source_image_component = gr.Image(label="Captured Source Image", type="pil", interactive=False, height=450) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 2. Effect Controls") | |
| with gr.Accordion("Controls", open=True): | |
| gr.Markdown("**Preserve Color (e.g., Topo Lines)** - Pulls this color from white.") | |
| preserve_picker = gr.ColorPicker(label="Preserve Color", value="#B5A89A") | |
| falloff_slider = gr.Slider(1, 300, value=94, label="Preserve Falloff / Feathering") | |
| gr.Markdown("---") | |
| gr.Markdown("**Drop Color (e.g., Roads)** - Pushes this color towards white.") | |
| drop_picker = gr.ColorPicker(label="Drop Color", value="#6E6E6E") | |
| drop_slider = gr.Slider(0, 10, value=10, label="Drop Strength") | |
| gr.Markdown("---") | |
| gr.Markdown("**Artistic Overlays**") | |
| min_fade_slider = gr.Slider(0, 100, value=96, label="Base Whitening (%)") | |
| recolor_picker = gr.ColorPicker(label="Recolor To", value="#FF1493") | |
| recolor_slider = gr.Slider(0, 100, value=37, label="Recolor Strength (%)") | |
| gr.Markdown("### 3. Processed Output") | |
| output_image_component = gr.Image(interactive=False, type="pil", label="Styled Map Output", height=450, show_download_button=True) | |
| with gr.Tab("🛠️ Diagnostics"): | |
| gr.Markdown("### Frontend Diagnostic Tool\nThis tool helps debug issues with the map capture functionality. After running a query to load the map, click the button below.") | |
| diagnostic_btn = gr.Button("🔬 Run Frontend Diagnostics") | |
| diagnostic_display = gr.JSON(label="Diagnostic Results") | |
| diagnostic_output = gr.Textbox(label="Raw Diagnostic Data", visible=False, elem_id="diagnostic_output") | |
| # --- Hidden State & Data Components --- | |
| map_state = gr.Textbox(visible=False) # Stores the GeoJSON of the parcel | |
| all_products_data = gr.Textbox(visible=False) # Stores the JSON of USGS products | |
| captured_map_data = gr.Textbox(visible=False) # Stores the base64 string of the captured map image | |
| map_init_script = gr.Textbox(visible=False, label="Map Initializer Script") # Stores the JS to render/update the map | |
| with gr.Accordion("Examples", open=False): | |
| # The example for the API key is now an empty string as it will be handled by the secret | |
| gr.Examples(examples=[[c, v["example_query"], ""] for c, v in COUNTY_CONFIG.items()], inputs=[county_select, address_input, api_key_input], label=None) | |
| # --- Event Wiring --- | |
| # Modern Gradio (4.x) uses a 'js' parameter to run JavaScript after a Python function completes. | |
| run_script_js = "(script) => { if (script) { eval(script); } }" | |
| outputs_list = [vector_output, report_output, json_output, map_view, map_state, all_products_data, map_type_radio, map_init_script] | |
| submit_click_event = submit_btn.click( | |
| fn=run_gis_and_gemini_query, | |
| inputs=[county_select, address_input, api_key_input], | |
| outputs=outputs_list | |
| ) | |
| submit_click_event.then(fn=None, inputs=[map_init_script], outputs=None, js=run_script_js) | |
| map_type_change_event = map_type_radio.change( | |
| fn=handle_map_type_change, | |
| inputs=[map_type_radio, map_state], | |
| outputs=[map_view, map_init_script] | |
| ) | |
| map_type_change_event.then(fn=None, inputs=[map_init_script], outputs=None, js=run_script_js) | |
| products_tab.select(fn=populate_products_tab, inputs=[all_products_data], outputs=[products_instructions, products_results_group, product_type_select, product_item_select, product_preview, product_download]) | |
| product_type_select.change(fn=product_list_fn, inputs=[product_type_select, all_products_data], outputs=[product_item_select]).then(fn=product_details_fn, inputs=[product_item_select, all_products_data], outputs=[product_preview, product_download]) | |
| product_item_select.change(fn=product_details_fn, inputs=[product_item_select, all_products_data], outputs=[product_preview, product_download]) | |
| # The 'js' parameter can also be used directly on an event listener to run client-side JS without a backend round-trip. | |
| load_button.click(fn=None, inputs=None, outputs=[captured_map_data], js=JS_MAP_CAPTURE_FUNCTION) | |
| captured_map_data.change(fn=load_captured_image, inputs=captured_map_data, outputs=source_image_component) | |
| editor_controls = [source_image_component, preserve_picker, falloff_slider, drop_picker, drop_slider, min_fade_slider, recolor_picker, recolor_slider] | |
| for control in editor_controls: | |
| control.change(fn=process_fade_with_drop, inputs=editor_controls, outputs=output_image_component) | |
| diagnostic_btn.click(fn=None, inputs=None, outputs=None, js="() => { runDiagnostics('#map-view', 'diagnostic_output'); }") | |
| diagnostic_output.change(fn=display_diagnostics, inputs=[diagnostic_output], outputs=[diagnostic_display]) | |
| if __name__ == "__main__": | |
| # A failsafe to ensure the master query script exists before launching. | |
| if not os.path.exists(GIS_SCRIPT_PATH): | |
| # Create a dummy file for demonstration purposes if it doesn't exist | |
| print(f"WARNING: Master query script '{GIS_SCRIPT_PATH}' not found. Creating a dummy file.") | |
| with open(GIS_SCRIPT_PATH, "w") as f: | |
| f.write("# Dummy GIS Script") | |
| # Launch the Gradio interface. `share=False` is safer for local development. | |
| # `allowed_paths` is necessary to serve local JS files like `diagnose.js`. | |
| iface.launch(share=False, allowed_paths=["."]) |