dgarzon/SQL-Explainer
0
1#!/usr/bin/env python32"""Streamlit interface for SQL Explainer Agent."""3 4from __future__ import annotations5 6import streamlit as st7 8from sql_explainer_core import (9 DEFAULT_MAX_NEW_TOKENS,10 DEFAULT_MODEL,11 MAX_SQL_CHARS,12 call_local_model,13 get_free_local_models,14 get_device_label,15)16 17 18EXAMPLE_SQL = """SELECT19 employee_id,20 first_name,21 last_name,22 department,23 salary24FROM employees25WHERE department = 'Sales'26 AND salary > 5000027ORDER BY salary DESC;"""28 29SQL_INPUT_HEIGHT = 36030EXPLANATION_PANEL_HEIGHT = 43031 32 33def initialize_state() -> None:34 """Initialize session state once so reruns keep the current values."""35 st.session_state.setdefault("sql_input", "")36 st.session_state.setdefault("explanation", "")37 st.session_state.setdefault("error_message", "")38 st.session_state.setdefault("local_model_id", DEFAULT_MODEL)39 st.session_state.setdefault("max_new_tokens", DEFAULT_MAX_NEW_TOKENS)40 41 42def render_sidebar() -> tuple[str, int]:43 """Draw the sidebar and return the selected settings."""44 with st.sidebar:45 st.header("Settings")46 model_options = list(get_free_local_models())47 current_model = st.session_state.local_model_id48 default_index = model_options.index(current_model) if current_model in model_options else 049 st.session_state.local_model_id = st.selectbox(50 "Local model",51 options=model_options,52 index=default_index,53 )54 55 with st.expander("Advanced"):56 st.number_input(57 "Max new tokens",58 min_value=120,59 max_value=600,60 step=20,61 key="max_new_tokens",62 )63 64 st.divider()65 if st.button("Load Example", use_container_width=True):66 st.session_state.sql_input = EXAMPLE_SQL67 if st.button("Clear", use_container_width=True):68 st.session_state.sql_input = ""69 st.session_state.explanation = ""70 st.session_state.error_message = ""71 72 st.caption(73 "Fully free mode: the model runs locally inside the Hugging Face Space. "74 "No token and no paid inference provider are used."75 )76 st.caption(77 "Only small local models are exposed here to keep the Space inside free CPU limits."78 )79 st.caption(f"Runtime device: {get_device_label()}")80 st.caption(f"Demo protection: SQL input is limited to {MAX_SQL_CHARS} characters.")81 82 return st.session_state.local_model_id, int(st.session_state.max_new_tokens)83 84 85def explain_sql(sql: str, model_id: str, max_new_tokens: int) -> None:86 """Run local generation and store either the result or the error."""87 if not sql.strip():88 st.session_state.error_message = (89 "Please paste a SQL query before running the explanation."90 )91 st.session_state.explanation = ""92 return93 94 try:95 with st.spinner("Loading the local model if needed and generating the explanation..."):96 st.session_state.explanation = call_local_model(97 sql,98 model_id=model_id,99 max_new_tokens=max_new_tokens,100 )101 st.session_state.error_message = ""102 except Exception as exc: # noqa: BLE001 - keep Streamlit errors simple for V1.103 st.session_state.error_message = str(exc)104 st.session_state.explanation = ""105 106 107def main() -> None:108 """Build the Streamlit interface and connect the UI to local inference."""109 st.set_page_config(page_title="SQL Explainer Agent", layout="wide")110 initialize_state()111 model_id, max_new_tokens = render_sidebar()112 113 st.title("SQL Explainer Agent")114 st.caption(115 "Paste a SQL query and get a practical explanation for analytics or business users "116 "using a local open model running inside this Space."117 )118 st.info(119 "This version is fully free to run on Hugging Face Spaces CPU Basic. "120 "The first request can be slow while the model is downloaded and loaded."121 )122 123 left_col, right_col = st.columns([1.05, 0.95], gap="large")124 125 with left_col:126 st.subheader("SQL Query")127 st.text_area(128 "Paste SQL",129 key="sql_input",130 height=SQL_INPUT_HEIGHT,131 label_visibility="collapsed",132 placeholder="SELECT * FROM your_table WHERE created_at >= CURRENT_DATE - INTERVAL '7 days';",133 )134 135 action_col, clear_col = st.columns(2)136 if action_col.button("Explain Query", type="primary", use_container_width=True):137 explain_sql(st.session_state.sql_input, model_id, max_new_tokens)138 if clear_col.button("Clear Result", use_container_width=True):139 st.session_state.explanation = ""140 st.session_state.error_message = ""141 142 with right_col:143 st.subheader("Explanation")144 with st.container(height=EXPLANATION_PANEL_HEIGHT):145 if st.session_state.error_message:146 st.error(st.session_state.error_message)147 elif st.session_state.explanation:148 st.markdown(st.session_state.explanation)149 else:150 st.info("Run the explanation to see the six-section output here.")151 152 153if __name__ == "__main__":154 main()155 