Update app.py
Browse files
app.py
CHANGED
|
@@ -6,49 +6,48 @@ import plotly.graph_objects as go
|
|
| 6 |
import requests
|
| 7 |
import os
|
| 8 |
from datetime import datetime, timezone, timedelta
|
| 9 |
-
from
|
| 10 |
from typing import List, Optional
|
| 11 |
|
| 12 |
# --- Configuration & Model Loading ---
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
|
| 17 |
-
print("Loading
|
| 18 |
-
device = "
|
| 19 |
-
from transformers import TimesFm2_5ModelForPrediction
|
| 20 |
|
| 21 |
-
# Load
|
| 22 |
-
|
| 23 |
-
|
| 24 |
torch_dtype=torch.float32,
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
)
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
|
| 41 |
# --- AQI Logic ---
|
| 42 |
def pm25_to_aqi(pm25: float) -> float:
|
| 43 |
pm25 = max(pm25, 0)
|
| 44 |
breakpoints = [
|
| 45 |
-
(0.0, 12.0, 0, 50),
|
| 46 |
-
(
|
| 47 |
-
(
|
| 48 |
-
(55.5, 150.4, 151, 200),
|
| 49 |
-
(150.5, 250.4, 201, 300),
|
| 50 |
-
(250.5, 350.4, 301, 400),
|
| 51 |
-
(350.5, 500.4, 401, 500),
|
| 52 |
]
|
| 53 |
for lo, hi, aqi_lo, aqi_hi in breakpoints:
|
| 54 |
if pm25 <= hi:
|
|
@@ -56,196 +55,105 @@ def pm25_to_aqi(pm25: float) -> float:
|
|
| 56 |
return 500.0
|
| 57 |
|
| 58 |
AQI_BANDS = [
|
| 59 |
-
(0,
|
| 60 |
-
(51,
|
| 61 |
-
(101, 150, "Unhealthy for Sensitive Groups",
|
| 62 |
-
(151, 200, "Unhealthy",
|
| 63 |
-
(201, 300, "Very Unhealthy",
|
| 64 |
-
(301, 999, "Hazardous",
|
| 65 |
]
|
| 66 |
|
| 67 |
AQI_LEGEND_HTML = """
|
| 68 |
<div class="aqi-legend">
|
| 69 |
<h3>AQI Categories</h3>
|
| 70 |
<table>
|
| 71 |
-
<thead>
|
| 72 |
-
<tr>
|
| 73 |
-
<th>AQI Range</th>
|
| 74 |
-
<th>Category</th>
|
| 75 |
-
<th>Status</th>
|
| 76 |
-
</tr>
|
| 77 |
-
</thead>
|
| 78 |
<tbody>
|
| 79 |
"""
|
| 80 |
-
for lo, hi,
|
| 81 |
-
AQI_LEGEND_HTML += f""
|
| 82 |
-
<tr style="background-color: {bgcolor};">
|
| 83 |
-
<td>{lo} - {hi}</td>
|
| 84 |
-
<td>{category}</td>
|
| 85 |
-
<td style="background-color: {color}; width: 20px;"></td>
|
| 86 |
-
</tr>
|
| 87 |
-
"""
|
| 88 |
AQI_LEGEND_HTML += "</tbody></table></div>"
|
| 89 |
|
| 90 |
-
|
| 91 |
-
{"name": "US Consulate", "lat": 24.8494, "lon": 67.0111},
|
| 92 |
-
{"name": "Korangi", "lat": 24.8286, "lon": 67.1147},
|
| 93 |
-
{"name": "Gulshan-e-Iqbal", "lat": 24.9161, "lon": 67.0911},
|
| 94 |
-
]
|
| 95 |
-
|
| 96 |
-
def aqi_meta(value):
|
| 97 |
-
for lo, hi, cat, color, bg in AQI_BANDS:
|
| 98 |
-
if value <= hi: return cat, color, bg
|
| 99 |
-
return "Hazardous", "#922b21", "#f5b7b1"
|
| 100 |
-
|
| 101 |
-
# --- Data Fetching ---
|
| 102 |
API_KEY = os.getenv("OPENWEATHER_API_KEY", "")
|
| 103 |
KARACHI_LAT, KARACHI_LON = 24.8607, 67.0011
|
| 104 |
|
| 105 |
-
def
|
| 106 |
if not API_KEY:
|
| 107 |
-
return [
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
data = resp.json().get("list", [])
|
| 119 |
-
pm25_values = [entry.get("components", {}).get("pm2_5", 0.0) for entry in data]
|
| 120 |
-
return pm25_values[-hours:] if len(pm25_values) >= hours else pm25_values
|
| 121 |
-
|
| 122 |
-
def forecast_from_array(past_values: np.ndarray, horizon: int = 24) -> np.ndarray:
|
| 123 |
-
# Prepare input for TimesFM
|
| 124 |
-
inputs = torch.from_numpy(past_values).float().unsqueeze(0).unsqueeze(0)
|
| 125 |
with torch.no_grad():
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
forecast = outputs.point_forecast.squeeze().numpy()
|
| 129 |
-
return forecast
|
| 130 |
-
|
| 131 |
-
def get_real_forecast_data(hours: int = 24):
|
| 132 |
-
pm25_history = fetch_historical_pm25(KARACHI_LAT, KARACHI_LON, hours=48)
|
| 133 |
-
aqi_history = [pm25_to_aqi(v) for v in pm25_history]
|
| 134 |
-
history_arr = np.array(aqi_history[-24:])
|
| 135 |
-
forecast_arr = forecast_from_array(history_arr, hours)
|
| 136 |
-
return history_arr, forecast_arr, hours
|
| 137 |
-
|
| 138 |
-
# --- Visualizations ---
|
| 139 |
-
def make_forecast_plot(history, forecast, horizon):
|
| 140 |
-
fig = go.Figure()
|
| 141 |
-
fig.add_trace(go.Scatter(y=history, name="History", line=dict(color="blue")))
|
| 142 |
-
fig.add_trace(go.Scatter(x=list(range(len(history), len(history)+len(forecast))), y=forecast, name="Forecast", line=dict(color="red", dash="dash")))
|
| 143 |
-
fig.update_layout(title="AQI Forecast", template="plotly_white", height=400)
|
| 144 |
-
return fig
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
-
|
|
|
|
| 152 |
fig = go.Figure()
|
| 153 |
-
fig.add_trace(go.
|
| 154 |
-
fig.add_trace(go.
|
| 155 |
-
fig.update_layout(
|
| 156 |
return fig
|
| 157 |
|
| 158 |
-
def make_rate_of_change_plot(forecast):
|
| 159 |
-
roc = np.diff(forecast)
|
| 160 |
-
fig = go.Figure(go.Scatter(y=roc, mode='lines+markers', name="Rate of Change"))
|
| 161 |
-
fig.update_layout(title="Hourly AQI Change")
|
| 162 |
-
return fig
|
| 163 |
-
|
| 164 |
-
def make_station_map(current_forecast_mean: float = None):
|
| 165 |
-
# Mock map for demonstration
|
| 166 |
-
fig = go.Figure(go.Scattermapbox(lat=[s['lat'] for s in KARACHI_STATIONS], lon=[s['lon'] for s in KARACHI_STATIONS], mode='markers+text', marker=dict(size=15), text=[s['name'] for s in KARACHI_STATIONS]))
|
| 167 |
-
fig.update_layout(mapbox_style="carto-positron", mapbox_zoom=10, mapbox_center={"lat": KARACHI_LAT, "lon": KARACHI_LON})
|
| 168 |
-
return fig
|
| 169 |
-
|
| 170 |
-
def build_kpi_html(forecast, history):
|
| 171 |
-
current = forecast[0]
|
| 172 |
-
cat, color, bg = aqi_meta(current)
|
| 173 |
-
return f"<div style='background:{bg}; padding:20px; border-radius:10px; text-align:center;'><h2 style='color:{color};'>Current AQI: {current:.1f}</h2><h3>Category: {cat}</h3></div>"
|
| 174 |
-
|
| 175 |
-
# --- AI Advisor ---
|
| 176 |
-
def chat_with_advisor(user_message, chat_history, history_arr, forecast_arr, horizon):
|
| 177 |
-
if not os.getenv("ANTHROPIC_API_KEY"):
|
| 178 |
-
return chat_history + [[user_message, "Please set ANTHROPIC_API_KEY to use the advisor."]], ""
|
| 179 |
-
|
| 180 |
-
context = f"History: {history_arr.tolist()}\nForecast: {forecast_arr.tolist()}"
|
| 181 |
-
response = anthropic_client.messages.create(
|
| 182 |
-
model="claude-3-5-sonnet-20240620",
|
| 183 |
-
max_tokens=500,
|
| 184 |
-
system="You are an AQI health advisor for Karachi. Provide actionable advice based on the forecast.",
|
| 185 |
-
messages=[{"role": "user", "content": f"Context: {context}\nQuestion: {user_message}"}]
|
| 186 |
-
)
|
| 187 |
-
answer = response.content[0].text
|
| 188 |
-
chat_history.append((user_message, answer))
|
| 189 |
-
return chat_history, ""
|
| 190 |
-
|
| 191 |
# --- Gradio UI ---
|
| 192 |
-
CSS = ""
|
| 193 |
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 194 |
-
.aqi-legend table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
| 195 |
-
.aqi-legend th, .aqi-legend td { border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 0.8rem; }
|
| 196 |
-
.aqi-legend th { background-color: #f2f2f2; }
|
| 197 |
-
.sky-chat { border-radius: 10px; }
|
| 198 |
-
"""
|
| 199 |
|
| 200 |
-
def
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
return "Error loading data", None, None, None, None, None, [], []
|
| 213 |
-
|
| 214 |
-
with gr.Blocks(title="Skyloom – Live Karachi AQI Forecaster", css=CSS) as demo:
|
| 215 |
-
forecast_state = gr.State([])
|
| 216 |
-
history_state = gr.State([])
|
| 217 |
-
|
| 218 |
-
gr.HTML("<div style='text-align:center;'><h1>🌬️ Skyloom – Karachi AQI</h1><p>AI-Powered Forecasts</p></div>")
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
with gr.Row():
|
| 221 |
with gr.Column(scale=1):
|
| 222 |
-
horizon = gr.Slider(label="Forecast
|
| 223 |
-
|
| 224 |
-
with gr.Accordion("📘 AQI
|
| 225 |
gr.HTML(AQI_LEGEND_HTML)
|
| 226 |
|
| 227 |
with gr.Column(scale=2):
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
ALL_OUTPUTS = [kpi_html, plot_main, plot_heat, plot_dist, plot_roc if 'plot_roc' in locals() else plot_dist, plot_map, forecast_state, history_state]
|
| 240 |
-
|
| 241 |
-
# Fix ALL_OUTPUTS to match the refresh function
|
| 242 |
-
ALL_OUTPUTS = [kpi_html, plot_main, plot_heat, plot_dist, gr.State(), plot_map, forecast_state, history_state]
|
| 243 |
-
|
| 244 |
-
def on_refresh(hor):
|
| 245 |
-
return refresh_dashboard(hor)
|
| 246 |
-
|
| 247 |
-
refresh_btn.click(fn=on_refresh, inputs=horizon, outputs=[kpi_html, plot_main, plot_heat, plot_dist, gr.State(), plot_map, forecast_state, history_state])
|
| 248 |
-
send_btn.click(chat_with_advisor, inputs=[chat_input, chatbot, history_state, forecast_state, horizon], outputs=[chatbot, chat_input])
|
| 249 |
|
| 250 |
if __name__ == "__main__":
|
| 251 |
demo.launch()
|
|
|
|
| 6 |
import requests
|
| 7 |
import os
|
| 8 |
from datetime import datetime, timezone, timedelta
|
| 9 |
+
from transformers import TimesFm2_5ModelForPrediction, AutoModelForCausalLM, AutoTokenizer, pipeline
|
| 10 |
from typing import List, Optional
|
| 11 |
|
| 12 |
# --- Configuration & Model Loading ---
|
| 13 |
+
TIMESFM_MODEL_ID = "mahwizzzz/skyloom"
|
| 14 |
+
# Using a very lightweight open-source model for the advisor to fit in CPU RAM
|
| 15 |
+
ADVISOR_MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
|
| 16 |
|
| 17 |
+
print("Loading Models …")
|
| 18 |
+
device = "cpu" # Force CPU for Hugging Face Space stability
|
|
|
|
| 19 |
|
| 20 |
+
# 1. Load TimesFM (Forecaster)
|
| 21 |
+
forecaster_model = TimesFm2_5ModelForPrediction.from_pretrained(
|
| 22 |
+
TIMESFM_MODEL_ID,
|
| 23 |
torch_dtype=torch.float32,
|
| 24 |
+
)
|
| 25 |
+
print("Quantizing Forecaster...")
|
| 26 |
+
forecaster_model = torch.quantization.quantize_dynamic(
|
| 27 |
+
forecaster_model, {torch.nn.Linear}, dtype=torch.qint8
|
| 28 |
)
|
| 29 |
|
| 30 |
+
# 2. Load SmolLM2 (Advisor - Open Source & Tiny)
|
| 31 |
+
print("Loading Advisor (SmolLM2)...")
|
| 32 |
+
advisor_tokenizer = AutoTokenizer.from_pretrained(ADVISOR_MODEL_ID)
|
| 33 |
+
advisor_model = AutoModelForCausalLM.from_pretrained(
|
| 34 |
+
ADVISOR_MODEL_ID,
|
| 35 |
+
torch_dtype=torch.float32,
|
| 36 |
+
)
|
| 37 |
+
# Quantize the advisor model too for speed
|
| 38 |
+
advisor_model = torch.quantization.quantize_dynamic(
|
| 39 |
+
advisor_model, {torch.nn.Linear}, dtype=torch.qint8
|
| 40 |
+
)
|
| 41 |
|
| 42 |
+
print("Models ready on CPU")
|
| 43 |
|
| 44 |
# --- AQI Logic ---
|
| 45 |
def pm25_to_aqi(pm25: float) -> float:
|
| 46 |
pm25 = max(pm25, 0)
|
| 47 |
breakpoints = [
|
| 48 |
+
(0.0, 12.0, 0, 50), (12.1, 35.4, 51, 100), (35.5, 55.4, 101, 150),
|
| 49 |
+
(55.5, 150.4, 151, 200), (150.5, 250.4, 201, 300), (250.5, 350.4, 301, 400),
|
| 50 |
+
(350.5, 500.4, 401, 500)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
]
|
| 52 |
for lo, hi, aqi_lo, aqi_hi in breakpoints:
|
| 53 |
if pm25 <= hi:
|
|
|
|
| 55 |
return 500.0
|
| 56 |
|
| 57 |
AQI_BANDS = [
|
| 58 |
+
(0, 50, "Good", "#2ecc71", "#d4efdf"),
|
| 59 |
+
(51, 100, "Moderate", "#f1c40f", "#fef9e7"),
|
| 60 |
+
(101, 150, "Unhealthy for Sensitive Groups", "#e67e22", "#fdebd0"),
|
| 61 |
+
(151, 200, "Unhealthy", "#e74c3c", "#fadbd8"),
|
| 62 |
+
(201, 300, "Very Unhealthy", "#8e44ad", "#e8daef"),
|
| 63 |
+
(301, 999, "Hazardous", "#922b21", "#f5b7b1"),
|
| 64 |
]
|
| 65 |
|
| 66 |
AQI_LEGEND_HTML = """
|
| 67 |
<div class="aqi-legend">
|
| 68 |
<h3>AQI Categories</h3>
|
| 69 |
<table>
|
| 70 |
+
<thead><tr><th>AQI</th><th>Category</th><th>Color</th></tr></thead>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
<tbody>
|
| 72 |
"""
|
| 73 |
+
for lo, hi, cat, color, bg in AQI_BANDS:
|
| 74 |
+
AQI_LEGEND_HTML += f"<tr style='background-color:{bg};'><td>{lo}-{hi}</td><td>{cat}</td><td style='background-color:{color};'></td></tr>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
AQI_LEGEND_HTML += "</tbody></table></div>"
|
| 76 |
|
| 77 |
+
# --- Data Fetching (Karachi) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
API_KEY = os.getenv("OPENWEATHER_API_KEY", "")
|
| 79 |
KARACHI_LAT, KARACHI_LON = 24.8607, 67.0011
|
| 80 |
|
| 81 |
+
def fetch_data(hours=24):
|
| 82 |
if not API_KEY:
|
| 83 |
+
return [30 + np.random.normal(0, 5) for _ in range(hours)]
|
| 84 |
+
url = f"http://api.openweathermap.org/data/2.5/air_pollution/history?lat={KARACHI_LAT}&lon={KARACHI_LON}&start={int((datetime.now()-timedelta(hours=hours)).timestamp())}&end={int(datetime.now().timestamp())}&appid={API_KEY}"
|
| 85 |
+
try:
|
| 86 |
+
r = requests.get(url).json()
|
| 87 |
+
return [e['components']['pm2_5'] for e in r['list']][-hours:]
|
| 88 |
+
except:
|
| 89 |
+
return [30 + np.random.normal(0, 5) for _ in range(hours)]
|
| 90 |
+
|
| 91 |
+
# --- Inference ---
|
| 92 |
+
def get_forecast(history, horizon=24):
|
| 93 |
+
inputs = torch.tensor(history).float().view(1, 1, -1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
with torch.no_grad():
|
| 95 |
+
out = forecaster_model(inputs, prediction_length=horizon)
|
| 96 |
+
return out.point_forecast.numpy().flatten()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
# --- Advisor Logic (Open Source) ---
|
| 99 |
+
def ask_advisor(msg, history, fc):
|
| 100 |
+
current_aqi = fc[0] if len(fc) > 0 else 0
|
| 101 |
+
prompt = f"<|im_start|>system\nYou are an AQI health advisor for Karachi. Current AQI is {current_aqi:.1f}. Give short, helpful health advice.<|im_end|>\n<|im_start|>user\n{msg}<|im_end|>\n<|im_start|>assistant\n"
|
| 102 |
+
|
| 103 |
+
inputs = advisor_tokenizer(prompt, return_tensors="pt")
|
| 104 |
+
with torch.no_grad():
|
| 105 |
+
outputs = advisor_model.generate(**inputs, max_new_tokens=100, do_sample=True, temperature=0.7)
|
| 106 |
+
|
| 107 |
+
answer = advisor_tokenizer.decode(outputs[0], skip_special_tokens=True).split("assistant\n")[-1]
|
| 108 |
+
history.append((msg, answer))
|
| 109 |
+
return history, ""
|
| 110 |
|
| 111 |
+
# --- Visuals ---
|
| 112 |
+
def make_plot(hist, fc):
|
| 113 |
fig = go.Figure()
|
| 114 |
+
fig.add_trace(go.Scatter(y=hist, name="History", line=dict(color="blue")))
|
| 115 |
+
fig.add_trace(go.Scatter(x=list(range(len(hist), len(hist)+len(fc))), y=fc, name="Forecast", line=dict(color="red", dash="dash")))
|
| 116 |
+
fig.update_layout(title="Karachi AQI Forecast", template="plotly_white", margin=dict(l=20, r=20, t=40, b=20))
|
| 117 |
return fig
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
# --- Gradio UI ---
|
| 120 |
+
CSS = ".aqi-legend table { width: 100%; border-collapse: collapse; } .aqi-legend td { padding: 5px; border: 1px solid #eee; }"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
+
def update_ui(horizon):
|
| 123 |
+
pm_hist = fetch_data(24)
|
| 124 |
+
aqi_hist = [pm25_to_aqi(p) for p in pm_hist]
|
| 125 |
+
fc = get_forecast(aqi_hist, horizon)
|
| 126 |
+
|
| 127 |
+
current_aqi = fc[0]
|
| 128 |
+
for lo, hi, cat, color, bg in AQI_BANDS:
|
| 129 |
+
if current_aqi <= hi:
|
| 130 |
+
status_html = f"<div style='background:{bg}; color:{color}; padding:15px; border-radius:10px; text-align:center;'><h2>Current: {current_aqi:.1f} ({cat})</h2></div>"
|
| 131 |
+
break
|
| 132 |
+
|
| 133 |
+
return status_html, make_plot(aqi_hist, fc), fc.tolist()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
with gr.Blocks(css=CSS, title="Skyloom Karachi") as demo:
|
| 136 |
+
fc_state = gr.State([])
|
| 137 |
+
gr.Markdown("# 🌬️ Skyloom Karachi\n*CPU-Optimized Open-Source AQI Forecaster*")
|
| 138 |
+
|
| 139 |
with gr.Row():
|
| 140 |
with gr.Column(scale=1):
|
| 141 |
+
horizon = gr.Slider(label="Forecast Hours", minimum=1, maximum=72, value=24)
|
| 142 |
+
btn = gr.Button("🔄 Predict Current", variant="primary")
|
| 143 |
+
with gr.Accordion("📘 AQI Legend", open=False):
|
| 144 |
gr.HTML(AQI_LEGEND_HTML)
|
| 145 |
|
| 146 |
with gr.Column(scale=2):
|
| 147 |
+
status = gr.HTML("Click Predict to start...")
|
| 148 |
+
plot = gr.Plot()
|
| 149 |
+
|
| 150 |
+
with gr.Tab("🤖 Open-Source Advisor"):
|
| 151 |
+
chatbot = gr.Chatbot(height=300)
|
| 152 |
+
msg_input = gr.Textbox(placeholder="Ask about health precautions...")
|
| 153 |
+
send = gr.Button("Ask Advisor")
|
| 154 |
+
|
| 155 |
+
btn.click(update_ui, inputs=horizon, outputs=[status, plot, fc_state])
|
| 156 |
+
send.click(ask_advisor, inputs=[msg_input, chatbot, fc_state], outputs=[chatbot, msg_input])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
if __name__ == "__main__":
|
| 159 |
demo.launch()
|