Rick commited on
Commit
35e029d
·
1 Parent(s): d8decda

HF demo: block chat submissions and guide local edge install; add optional virgin-system runtime config

Browse files
README.md CHANGED
@@ -85,6 +85,25 @@ chmod +x scripts/install_fresh_copy.sh
85
  ./scripts/install_fresh_copy.sh --skip-clone
86
  ```
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  For full instructions and troubleshooting, see `docs/FRESH_INSTALL.md`.
89
 
90
  You can re-run the deterministic installation verification at any time:
@@ -100,6 +119,18 @@ chmod +x scripts/bootstrap_ubuntu24_sailingmedadvisor.sh
100
  ./scripts/bootstrap_ubuntu24_sailingmedadvisor.sh
101
  ```
102
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  ## Demo Reproduction (27B scenario)
104
 
105
  For the Kaggle demo scenario, use the 27B model path in the UI:
 
85
  ./scripts/install_fresh_copy.sh --skip-clone
86
  ```
87
 
88
+ Optional: predownload MedGemma model files during install:
89
+
90
+ ```bash
91
+ ./scripts/install_fresh_copy.sh --skip-clone --download-models --hf-token <YOUR_HF_TOKEN>
92
+ ```
93
+
94
+ Optional: explicitly configure runtime defaults for this machine during install:
95
+
96
+ ```bash
97
+ ./scripts/install_fresh_copy.sh --skip-clone --configure-system
98
+ ```
99
+
100
+ This writes `sailingmed.local.env`, which `run_med_advisor.sh` loads automatically at startup.
101
+
102
+ Notes:
103
+ - Model downloads are large (multi-GB; 27B is significantly larger).
104
+ - Your Hugging Face token must have access to the MedGemma repositories.
105
+ - You can also download later from `Settings -> Offline Readiness Check`.
106
+
107
  For full instructions and troubleshooting, see `docs/FRESH_INSTALL.md`.
108
 
109
  You can re-run the deterministic installation verification at any time:
 
119
  ./scripts/bootstrap_ubuntu24_sailingmedadvisor.sh
120
  ```
121
 
122
+ With optional model predownload:
123
+
124
+ ```bash
125
+ ./scripts/bootstrap_ubuntu24_sailingmedadvisor.sh --download-models --hf-token <YOUR_HF_TOKEN>
126
+ ```
127
+
128
+ With explicit machine configuration during bootstrap:
129
+
130
+ ```bash
131
+ ./scripts/bootstrap_ubuntu24_sailingmedadvisor.sh --configure-system
132
+ ```
133
+
134
  ## Demo Reproduction (27B scenario)
135
 
136
  For the Kaggle demo scenario, use the 27B model path in the UI:
app.py CHANGED
@@ -181,6 +181,17 @@ def _env_bool(name: str, default: bool = False) -> bool:
181
  DISABLE_LOCAL_INFERENCE = _env_bool("DISABLE_LOCAL_INFERENCE", False)
182
  # Optional override: force remote inference only when on HF runtime.
183
  FORCE_REMOTE_INFERENCE_ON_HF = _env_bool("FORCE_REMOTE_INFERENCE_ON_HF", False)
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  # Gemma3 masking patch for torch<2.6 (required when token_type_ids are present).
186
  def _torch_version_ge(major: int, minor: int) -> bool:
@@ -266,6 +277,7 @@ _dbg(
266
  f"IS_HF_SPACE={IS_HF_SPACE}",
267
  f"DISABLE_LOCAL_INFERENCE={DISABLE_LOCAL_INFERENCE}",
268
  f"FORCE_REMOTE_INFERENCE_ON_HF={FORCE_REMOTE_INFERENCE_ON_HF}",
 
269
  f"AUTO_DOWNLOAD_MODELS={AUTO_DOWNLOAD_MODELS}",
270
  f"VERIFY_MODELS_ON_START={VERIFY_MODELS_ON_START}",
271
  f"AUTO_VERIFY_ONLINE={AUTO_VERIFY_ONLINE}",
@@ -468,6 +480,14 @@ def _use_remote_inference(request: Optional[Request] = None) -> bool:
468
  return False
469
 
470
 
 
 
 
 
 
 
 
 
471
  def _is_valid_sqlite(path: Path) -> bool:
472
  """
473
  Is Valid Sqlite helper.
@@ -635,6 +655,7 @@ async def _log_db_path():
635
  db_path=DB_PATH.resolve(),
636
  runtime_log_path=RUNTIME_LOG_PATH.resolve(),
637
  is_hf_space=IS_HF_SPACE,
 
638
  disable_local_inference=DISABLE_LOCAL_INFERENCE,
639
  force_remote_inference_on_hf=FORCE_REMOTE_INFERENCE_ON_HF,
640
  remote_timeout_s=HF_REMOTE_TIMEOUT_SECONDS,
@@ -2539,6 +2560,10 @@ async def index(request: Request):
2539
  "store": store,
2540
  "vessel_prefill": vessel_prefill,
2541
  "use_splash_purple_tabbar": USE_SPLASH_PURPLE_TABBAR,
 
 
 
 
2542
  },
2543
  )
2544
 
@@ -3899,6 +3924,24 @@ async def chat(request: Request, _=Depends(require_auth)):
3899
  if not msg:
3900
  _runtime_log("chat.request.invalid", level=logging.WARNING, trace_id=trace_id, reason="empty_message")
3901
  return JSONResponse({"error": "Message is required."}, status_code=status.HTTP_400_BAD_REQUEST)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3902
  user_msg_raw = msg
3903
  p_name = form.get("patient") or ""
3904
  mode = (form.get("mode") or "triage").strip()
 
181
  DISABLE_LOCAL_INFERENCE = _env_bool("DISABLE_LOCAL_INFERENCE", False)
182
  # Optional override: force remote inference only when on HF runtime.
183
  FORCE_REMOTE_INFERENCE_ON_HF = _env_bool("FORCE_REMOTE_INFERENCE_ON_HF", False)
184
+ # Public demo mode: disable live MedGemma chat submissions in hosted demo UI.
185
+ PUBLIC_DEMO_DISABLE_CHAT = _env_bool("PUBLIC_DEMO_DISABLE_CHAT", IS_HF_SPACE)
186
+ PUBLIC_DEMO_REPO_URL = (
187
+ os.environ.get("PUBLIC_DEMO_REPO_URL")
188
+ or "https://github.com/rickeae/SailingMedAdvisor"
189
+ ).strip()
190
+ HF_DEMO_CHAT_DISABLED_MESSAGE = (
191
+ "This Hugging Face-hosted build is a workflow demo for an edge deployment. "
192
+ "MedGemma triage/inquiry execution is not available in this hosted version. "
193
+ "Install SailingMedAdvisor locally to run full consultations."
194
+ )
195
 
196
  # Gemma3 masking patch for torch<2.6 (required when token_type_ids are present).
197
  def _torch_version_ge(major: int, minor: int) -> bool:
 
277
  f"IS_HF_SPACE={IS_HF_SPACE}",
278
  f"DISABLE_LOCAL_INFERENCE={DISABLE_LOCAL_INFERENCE}",
279
  f"FORCE_REMOTE_INFERENCE_ON_HF={FORCE_REMOTE_INFERENCE_ON_HF}",
280
+ f"PUBLIC_DEMO_DISABLE_CHAT={PUBLIC_DEMO_DISABLE_CHAT}",
281
  f"AUTO_DOWNLOAD_MODELS={AUTO_DOWNLOAD_MODELS}",
282
  f"VERIFY_MODELS_ON_START={VERIFY_MODELS_ON_START}",
283
  f"AUTO_VERIFY_ONLINE={AUTO_VERIFY_ONLINE}",
 
480
  return False
481
 
482
 
483
+ def _chat_disabled_in_hosted_demo(request: Optional[Request] = None) -> bool:
484
+ """
485
+ Disable chat execution for hosted demo contexts.
486
+ This keeps HF demos focused on workflow while preserving full local edge behavior.
487
+ """
488
+ return bool(PUBLIC_DEMO_DISABLE_CHAT or _request_is_hf_runtime(request))
489
+
490
+
491
  def _is_valid_sqlite(path: Path) -> bool:
492
  """
493
  Is Valid Sqlite helper.
 
655
  db_path=DB_PATH.resolve(),
656
  runtime_log_path=RUNTIME_LOG_PATH.resolve(),
657
  is_hf_space=IS_HF_SPACE,
658
+ public_demo_disable_chat=PUBLIC_DEMO_DISABLE_CHAT,
659
  disable_local_inference=DISABLE_LOCAL_INFERENCE,
660
  force_remote_inference_on_hf=FORCE_REMOTE_INFERENCE_ON_HF,
661
  remote_timeout_s=HF_REMOTE_TIMEOUT_SECONDS,
 
2560
  "store": store,
2561
  "vessel_prefill": vessel_prefill,
2562
  "use_splash_purple_tabbar": USE_SPLASH_PURPLE_TABBAR,
2563
+ "is_hf_space": IS_HF_SPACE,
2564
+ "disable_local_inference": DISABLE_LOCAL_INFERENCE,
2565
+ "public_demo_disable_chat": PUBLIC_DEMO_DISABLE_CHAT,
2566
+ "public_demo_repo_url": PUBLIC_DEMO_REPO_URL,
2567
  },
2568
  )
2569
 
 
3924
  if not msg:
3925
  _runtime_log("chat.request.invalid", level=logging.WARNING, trace_id=trace_id, reason="empty_message")
3926
  return JSONResponse({"error": "Message is required."}, status_code=status.HTTP_400_BAD_REQUEST)
3927
+ if _chat_disabled_in_hosted_demo(request):
3928
+ _runtime_log(
3929
+ "chat.request.rejected",
3930
+ level=logging.INFO,
3931
+ trace_id=trace_id,
3932
+ reason="hosted_demo_chat_disabled",
3933
+ is_hf_runtime=_request_is_hf_runtime(request),
3934
+ public_demo_disable_chat=PUBLIC_DEMO_DISABLE_CHAT,
3935
+ )
3936
+ return JSONResponse(
3937
+ {
3938
+ "error": HF_DEMO_CHAT_DISABLED_MESSAGE,
3939
+ "public_demo_disabled": True,
3940
+ "is_hf_runtime": _request_is_hf_runtime(request),
3941
+ "repo_url": PUBLIC_DEMO_REPO_URL,
3942
+ },
3943
+ status_code=status.HTTP_403_FORBIDDEN,
3944
+ )
3945
  user_msg_raw = msg
3946
  p_name = form.get("patient") or ""
3947
  mode = (form.get("mode") or "triage").strip()
run_med_advisor.sh CHANGED
@@ -24,6 +24,18 @@ fi
24
  # Activate virtual environment
25
  source .venv/bin/activate
26
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # Check if required packages are installed
28
  python3 -c "import fastapi, uvicorn" 2>/dev/null || {
29
  echo "❌ Error: FastAPI or Uvicorn not installed. Install with: pip install fastapi uvicorn[standard]"
 
24
  # Activate virtual environment
25
  source .venv/bin/activate
26
 
27
+ # Optional local runtime overrides generated during fresh install.
28
+ # This lets us configure a new machine once and keep startup consistent.
29
+ LOCAL_ENV_FILE="${SAILINGMED_LOCAL_ENV:-./sailingmed.local.env}"
30
+ if [ -f "$LOCAL_ENV_FILE" ]; then
31
+ echo "🔧 Loading local runtime config from $LOCAL_ENV_FILE"
32
+ # Export sourced vars so child processes (uvicorn/app) receive them.
33
+ set -a
34
+ # shellcheck disable=SC1090
35
+ source "$LOCAL_ENV_FILE"
36
+ set +a
37
+ fi
38
+
39
  # Check if required packages are installed
40
  python3 -c "import fastapi, uvicorn" 2>/dev/null || {
41
  echo "❌ Error: FastAPI or Uvicorn not installed. Install with: pip install fastapi uvicorn[standard]"
scripts/bootstrap_ubuntu24_sailingmedadvisor.sh CHANGED
@@ -28,6 +28,10 @@
28
  # --start Start app after verification
29
  # --prefer-gpu-start Prefer GPU settings when starting app (default is CPU-safe start)
30
  # --force-cuda <0|1> Set FORCE_CUDA explicitly when starting app
 
 
 
 
31
  # --help Show usage
32
 
33
  set -euo pipefail
@@ -39,6 +43,9 @@ SKIP_SYSTEM_PACKAGES="0"
39
  START_APP="0"
40
  PREFER_GPU_START="0"
41
  FORCE_CUDA_OVERRIDE=""
 
 
 
42
 
43
  usage() {
44
  cat <<'EOF'
@@ -52,6 +59,10 @@ Flags:
52
  --start Start app after successful verification
53
  --prefer-gpu-start Prefer GPU startup flags (default start is CPU-safe)
54
  --force-cuda <0|1> Force FORCE_CUDA value when starting app
 
 
 
 
55
  --help Show this help text
56
  EOF
57
  }
@@ -72,6 +83,14 @@ while [[ $# -gt 0 ]]; do
72
  PREFER_GPU_START="1"; shift ;;
73
  --force-cuda)
74
  FORCE_CUDA_OVERRIDE="$2"; shift 2 ;;
 
 
 
 
 
 
 
 
75
  --help|-h)
76
  usage; exit 0 ;;
77
  *)
@@ -128,7 +147,19 @@ install_project() {
128
  echo "[step] Running project installer"
129
  cd "$TARGET_DIR"
130
  chmod +x scripts/install_fresh_copy.sh
131
- ./scripts/install_fresh_copy.sh --skip-clone
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
  verify_project() {
 
28
  # --start Start app after verification
29
  # --prefer-gpu-start Prefer GPU settings when starting app (default is CPU-safe start)
30
  # --force-cuda <0|1> Set FORCE_CUDA explicitly when starting app
31
+ # --download-models Download MedGemma model files during install (requires HF token)
32
+ # --hf-token <token> Hugging Face token used for optional model download
33
+ # --configure-system Configure local runtime defaults during install
34
+ # --skip-configure Skip local runtime configuration during install
35
  # --help Show usage
36
 
37
  set -euo pipefail
 
43
  START_APP="0"
44
  PREFER_GPU_START="0"
45
  FORCE_CUDA_OVERRIDE=""
46
+ DOWNLOAD_MODELS="0"
47
+ HF_TOKEN_INPUT=""
48
+ CONFIGURE_VIRGIN_SYSTEM="ask"
49
 
50
  usage() {
51
  cat <<'EOF'
 
59
  --start Start app after successful verification
60
  --prefer-gpu-start Prefer GPU startup flags (default start is CPU-safe)
61
  --force-cuda <0|1> Force FORCE_CUDA value when starting app
62
+ --download-models Download MedGemma model files during install (requires HF token)
63
+ --hf-token <token> Hugging Face token used for optional model download
64
+ --configure-system Configure local runtime defaults during install
65
+ --skip-configure Skip local runtime configuration during install
66
  --help Show this help text
67
  EOF
68
  }
 
83
  PREFER_GPU_START="1"; shift ;;
84
  --force-cuda)
85
  FORCE_CUDA_OVERRIDE="$2"; shift 2 ;;
86
+ --download-models)
87
+ DOWNLOAD_MODELS="1"; shift ;;
88
+ --hf-token)
89
+ HF_TOKEN_INPUT="$2"; shift 2 ;;
90
+ --configure-system)
91
+ CONFIGURE_VIRGIN_SYSTEM="1"; shift ;;
92
+ --skip-configure)
93
+ CONFIGURE_VIRGIN_SYSTEM="0"; shift ;;
94
  --help|-h)
95
  usage; exit 0 ;;
96
  *)
 
147
  echo "[step] Running project installer"
148
  cd "$TARGET_DIR"
149
  chmod +x scripts/install_fresh_copy.sh
150
+ local install_args=(--skip-clone)
151
+ if [[ "$DOWNLOAD_MODELS" == "1" ]]; then
152
+ install_args+=(--download-models)
153
+ fi
154
+ if [[ -n "$HF_TOKEN_INPUT" ]]; then
155
+ install_args+=(--hf-token "$HF_TOKEN_INPUT")
156
+ fi
157
+ if [[ "$CONFIGURE_VIRGIN_SYSTEM" == "1" ]]; then
158
+ install_args+=(--configure-system)
159
+ elif [[ "$CONFIGURE_VIRGIN_SYSTEM" == "0" ]]; then
160
+ install_args+=(--skip-configure)
161
+ fi
162
+ ./scripts/install_fresh_copy.sh "${install_args[@]}"
163
  }
164
 
165
  verify_project() {
scripts/install_fresh_copy.sh CHANGED
@@ -28,6 +28,9 @@ TARGET_DIR=""
28
  SKIP_CLONE="0"
29
  SKIP_VERIFY="0"
30
  PYTHON_BIN="python3"
 
 
 
31
 
32
  usage() {
33
  cat <<'EOF'
@@ -40,6 +43,10 @@ Options:
40
  --python <bin> Python executable (default: python3)
41
  --skip-clone Use existing repository in target/current directory
42
  --skip-verify Skip post-install verification script
 
 
 
 
43
  -h, --help Show this help
44
  EOF
45
  }
@@ -58,6 +65,14 @@ while [[ $# -gt 0 ]]; do
58
  SKIP_CLONE="1"; shift ;;
59
  --skip-verify)
60
  SKIP_VERIFY="1"; shift ;;
 
 
 
 
 
 
 
 
61
  -h|--help)
62
  usage; exit 0 ;;
63
  *)
@@ -123,6 +138,195 @@ else
123
  echo "[warn] Verification skipped (--skip-verify)"
124
  fi
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  cat <<'EOF'
127
 
128
  Installation complete.
@@ -136,4 +340,6 @@ Next steps:
136
  - Check cache status
137
  - Download missing models (while online)
138
  - Enable offline mode before offshore use
 
 
139
  EOF
 
28
  SKIP_CLONE="0"
29
  SKIP_VERIFY="0"
30
  PYTHON_BIN="python3"
31
+ DOWNLOAD_MODELS="ask"
32
+ HF_TOKEN_INPUT=""
33
+ CONFIGURE_VIRGIN_SYSTEM="ask"
34
 
35
  usage() {
36
  cat <<'EOF'
 
43
  --python <bin> Python executable (default: python3)
44
  --skip-clone Use existing repository in target/current directory
45
  --skip-verify Skip post-install verification script
46
+ --download-models Download MedGemma model files after install (requires HF token)
47
+ --hf-token <token> Hugging Face token used for model download
48
+ --configure-system Configure local runtime defaults for this machine
49
+ --skip-configure Skip local runtime configuration file generation
50
  -h, --help Show this help
51
  EOF
52
  }
 
65
  SKIP_CLONE="1"; shift ;;
66
  --skip-verify)
67
  SKIP_VERIFY="1"; shift ;;
68
+ --download-models)
69
+ DOWNLOAD_MODELS="1"; shift ;;
70
+ --hf-token)
71
+ HF_TOKEN_INPUT="$2"; shift 2 ;;
72
+ --configure-system)
73
+ CONFIGURE_VIRGIN_SYSTEM="1"; shift ;;
74
+ --skip-configure)
75
+ CONFIGURE_VIRGIN_SYSTEM="0"; shift ;;
76
  -h|--help)
77
  usage; exit 0 ;;
78
  *)
 
138
  echo "[warn] Verification skipped (--skip-verify)"
139
  fi
140
 
141
+ maybe_download_models() {
142
+ local want_download="$DOWNLOAD_MODELS"
143
+ if [[ "$want_download" == "ask" ]]; then
144
+ if [[ -t 0 ]]; then
145
+ cat <<'EOF'
146
+
147
+ [optional] MedGemma model predownload
148
+ - This can download many gigabytes (4B is large; 27B is much larger).
149
+ - You need a Hugging Face token with access to MedGemma model repositories.
150
+ - You can skip now and download later in Settings -> Offline Readiness Check.
151
+ EOF
152
+ read -r -p "Download MedGemma model files now? [y/N]: " reply
153
+ case "${reply,,}" in
154
+ y|yes) want_download="1" ;;
155
+ *) want_download="0" ;;
156
+ esac
157
+ else
158
+ want_download="0"
159
+ fi
160
+ fi
161
+
162
+ if [[ "$want_download" != "1" ]]; then
163
+ echo "[info] Skipping model download."
164
+ return
165
+ fi
166
+
167
+ local token="${HF_TOKEN_INPUT:-${HUGGINGFACE_TOKEN:-${HF_TOKEN:-}}}"
168
+ if [[ -z "$token" && -t 0 ]]; then
169
+ read -r -p "Enter Hugging Face token (hf_...) or leave blank to skip: " token
170
+ fi
171
+ if [[ -z "$token" ]]; then
172
+ echo "[warn] No Hugging Face token provided. Skipping model download."
173
+ echo " Use Settings -> Offline Readiness Check later when token is available."
174
+ return
175
+ fi
176
+
177
+ echo "[info] Starting optional MedGemma model download."
178
+ echo "[info] This may take significant time and disk space."
179
+ export HUGGINGFACE_TOKEN="$token"
180
+ export HF_TOKEN="$token"
181
+ export HF_HOME="$WORKDIR/models_cache"
182
+ export HUGGINGFACE_HUB_CACHE="$WORKDIR/models_cache/hub"
183
+ mkdir -p "$HUGGINGFACE_HUB_CACHE"
184
+
185
+ set +e
186
+ ./.venv/bin/python - <<'PY'
187
+ import os
188
+ from huggingface_hub import snapshot_download
189
+
190
+ models = [
191
+ "google/medgemma-1.5-4b-it",
192
+ "google/medgemma-27b-text-it",
193
+ ]
194
+ allow_patterns = [
195
+ "config.json",
196
+ "generation_config.json",
197
+ "tokenizer_config.json",
198
+ "tokenizer.json",
199
+ "tokenizer.model",
200
+ "vocab.json",
201
+ "merges.txt",
202
+ "preprocessor_config.json",
203
+ "processor_config.json",
204
+ "special_tokens_map.json",
205
+ "model.safetensors",
206
+ "model.safetensors.index.json",
207
+ "model-*.safetensors",
208
+ "chat_template*",
209
+ "README*",
210
+ ]
211
+
212
+ cache_dir = os.environ.get("HUGGINGFACE_HUB_CACHE")
213
+ token = os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HF_TOKEN")
214
+ if not token:
215
+ raise SystemExit("Missing Hugging Face token.")
216
+
217
+ for model_name in models:
218
+ print(f"[model-download] Downloading {model_name} ...", flush=True)
219
+ snapshot_download(
220
+ repo_id=model_name,
221
+ cache_dir=cache_dir,
222
+ local_dir=None,
223
+ local_dir_use_symlinks=False,
224
+ resume_download=True,
225
+ allow_patterns=allow_patterns,
226
+ token=token,
227
+ )
228
+ print(f"[model-download] Completed {model_name}", flush=True)
229
+
230
+ print("[model-download] All requested downloads complete.", flush=True)
231
+ PY
232
+ local rc=$?
233
+ set -e
234
+ if [[ $rc -ne 0 ]]; then
235
+ echo "[warn] Model download did not complete successfully (exit code: $rc)."
236
+ echo " You can retry from Settings -> Offline Readiness Check."
237
+ fi
238
+ }
239
+
240
+ configure_virgin_system() {
241
+ local want_config="$CONFIGURE_VIRGIN_SYSTEM"
242
+ if [[ "$want_config" == "ask" ]]; then
243
+ if [[ -t 0 ]]; then
244
+ read -r -p "Create local runtime config for this machine now? [Y/n]: " reply
245
+ case "${reply,,}" in
246
+ n|no) want_config="0" ;;
247
+ *) want_config="1" ;;
248
+ esac
249
+ else
250
+ # Non-interactive install: generate a sane default config automatically.
251
+ want_config="1"
252
+ fi
253
+ fi
254
+
255
+ if [[ "$want_config" != "1" ]]; then
256
+ echo "[info] Skipping local runtime configuration."
257
+ return
258
+ fi
259
+
260
+ local force_cuda_default="0"
261
+ if command -v nvidia-smi >/dev/null 2>&1; then
262
+ force_cuda_default="1"
263
+ fi
264
+ local force_cuda="$force_cuda_default"
265
+ local allow_cpu_fallback="0"
266
+ local verify_models_on_start="0"
267
+ local auto_verify_online="0"
268
+ local model_device_map_27b="manual"
269
+ local model_gpu_layers_27b="14"
270
+
271
+ if [[ -t 0 ]]; then
272
+ local reply=""
273
+ if [[ "$force_cuda_default" == "1" ]]; then
274
+ read -r -p "Prefer GPU startup by default (FORCE_CUDA=1)? [Y/n]: " reply
275
+ case "${reply,,}" in
276
+ n|no) force_cuda="0" ;;
277
+ *) force_cuda="1" ;;
278
+ esac
279
+ else
280
+ read -r -p "No NVIDIA tooling detected. Keep CPU-safe startup (FORCE_CUDA=0)? [Y/n]: " reply
281
+ case "${reply,,}" in
282
+ n|no) force_cuda="1" ;;
283
+ *) force_cuda="0" ;;
284
+ esac
285
+ fi
286
+
287
+ read -r -p "Allow CPU fallback when CUDA runtime errors occur? [y/N]: " reply
288
+ case "${reply,,}" in
289
+ y|yes) allow_cpu_fallback="1" ;;
290
+ *) allow_cpu_fallback="0" ;;
291
+ esac
292
+
293
+ read -r -p "Verify model cache at startup (slower startup)? [y/N]: " reply
294
+ case "${reply,,}" in
295
+ y|yes) verify_models_on_start="1" ;;
296
+ *) verify_models_on_start="0" ;;
297
+ esac
298
+ fi
299
+
300
+ local config_file="$WORKDIR/sailingmed.local.env"
301
+ cat >"$config_file" <<EOF
302
+ # =============================================================================
303
+ # SailingMedAdvisor local runtime configuration
304
+ # Generated by scripts/install_fresh_copy.sh
305
+ # =============================================================================
306
+ FORCE_CUDA=${force_cuda}
307
+ ALLOW_CPU_FALLBACK_ON_CUDA_ERROR=${allow_cpu_fallback}
308
+ DISABLE_LOCAL_INFERENCE=0
309
+ PUBLIC_DEMO_DISABLE_CHAT=0
310
+ VERIFY_MODELS_ON_START=${verify_models_on_start}
311
+ AUTO_VERIFY_ONLINE=${auto_verify_online}
312
+ MODEL_DEVICE_MAP_27B=${model_device_map_27b}
313
+ MODEL_GPU_LAYERS_27B=${model_gpu_layers_27b}
314
+ EOF
315
+
316
+ if [[ -d "$WORKDIR/models_cache/hub" ]]; then
317
+ {
318
+ echo "HF_HOME=${WORKDIR}/models_cache"
319
+ echo "HUGGINGFACE_HUB_CACHE=${WORKDIR}/models_cache/hub"
320
+ } >>"$config_file"
321
+ fi
322
+
323
+ chmod 600 "$config_file" || true
324
+ echo "[info] Wrote local runtime config: $config_file"
325
+ }
326
+
327
+ maybe_download_models
328
+ configure_virgin_system
329
+
330
  cat <<'EOF'
331
 
332
  Installation complete.
 
340
  - Check cache status
341
  - Download missing models (while online)
342
  - Enable offline mode before offshore use
343
+ 4) Optional local runtime config:
344
+ - Edit ./sailingmed.local.env to tune startup defaults for this machine
345
  EOF
static/js/chat.js CHANGED
@@ -122,6 +122,7 @@ const CHAT_STATE_KEY = 'sailingmed:chatState';
122
  const SKIP_LAST_CHAT_KEY = 'sailingmed:skipLastChat';
123
  const EMPTY_RESPONSE_PLACEHOLDER_TEXT = 'No consultation response yet. Expand "Start New Triage Consultation" above and submit to generate guidance.';
124
  const NO_LOCAL_MODELS_MESSAGE = 'No local MedGemma models are installed. Open Settings -> Offline Readiness Check to download at least one model.';
 
125
  const MODEL_CHOICES = [
126
  { value: 'google/medgemma-1.5-4b-it', label: 'medgemma-1.5-4b-it (local)' },
127
  { value: 'google/medgemma-27b-text-it', label: 'medgemma-27b-text-it (local)' },
@@ -139,6 +140,37 @@ let modelAvailabilityState = {
139
  message: '',
140
  };
141
  let modelSelectSignature = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  function isHfHostedRuntime() {
144
  try {
@@ -1074,9 +1106,10 @@ function updateUI() {
1074
  const chatModelWarning = document.getElementById('chat-model-availability-warning');
1075
  const promptRefreshInline = document.getElementById('prompt-refresh-inline');
1076
  const promptHeader = document.getElementById('prompt-preview-header');
 
1077
  const localModelsAvailable = hasRunnableLocalModel();
1078
  const noModels = !localModelsAvailable;
1079
- const noModelsMsg = noLocalModelsMessage();
1080
 
1081
  // Remove both classes first
1082
  banner.classList.remove('inquiry-mode', 'private-mode', 'no-privacy');
@@ -1166,10 +1199,11 @@ function updateUI() {
1166
  }
1167
  }
1168
  if (runBtn) {
1169
- runBtn.disabled = isSessionActive() || noModels;
 
1170
  }
1171
  if (sendBtn) {
1172
- sendBtn.disabled = !isSessionActive() || noModels;
1173
  sendBtn.title = noModels ? noModelsMsg : '';
1174
  }
1175
  if (startModelWarning) {
@@ -1475,6 +1509,10 @@ function buildSessionMetaPayload({ initialQuery, patientId, patientName, mode })
1475
  }
1476
 
1477
  async function submitChatMessage({ message, isStart, force28b = false, queueWait = false }) {
 
 
 
 
1478
  const txt = (message || '').trim();
1479
  if (!txt || isProcessing) return;
1480
  if (!hasRunnableLocalModel()) {
@@ -1718,6 +1756,10 @@ async function submitChatMessage({ message, isStart, force28b = false, queueWait
1718
  }
1719
 
1720
  async function runChat(promptText = null, force28b = false) {
 
 
 
 
1721
  if (!hasRunnableLocalModel()) {
1722
  alert(noLocalModelsMessage());
1723
  updateUI();
@@ -1746,6 +1788,10 @@ async function runChat(promptText = null, force28b = false) {
1746
  }
1747
 
1748
  async function sendChatMessage() {
 
 
 
 
1749
  if (!hasRunnableLocalModel()) {
1750
  alert(noLocalModelsMessage());
1751
  updateUI();
 
122
  const SKIP_LAST_CHAT_KEY = 'sailingmed:skipLastChat';
123
  const EMPTY_RESPONSE_PLACEHOLDER_TEXT = 'No consultation response yet. Expand "Start New Triage Consultation" above and submit to generate guidance.';
124
  const NO_LOCAL_MODELS_MESSAGE = 'No local MedGemma models are installed. Open Settings -> Offline Readiness Check to download at least one model.';
125
+ const HF_DEMO_UNAVAILABLE_MESSAGE = 'This Hugging Face-hosted build is a demo of an edge system. MedGemma triage/inquiry execution is not available here. Install SailingMedAdvisor locally to run full consultations.';
126
  const MODEL_CHOICES = [
127
  { value: 'google/medgemma-1.5-4b-it', label: 'medgemma-1.5-4b-it (local)' },
128
  { value: 'google/medgemma-27b-text-it', label: 'medgemma-27b-text-it (local)' },
 
140
  message: '',
141
  };
142
  let modelSelectSignature = '';
143
+ const PUBLIC_DEMO_DISABLE_CHAT = !!(window.SMA_RUNTIME && window.SMA_RUNTIME.publicDemoDisableChat);
144
+ const PUBLIC_DEMO_REPO_URL = String(
145
+ (window.SMA_RUNTIME && window.SMA_RUNTIME.publicDemoRepoUrl)
146
+ || 'https://github.com/rickeae/SailingMedAdvisor'
147
+ );
148
+
149
+ function isPublicDemoChatDisabled() {
150
+ const runtimeFlag = !!(window.SMA_RUNTIME && window.SMA_RUNTIME.isHfSpace);
151
+ return PUBLIC_DEMO_DISABLE_CHAT || runtimeFlag || isHfHostedRuntime();
152
+ }
153
+
154
+ function closePublicDemoChatDisabledModal() {
155
+ const modal = document.getElementById('public-demo-chat-disabled-modal');
156
+ if (modal) modal.style.display = 'none';
157
+ }
158
+
159
+ function showPublicDemoChatDisabledModal() {
160
+ const modal = document.getElementById('public-demo-chat-disabled-modal');
161
+ if (!modal) {
162
+ alert(HF_DEMO_UNAVAILABLE_MESSAGE);
163
+ return;
164
+ }
165
+ const repoLink = document.getElementById('public-demo-repo-link');
166
+ if (repoLink) {
167
+ repoLink.href = PUBLIC_DEMO_REPO_URL;
168
+ repoLink.textContent = PUBLIC_DEMO_REPO_URL;
169
+ }
170
+ modal.style.display = 'flex';
171
+ }
172
+
173
+ window.closePublicDemoChatDisabledModal = closePublicDemoChatDisabledModal;
174
 
175
  function isHfHostedRuntime() {
176
  try {
 
1106
  const chatModelWarning = document.getElementById('chat-model-availability-warning');
1107
  const promptRefreshInline = document.getElementById('prompt-refresh-inline');
1108
  const promptHeader = document.getElementById('prompt-preview-header');
1109
+ const demoChatBlocked = isPublicDemoChatDisabled();
1110
  const localModelsAvailable = hasRunnableLocalModel();
1111
  const noModels = !localModelsAvailable;
1112
+ const noModelsMsg = demoChatBlocked ? HF_DEMO_UNAVAILABLE_MESSAGE : noLocalModelsMessage();
1113
 
1114
  // Remove both classes first
1115
  banner.classList.remove('inquiry-mode', 'private-mode', 'no-privacy');
 
1199
  }
1200
  }
1201
  if (runBtn) {
1202
+ // Keep submit clickable in hosted demo mode so we can show the explanatory popup.
1203
+ runBtn.disabled = isSessionActive() || (noModels && !demoChatBlocked);
1204
  }
1205
  if (sendBtn) {
1206
+ sendBtn.disabled = !isSessionActive() || (noModels && !demoChatBlocked);
1207
  sendBtn.title = noModels ? noModelsMsg : '';
1208
  }
1209
  if (startModelWarning) {
 
1509
  }
1510
 
1511
  async function submitChatMessage({ message, isStart, force28b = false, queueWait = false }) {
1512
+ if (isPublicDemoChatDisabled()) {
1513
+ showPublicDemoChatDisabledModal();
1514
+ return;
1515
+ }
1516
  const txt = (message || '').trim();
1517
  if (!txt || isProcessing) return;
1518
  if (!hasRunnableLocalModel()) {
 
1756
  }
1757
 
1758
  async function runChat(promptText = null, force28b = false) {
1759
+ if (isPublicDemoChatDisabled()) {
1760
+ showPublicDemoChatDisabledModal();
1761
+ return;
1762
+ }
1763
  if (!hasRunnableLocalModel()) {
1764
  alert(noLocalModelsMessage());
1765
  updateUI();
 
1788
  }
1789
 
1790
  async function sendChatMessage() {
1791
+ if (isPublicDemoChatDisabled()) {
1792
+ showPublicDemoChatDisabledModal();
1793
+ return;
1794
+ }
1795
  if (!hasRunnableLocalModel()) {
1796
  alert(noLocalModelsMessage());
1797
  updateUI();
templates/index.html CHANGED
@@ -181,9 +181,32 @@
181
  .btn-row { display: flex; gap: 8px; margin-top: 10px; }
182
  .btn-sm { padding: 6px 12px; font-size: 13px; }
183
  .crew-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  .btn { padding: 10px 20px; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: opacity 0.3s; }
186
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
 
 
 
 
 
 
 
 
 
187
  .export-cta-btn {
188
  display: inline-flex;
189
  align-items: center;
@@ -1125,7 +1148,7 @@
1125
  <div class="page-body sidebar-open">
1126
  <div class="page-main">
1127
  <div class="panel-wrapper">
1128
- <div class="collapsible" style="margin-bottom:12px;">
1129
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1130
  <span class="dev-tag">dev:settings-crew-creds</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Crew Login Credentials</span>
1131
  <span id="crew-creds-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1134,7 +1157,7 @@
1134
  <div id="crew-credentials"></div>
1135
  </div>
1136
  </div>
1137
- <div class="collapsible" style="margin-bottom:12px;">
1138
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1139
  <span class="dev-tag">dev:settings-vax-types</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Vaccine Type Dropdown</span>
1140
  <span id="vax-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1150,7 +1173,7 @@
1150
  <div id="vaccine-types-status" style="margin-top:8px; font-size:12px; color:#555;">Saved with app settings.</div>
1151
  </div>
1152
  </div>
1153
- <div class="collapsible" style="margin-bottom:12px;">
1154
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1155
  <span class="dev-tag">dev:settings-pharmacy-labels</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Pharmaceutical User Labels</span>
1156
  <span id="pharm-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1166,7 +1189,7 @@
1166
  <div id="pharmacy-labels-status" style="margin-top:8px; font-size:12px; color:#555;">Saved with app settings.</div>
1167
  </div>
1168
  </div>
1169
- <div class="collapsible" style="margin-bottom:12px;">
1170
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1171
  <span class="dev-tag">dev:settings-usermode</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">User Mode</span>
1172
  <span id="user-mode-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1193,7 +1216,7 @@
1193
  </div>
1194
  </div>
1195
  </div>
1196
- <div class="collapsible" style="margin-bottom:12px;">
1197
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1198
  <span class="dev-tag">dev:settings-offline</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Offline Readiness Check</span>
1199
  <span id="offline-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1217,7 +1240,7 @@
1217
  <div style="font-size:12px; color:#555;">Recommended sequence: Run readiness check → Download missing models → Create backup snapshot → Enable offline mode.</div>
1218
  </div>
1219
  </div>
1220
- <div class="collapsible" style="margin-bottom:12px;">
1221
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1222
  <span class="dev-tag">dev:settings-runtime-log</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Runtime Debug Log (HF)</span>
1223
  <span id="runtime-log-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1237,7 +1260,7 @@
1237
  <textarea id="runtime-log-text" data-settings-no-autosave="1" readonly style="width:100%; min-height:240px; font-family:Menlo, Consolas, monospace; font-size:12px; line-height:1.35; background:#fff;"></textarea>
1238
  </div>
1239
  </div>
1240
- <div class="collapsible" style="margin-bottom:12px;">
1241
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1242
  <span class="dev-tag">dev:settings-last-prompt</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Last Prompt Verbatim</span>
1243
  </div>
@@ -1246,7 +1269,7 @@
1246
  <textarea id="last_prompt_verbatim" readonly style="width:100%; min-height:160px;"></textarea>
1247
  </div>
1248
  </div>
1249
- <div class="collapsible developer-only" style="margin-bottom:12px; display:none;">
1250
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1251
  <span class="dev-tag">dev:settings-default-export</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Export Default Dataset (Dev)</span>
1252
  </div>
@@ -1256,7 +1279,7 @@
1256
  <div id="default-export-status" style="margin-top:8px; font-size:12px; color:#555;"></div>
1257
  </div>
1258
  </div>
1259
- <div class="collapsible" style="margin-top:12px;">
1260
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1261
  <span class="dev-tag">dev:settings-models</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">MedGemma Model Parameters</span>
1262
  <span id="model-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
@@ -1281,8 +1304,18 @@
1281
  </div>
1282
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
1283
  <div style="padding:10px; border:1px solid #dde3ec; border-radius:8px; background:#fff; margin-bottom:12px;">
1284
- <div style="font-size:12px; color:#4a5568; margin-bottom:10px; font-weight:700;">
1285
- Generation Parameters (Inquiry Only)
 
 
 
 
 
 
 
 
 
 
1286
  </div>
1287
  <div style="display:flex; gap:20px; align-items:center; flex-wrap:wrap;">
1288
  <div style="display:flex; align-items:center; gap:6px;">
@@ -1326,12 +1359,22 @@
1326
 
1327
  <div class="collapsible" style="margin-bottom:12px;">
1328
  <div class="col-header crew-med-header" data-pref-key="settings-triage-open" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1329
- <span class="dev-tag">dev:settings-triage</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Triage</span>
1330
  </div>
1331
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
1332
  <div style="padding:10px; border:1px solid #dde3ec; border-radius:8px; background:#fff; margin-bottom:12px;">
1333
- <div style="font-size:12px; color:#4a5568; margin-bottom:10px; font-weight:700;">
1334
- Generation Parameters (Triage Only)
 
 
 
 
 
 
 
 
 
 
1335
  </div>
1336
  <div style="display:flex; gap:20px; align-items:center; flex-wrap:wrap;">
1337
  <div style="display:flex; align-items:center; gap:6px;">
@@ -1351,7 +1394,7 @@
1351
  <input type="number" id="tr_k" style="width:70px;">
1352
  </div>
1353
  <div style="margin-left:auto;">
1354
- <button class="btn btn-sm" style="background:var(--inquiry);" onclick="resetSection('triage')">Reset to Default</button>
1355
  </div>
1356
  </div>
1357
  </div>
@@ -1383,9 +1426,9 @@
1383
  <div class="col-header crew-med-header" data-pref-key="settings-triage-pathway-open" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1384
  <span class="dev-tag">dev:settings-triage-tree</span><span class="detail-icon history-arrow" style="font-size:16px; margin-right:8px;">▸</span><span style="font-weight:700;">Clinical Triage Pathway Prompt Builder</span>
1385
  <span style="margin-left:auto; display:flex; gap:6px;" onclick="event.stopPropagation();">
1386
- <button class="btn btn-sm" style="background:#0b8457;" onclick="event.stopPropagation(); exportTriagePromptTreeJson();">Export</button>
1387
- <button class="btn btn-sm" style="background:#0f766e;" onclick="event.stopPropagation(); triggerTriagePromptTreeImportPicker();">Import</button>
1388
- <button class="btn btn-sm" style="background:#4a5568;" onclick="event.stopPropagation(); resetTriagePromptTreeToDefault();">Reset to Default</button>
1389
  </span>
1390
  </div>
1391
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
@@ -1520,9 +1563,70 @@
1520
  </div>
1521
  </div>
1522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1523
  <script>
1524
  // Preload vessel data from server-rendered context to avoid empty fields if API fetch is delayed or fails
1525
  window.VESSEL_PREFILL = {{ vessel_prefill|tojson|safe }};
 
 
 
 
 
 
1526
  </script>
1527
  <script src="/static/js/utils.js?v=20260226"></script>
1528
  <script src="/static/js/chat.js?v=20260325"></script>
@@ -1596,6 +1700,39 @@
1596
  }
1597
  }
1598
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1599
  document.addEventListener('DOMContentLoaded', checkDbStatus);
1600
  </script>
1601
  </body>
 
181
  .btn-row { display: flex; gap: 8px; margin-top: 10px; }
182
  .btn-sm { padding: 6px 12px; font-size: 13px; }
183
  .crew-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
184
+ /* Settings page section ordering */
185
+ #Settings .panel-wrapper {
186
+ display: flex;
187
+ flex-direction: column;
188
+ }
189
+ #Settings .settings-section-medgemma { order: 1; }
190
+ #Settings .settings-section-user-mode { order: 2; }
191
+ #Settings .settings-section-offline { order: 3; }
192
+ #Settings .settings-section-runtime-log { order: 4; }
193
+ #Settings .settings-section-last-prompt { order: 5; }
194
+ #Settings .settings-section-crew-creds { order: 6; }
195
+ #Settings .settings-section-vax-types { order: 7; }
196
+ #Settings .settings-section-pharmacy-labels { order: 8; }
197
+ #Settings .settings-section-default-export { order: 9; }
198
 
199
  .btn { padding: 10px 20px; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: opacity 0.3s; }
200
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
201
+ .triage-action-btn {
202
+ background: #1f4f8f !important;
203
+ color: #fff !important;
204
+ padding: 6px 12px !important;
205
+ min-width: 116px;
206
+ text-align: center;
207
+ border-radius: 6px;
208
+ font-weight: 800;
209
+ }
210
  .export-cta-btn {
211
  display: inline-flex;
212
  align-items: center;
 
1148
  <div class="page-body sidebar-open">
1149
  <div class="page-main">
1150
  <div class="panel-wrapper">
1151
+ <div class="collapsible settings-section settings-section-crew-creds" style="margin-bottom:12px;">
1152
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1153
  <span class="dev-tag">dev:settings-crew-creds</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Crew Login Credentials</span>
1154
  <span id="crew-creds-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1157
  <div id="crew-credentials"></div>
1158
  </div>
1159
  </div>
1160
+ <div class="collapsible settings-section settings-section-vax-types" style="margin-bottom:12px;">
1161
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1162
  <span class="dev-tag">dev:settings-vax-types</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Vaccine Type Dropdown</span>
1163
  <span id="vax-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1173
  <div id="vaccine-types-status" style="margin-top:8px; font-size:12px; color:#555;">Saved with app settings.</div>
1174
  </div>
1175
  </div>
1176
+ <div class="collapsible settings-section settings-section-pharmacy-labels" style="margin-bottom:12px;">
1177
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1178
  <span class="dev-tag">dev:settings-pharmacy-labels</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Pharmaceutical User Labels</span>
1179
  <span id="pharm-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1189
  <div id="pharmacy-labels-status" style="margin-top:8px; font-size:12px; color:#555;">Saved with app settings.</div>
1190
  </div>
1191
  </div>
1192
+ <div class="collapsible settings-section settings-section-user-mode" style="margin-bottom:12px;">
1193
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1194
  <span class="dev-tag">dev:settings-usermode</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">User Mode</span>
1195
  <span id="user-mode-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1216
  </div>
1217
  </div>
1218
  </div>
1219
+ <div class="collapsible settings-section settings-section-offline" style="margin-bottom:12px;">
1220
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1221
  <span class="dev-tag">dev:settings-offline</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Offline Readiness Check</span>
1222
  <span id="offline-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1240
  <div style="font-size:12px; color:#555;">Recommended sequence: Run readiness check → Download missing models → Create backup snapshot → Enable offline mode.</div>
1241
  </div>
1242
  </div>
1243
+ <div class="collapsible settings-section settings-section-runtime-log" style="margin-bottom:12px;">
1244
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1245
  <span class="dev-tag">dev:settings-runtime-log</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Runtime Debug Log (HF)</span>
1246
  <span id="runtime-log-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1260
  <textarea id="runtime-log-text" data-settings-no-autosave="1" readonly style="width:100%; min-height:240px; font-family:Menlo, Consolas, monospace; font-size:12px; line-height:1.35; background:#fff;"></textarea>
1261
  </div>
1262
  </div>
1263
+ <div class="collapsible settings-section settings-section-last-prompt" style="margin-bottom:12px;">
1264
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1265
  <span class="dev-tag">dev:settings-last-prompt</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Last Prompt Verbatim</span>
1266
  </div>
 
1269
  <textarea id="last_prompt_verbatim" readonly style="width:100%; min-height:160px;"></textarea>
1270
  </div>
1271
  </div>
1272
+ <div class="collapsible developer-only settings-section settings-section-default-export" style="margin-bottom:12px; display:none;">
1273
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1274
  <span class="dev-tag">dev:settings-default-export</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Export Default Dataset (Dev)</span>
1275
  </div>
 
1279
  <div id="default-export-status" style="margin-top:8px; font-size:12px; color:#555;"></div>
1280
  </div>
1281
  </div>
1282
+ <div class="collapsible settings-section settings-section-medgemma" style="margin-bottom:12px;">
1283
  <div class="col-header crew-med-header" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1284
  <span class="dev-tag">dev:settings-models</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">MedGemma Model Parameters</span>
1285
  <span id="model-meta" style="margin-left:auto; font-size:12px; color:#4a5568;"></span>
 
1304
  </div>
1305
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
1306
  <div style="padding:10px; border:1px solid #dde3ec; border-radius:8px; background:#fff; margin-bottom:12px;">
1307
+ <div style="display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px; flex-wrap:wrap;">
1308
+ <div style="font-size:12px; color:#4a5568; font-weight:700;">
1309
+ Generation Parameters (Inquiry Only)
1310
+ </div>
1311
+ <button
1312
+ type="button"
1313
+ class="btn btn-sm"
1314
+ style="background:#1f4f8f; color:#fff; padding:5px 10px;"
1315
+ onclick="showGenerationParametersHelp('inquiry')"
1316
+ >
1317
+ What do these mean?
1318
+ </button>
1319
  </div>
1320
  <div style="display:flex; gap:20px; align-items:center; flex-wrap:wrap;">
1321
  <div style="display:flex; align-items:center; gap:6px;">
 
1359
 
1360
  <div class="collapsible" style="margin-bottom:12px;">
1361
  <div class="col-header crew-med-header" data-pref-key="settings-triage-open" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1362
+ <span class="dev-tag">dev:settings-triage</span><span class="detail-icon history-arrow" style="font-size:18px; margin-right:8px;">▸</span><span style="font-weight:700;">Triage Consultation Prompt (General and Clinical Triage Pathway Builder)</span>
1363
  </div>
1364
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
1365
  <div style="padding:10px; border:1px solid #dde3ec; border-radius:8px; background:#fff; margin-bottom:12px;">
1366
+ <div style="display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px; flex-wrap:wrap;">
1367
+ <div style="font-size:12px; color:#4a5568; font-weight:700;">
1368
+ Generation Parameters (Triage Only)
1369
+ </div>
1370
+ <button
1371
+ type="button"
1372
+ class="btn btn-sm"
1373
+ style="background:#1f4f8f; color:#fff; padding:5px 10px;"
1374
+ onclick="showGenerationParametersHelp('triage')"
1375
+ >
1376
+ What do these mean?
1377
+ </button>
1378
  </div>
1379
  <div style="display:flex; gap:20px; align-items:center; flex-wrap:wrap;">
1380
  <div style="display:flex; align-items:center; gap:6px;">
 
1394
  <input type="number" id="tr_k" style="width:70px;">
1395
  </div>
1396
  <div style="margin-left:auto;">
1397
+ <button class="btn btn-sm triage-action-btn" onclick="resetSection('triage')">Reset to Default</button>
1398
  </div>
1399
  </div>
1400
  </div>
 
1426
  <div class="col-header crew-med-header" data-pref-key="settings-triage-pathway-open" onclick="toggleSection(this)" style="background:#fff; justify-content:flex-start;">
1427
  <span class="dev-tag">dev:settings-triage-tree</span><span class="detail-icon history-arrow" style="font-size:16px; margin-right:8px;">▸</span><span style="font-weight:700;">Clinical Triage Pathway Prompt Builder</span>
1428
  <span style="margin-left:auto; display:flex; gap:6px;" onclick="event.stopPropagation();">
1429
+ <button class="btn btn-sm triage-action-btn" onclick="event.stopPropagation(); exportTriagePromptTreeJson();">Export</button>
1430
+ <button class="btn btn-sm triage-action-btn" onclick="event.stopPropagation(); triggerTriagePromptTreeImportPicker();">Import</button>
1431
+ <button class="btn btn-sm triage-action-btn" onclick="event.stopPropagation(); resetTriagePromptTreeToDefault();">Reset to Default</button>
1432
  </span>
1433
  </div>
1434
  <div class="col-body" style="padding:12px; background:#f8f9fa; display:none;">
 
1563
  </div>
1564
  </div>
1565
 
1566
+ <div
1567
+ id="gen-params-help-modal"
1568
+ style="display:none; position:fixed; inset:0; z-index:2100; background:rgba(6,28,48,0.55); align-items:center; justify-content:center; padding:20px;"
1569
+ onclick="if (event.target === this) closeGenerationParametersHelp()"
1570
+ >
1571
+ <div style="background:#fff; border-radius:14px; box-shadow:0 18px 48px rgba(0,0,0,0.25); max-width:700px; width:100%; max-height:min(84vh, 780px); overflow:auto; padding:20px; border:1px solid #d8e2f5;">
1572
+ <div id="gen-params-help-title" style="font-size:20px; font-weight:800; color:#122b4d; margin-bottom:10px;">Generation Parameters</div>
1573
+ <div id="gen-params-help-body" style="font-size:14px; color:#2c3e50; line-height:1.5;"></div>
1574
+ <div style="margin-top:14px; display:flex; justify-content:flex-end;">
1575
+ <button class="btn btn-sm" style="background:#2c3e50;" onclick="closeGenerationParametersHelp()">Close</button>
1576
+ </div>
1577
+ </div>
1578
+ </div>
1579
+
1580
+ <div
1581
+ id="public-demo-chat-disabled-modal"
1582
+ style="display:none; position:fixed; inset:0; z-index:2150; background:rgba(6,28,48,0.58); align-items:center; justify-content:center; padding:20px;"
1583
+ onclick="if (event.target === this) closePublicDemoChatDisabledModal()"
1584
+ >
1585
+ <div style="background:#fff; border-radius:14px; box-shadow:0 18px 48px rgba(0,0,0,0.25); max-width:760px; width:100%; max-height:min(88vh, 900px); overflow:auto; padding:20px; border:1px solid #d8e2f5;">
1586
+ <div style="font-size:20px; font-weight:800; color:#122b4d; margin-bottom:10px;">
1587
+ MedGemma Is Not Available In This Hugging Face Demo
1588
+ </div>
1589
+ <div style="font-size:14px; color:#2c3e50; line-height:1.5;">
1590
+ <p style="margin:0 0 10px 0;">
1591
+ This is a demo of a system that runs on the edge. MedGemma triage/inquiry execution is not available in this Hugging Face-hosted version.
1592
+ </p>
1593
+ <p style="margin:0 0 10px 0;">
1594
+ To run full MedGemma functionality, install locally from:
1595
+ <a id="public-demo-repo-link" href="#" target="_blank" rel="noopener">SailingMedAdvisor GitHub repository</a>.
1596
+ </p>
1597
+ <div style="background:#f8fbff; border:1px solid #d5e6fb; border-radius:8px; padding:10px; margin:0 0 10px 0;">
1598
+ <div style="font-weight:800; margin-bottom:6px;">Quick local setup</div>
1599
+ <ol style="margin:0 0 0 18px; padding:0;">
1600
+ <li><code>git clone https://github.com/rickeae/SailingMedAdvisor.git</code></li>
1601
+ <li><code>cd SailingMedAdvisor</code></li>
1602
+ <li><code>chmod +x scripts/bootstrap_ubuntu24_sailingmedadvisor.sh</code></li>
1603
+ <li><code>./scripts/bootstrap_ubuntu24_sailingmedadvisor.sh --start --configure-system</code></li>
1604
+ </ol>
1605
+ </div>
1606
+ <div style="background:#fff7ed; border:1px solid #f4c89a; border-radius:8px; padding:10px;">
1607
+ <div style="font-weight:800; margin-bottom:6px;">Before downloading models</div>
1608
+ <ul style="margin:0 0 0 18px; padding:0;">
1609
+ <li>Model downloads are large (multi-GB; 27B is significantly larger).</li>
1610
+ <li>You need a Hugging Face token with access to the MedGemma model repositories.</li>
1611
+ <li>Use Settings → Offline Readiness Check to download and verify model cache while online.</li>
1612
+ </ul>
1613
+ </div>
1614
+ </div>
1615
+ <div style="margin-top:14px; display:flex; justify-content:flex-end;">
1616
+ <button class="btn btn-sm" style="background:#2c3e50;" onclick="closePublicDemoChatDisabledModal()">Close</button>
1617
+ </div>
1618
+ </div>
1619
+ </div>
1620
+
1621
  <script>
1622
  // Preload vessel data from server-rendered context to avoid empty fields if API fetch is delayed or fails
1623
  window.VESSEL_PREFILL = {{ vessel_prefill|tojson|safe }};
1624
+ window.SMA_RUNTIME = {
1625
+ isHfSpace: {{ is_hf_space|tojson|safe }},
1626
+ disableLocalInference: {{ disable_local_inference|tojson|safe }},
1627
+ publicDemoDisableChat: {{ public_demo_disable_chat|tojson|safe }},
1628
+ publicDemoRepoUrl: {{ public_demo_repo_url|tojson|safe }},
1629
+ };
1630
  </script>
1631
  <script src="/static/js/utils.js?v=20260226"></script>
1632
  <script src="/static/js/chat.js?v=20260325"></script>
 
1700
  }
1701
  }
1702
 
1703
+ function showGenerationParametersHelp(mode) {
1704
+ const modal = document.getElementById('gen-params-help-modal');
1705
+ const title = document.getElementById('gen-params-help-title');
1706
+ const body = document.getElementById('gen-params-help-body');
1707
+ if (!modal || !title || !body) return;
1708
+ const normalized = mode === 'triage' ? 'triage' : 'inquiry';
1709
+ const modeLabel = normalized === 'triage' ? 'Triage Consultation' : 'Inquiry Consultation';
1710
+ const usageHint = normalized === 'triage'
1711
+ ? '<p style="margin:0 0 10px 0;"><strong>For triage:</strong> lower creativity is usually safer. Start with lower Temperature and keep Top-P/Top-K conservative.</p>'
1712
+ : '<p style="margin:0 0 10px 0;"><strong>For inquiry:</strong> you can allow a little more flexibility if you want broader explanations.</p>';
1713
+ title.textContent = `${modeLabel}: What these 4 settings mean`;
1714
+ body.innerHTML = `
1715
+ ${usageHint}
1716
+ <div style="display:grid; gap:8px;">
1717
+ <div><strong>Temperature</strong>: Controls how strict vs. creative the wording is. Lower = more consistent and predictable. Higher = more varied.</div>
1718
+ <div><strong>Top-P</strong>: Controls how wide the model's word choices are. Lower = safer and narrower choices. Higher = broader choices.</div>
1719
+ <div><strong>Top-K</strong>: Limits choices to the top K likely words each step. Smaller K = tighter and more focused. Larger K = more variety.</div>
1720
+ <div><strong>Tokens</strong>: Maximum response length. Higher values allow longer answers. Lower values produce shorter answers and faster return.</div>
1721
+ </div>
1722
+ <p style="margin:10px 0 0 0;"><strong>Simple rule:</strong> If output feels too random, lower Temperature/Top-P/Top-K. If output is too short, increase Tokens.</p>
1723
+ `;
1724
+ modal.style.display = 'flex';
1725
+ }
1726
+
1727
+ function closeGenerationParametersHelp() {
1728
+ const modal = document.getElementById('gen-params-help-modal');
1729
+ if (modal) modal.style.display = 'none';
1730
+ }
1731
+
1732
+ document.addEventListener('keydown', function(evt) {
1733
+ if (evt.key === 'Escape') closeGenerationParametersHelp();
1734
+ });
1735
+
1736
  document.addEventListener('DOMContentLoaded', checkDbStatus);
1737
  </script>
1738
  </body>