synapse-orch-ai 1.6.4 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/backend/core/config.py +11 -0
  2. package/backend/core/llm_providers.py +192 -1
  3. package/backend/core/models.py +7 -0
  4. package/backend/core/orchestration/steps.py +85 -13
  5. package/backend/core/routes/data.py +23 -2
  6. package/frontend-build/.next/BUILD_ID +1 -1
  7. package/frontend-build/.next/build-manifest.json +3 -3
  8. package/frontend-build/.next/prerender-manifest.json +3 -3
  9. package/frontend-build/.next/server/app/_global-error.html +1 -1
  10. package/frontend-build/.next/server/app/_global-error.rsc +1 -1
  11. package/frontend-build/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/frontend-build/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/frontend-build/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/frontend-build/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/frontend-build/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/frontend-build/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/frontend-build/.next/server/app/_not-found.html +1 -1
  18. package/frontend-build/.next/server/app/_not-found.rsc +2 -2
  19. package/frontend-build/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  20. package/frontend-build/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/frontend-build/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  22. package/frontend-build/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/frontend-build/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/frontend-build/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/frontend-build/.next/server/app/index.html +1 -1
  26. package/frontend-build/.next/server/app/index.rsc +2 -2
  27. package/frontend-build/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  28. package/frontend-build/.next/server/app/index.segments/_full.segment.rsc +2 -2
  29. package/frontend-build/.next/server/app/index.segments/_head.segment.rsc +1 -1
  30. package/frontend-build/.next/server/app/index.segments/_index.segment.rsc +2 -2
  31. package/frontend-build/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  32. package/frontend-build/.next/server/app/login/page_client-reference-manifest.js +1 -1
  33. package/frontend-build/.next/server/app/login.html +1 -1
  34. package/frontend-build/.next/server/app/login.rsc +2 -2
  35. package/frontend-build/.next/server/app/login.segments/_full.segment.rsc +2 -2
  36. package/frontend-build/.next/server/app/login.segments/_head.segment.rsc +1 -1
  37. package/frontend-build/.next/server/app/login.segments/_index.segment.rsc +2 -2
  38. package/frontend-build/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  39. package/frontend-build/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  40. package/frontend-build/.next/server/app/login.segments/login.segment.rsc +1 -1
  41. package/frontend-build/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/frontend-build/.next/server/app/settings/[tab]/page_client-reference-manifest.js +1 -1
  43. package/frontend-build/.next/server/chunks/ssr/_0b~n.nn._.js +21 -21
  44. package/frontend-build/.next/server/middleware-build-manifest.js +3 -3
  45. package/frontend-build/.next/server/middleware-manifest.json +5 -5
  46. package/frontend-build/.next/server/pages/404.html +1 -1
  47. package/frontend-build/.next/server/pages/500.html +1 -1
  48. package/frontend-build/.next/server/server-reference-manifest.js +1 -1
  49. package/frontend-build/.next/server/server-reference-manifest.json +1 -1
  50. package/frontend-build/.next/static/chunks/0_~6f._rtxo4c.js +128 -0
  51. package/frontend-build/.next/static/chunks/0e8yb9g1xgki8.css +1 -0
  52. package/package.json +1 -1
  53. package/frontend-build/.next/static/chunks/0xjv-b-~qzwt1.js +0 -128
  54. package/frontend-build/.next/static/chunks/0zsell.3txhrd.css +0 -1
  55. /package/frontend-build/.next/static/{KmMiWG6kMRXgyyjASDx-7 → x16v_ANP7BMUp9pXEqX4f}/_buildManifest.js +0 -0
  56. /package/frontend-build/.next/static/{KmMiWG6kMRXgyyjASDx-7 → x16v_ANP7BMUp9pXEqX4f}/_clientMiddlewareManifest.js +0 -0
  57. /package/frontend-build/.next/static/{KmMiWG6kMRXgyyjASDx-7 → x16v_ANP7BMUp9pXEqX4f}/_ssgManifest.js +0 -0
@@ -36,6 +36,9 @@ def load_settings():
36
36
  "local_compatible_models": "",
37
37
  "openai_compatible_embed_models": "",
38
38
  "local_compatible_embed_models": "",
39
+ "huggingface_token": "",
40
+ "huggingface_models": "",
41
+ "huggingface_max_new_tokens": 1024,
39
42
  "bedrock_api_key": "",
40
43
  "bedrock_inference_profile": "",
41
44
  "embedding_model": "",
@@ -57,6 +60,14 @@ def load_settings():
57
60
  # ~50–80% cost reduction on multi-turn agents at the cost of a 25% write
58
61
  # surcharge on the first turn. Disable only if a provider misbehaves.
59
62
  "prompt_cache_enabled": True,
63
+ # Transform step Python execution runtime.
64
+ # "docker" (default): runs in the sandbox-python container — 512 MB / 1 CPU /
65
+ # 60s, isolated from the host.
66
+ # "host": runs as a subprocess on the host with full RAM, GPU, filesystem,
67
+ # and network access. Required for HuggingFace / RecursiveMAS workflows that
68
+ # need torch + GPU but removes the sandbox security boundary. Self-hosted
69
+ # single-user deployments only.
70
+ "transform_runtime": "docker",
60
71
  "allow_db_write": False,
61
72
  "coding_agent_enabled": True,
62
73
  "report_agent_enabled": True,
@@ -125,6 +125,8 @@ def detect_mode_from_model(model_name: str) -> str:
125
125
  return "local"
126
126
  if m.startswith("ollama."):
127
127
  return "local"
128
+ if m.startswith("hf."):
129
+ return "hf"
128
130
  return "local"
129
131
 
130
132
 
@@ -162,6 +164,8 @@ def detect_provider_from_model(model_name: str) -> str:
162
164
  return "local_compatible"
163
165
  if m.startswith("ollama."):
164
166
  return "ollama"
167
+ if m.startswith("hf."):
168
+ return "huggingface"
165
169
  return "ollama"
166
170
 
167
171
 
@@ -1656,6 +1660,174 @@ async def call_bedrock(model_id, messages, system, region, settings, images=None
1656
1660
  raise LLMError(error_msg)
1657
1661
 
1658
1662
 
1663
+ # ─── HuggingFace local-model provider ───────────────────────────────────────────
1664
+ #
1665
+ # Loads transformers models in-process and runs inference on the host's GPU/CPU.
1666
+ # Used for `hf.<org>/<model>` model names (e.g. `hf.Qwen/Qwen2.5-7B-Instruct`).
1667
+ # Models are kept warm in a process-level cache so the 20-60s load cost is paid
1668
+ # once per model, not per call. torch + transformers are lazy-imported so users
1669
+ # who never use hf.* models don't need them installed.
1670
+
1671
+ _hf_model_cache: dict[str, tuple] = {}
1672
+ _hf_cache_lock = threading.Lock()
1673
+
1674
+
1675
+ def _hf_device() -> str:
1676
+ """Best available device for HF models: cuda > mps > cpu."""
1677
+ try:
1678
+ import torch
1679
+ if torch.cuda.is_available():
1680
+ return "cuda"
1681
+ if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
1682
+ return "mps"
1683
+ except Exception:
1684
+ pass
1685
+ return "cpu"
1686
+
1687
+
1688
+ def _load_hf_model(model_id: str, hf_token: str | None = None):
1689
+ """Load (or fetch from cache) an HF tokenizer + model pair.
1690
+
1691
+ Lock-guarded so concurrent calls don't double-load the same model.
1692
+ """
1693
+ with _hf_cache_lock:
1694
+ if model_id in _hf_model_cache:
1695
+ return _hf_model_cache[model_id]
1696
+
1697
+ try:
1698
+ import torch
1699
+ from transformers import AutoTokenizer, AutoModelForCausalLM
1700
+ except ImportError as e:
1701
+ raise LLMError(
1702
+ "HuggingFace provider: 'torch' and 'transformers' are not installed. "
1703
+ "Install them on the Synapse host (e.g. `pip install torch transformers`) "
1704
+ "to use hf.* models."
1705
+ ) from e
1706
+
1707
+ device = _hf_device()
1708
+ dtype = torch.float16 if device in ("cuda", "mps") else torch.float32
1709
+ load_kwargs: dict = {"torch_dtype": dtype}
1710
+ if device == "cuda":
1711
+ load_kwargs["device_map"] = "auto"
1712
+ if hf_token:
1713
+ load_kwargs["token"] = hf_token
1714
+
1715
+ print(f"DEBUG: 🤗 Loading HF model '{model_id}' on {device} — first call pays 20-60s load cost...", flush=True)
1716
+
1717
+ tok_kwargs: dict = {"token": hf_token} if hf_token else {}
1718
+ tokenizer = AutoTokenizer.from_pretrained(model_id, **tok_kwargs)
1719
+ model = AutoModelForCausalLM.from_pretrained(model_id, **load_kwargs)
1720
+
1721
+ # device_map="auto" already places shards; only move when we didn't use it.
1722
+ if device != "cuda":
1723
+ model = model.to(device)
1724
+ model.eval()
1725
+
1726
+ _hf_model_cache[model_id] = (tokenizer, model)
1727
+ print(f"DEBUG: ✅ HF model '{model_id}' loaded and cached.", flush=True)
1728
+ return tokenizer, model
1729
+
1730
+
1731
+ async def call_huggingface(
1732
+ model: str,
1733
+ messages: list[dict],
1734
+ system: str,
1735
+ settings: dict,
1736
+ tools: list[dict] | None = None,
1737
+ images: list[str] | None = None,
1738
+ ):
1739
+ """Run inference on a local HuggingFace transformers model.
1740
+
1741
+ Args:
1742
+ model: Full model name with hf. prefix (e.g. 'hf.Qwen/Qwen2.5-7B-Instruct').
1743
+ messages: OpenAI-style [{"role": ..., "content": ...}] list.
1744
+ system: System instruction text.
1745
+ settings: User settings dict. Reads `huggingface_token` (for gated models)
1746
+ and `huggingface_max_new_tokens` (default 1024).
1747
+ tools: Ignored with a warning — native tool calling varies by model and
1748
+ is left to the caller to inject via prompt text if needed.
1749
+ images: Ignored — vision is model-specific and not supported in this MVP.
1750
+
1751
+ Returns:
1752
+ (response_text, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens)
1753
+ — cache fields are always 0 (no cross-call prompt caching for local inference).
1754
+ """
1755
+ model_id = model[len("hf."):] if model.startswith("hf.") else model
1756
+ if not model_id:
1757
+ raise LLMError("HuggingFace provider: empty model name after 'hf.' prefix.")
1758
+
1759
+ if tools:
1760
+ print(
1761
+ f"DEBUG: ⚠️ HF provider received {len(tools)} tool(s) but does not support "
1762
+ f"native tool calling. Inject tool descriptions into the system prompt if needed.",
1763
+ flush=True,
1764
+ )
1765
+ if images:
1766
+ print(
1767
+ f"DEBUG: ⚠️ HF provider received {len(images)} image(s) but vision is not "
1768
+ f"supported in this MVP — they will be dropped.",
1769
+ flush=True,
1770
+ )
1771
+
1772
+ hf_token = (settings.get("huggingface_token") or "").strip() or None
1773
+ max_new_tokens = int(settings.get("huggingface_max_new_tokens") or 1024)
1774
+
1775
+ def _run():
1776
+ import torch
1777
+ tokenizer, model_obj = _load_hf_model(model_id, hf_token)
1778
+
1779
+ chat_messages: list[dict] = []
1780
+ if system and system.strip():
1781
+ chat_messages.append({"role": "system", "content": system.strip()})
1782
+ chat_messages.extend(messages or [])
1783
+
1784
+ # Prefer the model's chat template; fall back to a plain transcript if
1785
+ # the tokenizer doesn't define one (some base / non-instruct models).
1786
+ try:
1787
+ input_ids = tokenizer.apply_chat_template(
1788
+ chat_messages,
1789
+ add_generation_prompt=True,
1790
+ return_tensors="pt",
1791
+ )
1792
+ except Exception as e:
1793
+ print(f"DEBUG: ⚠️ HF chat template unavailable ({e}); using transcript fallback.", flush=True)
1794
+ transcript_parts: list[str] = []
1795
+ if system and system.strip():
1796
+ transcript_parts.append(f"System: {system.strip()}")
1797
+ t = _messages_to_transcript(messages)
1798
+ if t:
1799
+ transcript_parts.append(t)
1800
+ transcript_parts.append("Assistant:")
1801
+ transcript = "\n\n".join(transcript_parts)
1802
+ input_ids = tokenizer(transcript, return_tensors="pt").input_ids
1803
+
1804
+ input_ids = input_ids.to(model_obj.device)
1805
+ prompt_token_count = int(input_ids.shape[-1])
1806
+
1807
+ with torch.no_grad():
1808
+ output_ids = model_obj.generate(
1809
+ input_ids,
1810
+ max_new_tokens=max_new_tokens,
1811
+ do_sample=False,
1812
+ pad_token_id=tokenizer.eos_token_id,
1813
+ )
1814
+
1815
+ new_tokens = output_ids[0, prompt_token_count:]
1816
+ text = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
1817
+ output_token_count = int(new_tokens.shape[-1])
1818
+ return text, prompt_token_count, output_token_count
1819
+
1820
+ try:
1821
+ print(f"DEBUG: 🔄 HF call start (model={model_id})", flush=True)
1822
+ text, input_tokens, output_tokens = await asyncio.to_thread(_run)
1823
+ print(f"DEBUG: ✅ HF call complete (model={model_id}, out_tokens={output_tokens})", flush=True)
1824
+ return text, input_tokens, output_tokens, 0, 0
1825
+ except LLMError:
1826
+ raise
1827
+ except Exception as e:
1828
+ raise LLMError(f"HuggingFace provider error ({model_id}): {e}")
1829
+
1830
+
1659
1831
  def _messages_to_transcript(messages: list[dict] | None) -> str:
1660
1832
  """Lossy conversion of role/content messages to plain text for providers that only accept a single prompt."""
1661
1833
  if not messages:
@@ -1930,7 +2102,26 @@ async def generate_response(
1930
2102
  except Exception as e:
1931
2103
  return f"CLI Provider Error: {str(e)}"
1932
2104
 
1933
-
2105
+ elif mode == "hf":
2106
+ try:
2107
+ messages_for_hf = []
2108
+ if history_messages:
2109
+ messages_for_hf.extend(history_messages)
2110
+ messages_for_hf.append({"role": "user", "content": prompt_msg})
2111
+
2112
+ result_text, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens = await call_huggingface(
2113
+ current_model,
2114
+ messages_for_hf,
2115
+ augmented_system,
2116
+ current_settings,
2117
+ tools=tools,
2118
+ images=images,
2119
+ )
2120
+ except LLMError:
2121
+ raise
2122
+ except Exception as e:
2123
+ return f"HuggingFace Provider Error: {str(e)}"
2124
+
1934
2125
  else:
1935
2126
  # Local Ollama
1936
2127
  # Strip the ollama. prefix — the Ollama API expects bare model names
@@ -88,6 +88,13 @@ class Settings(BaseModel):
88
88
  local_compatible_models: str = "" # Comma-separated model names for local v1-compatible
89
89
  openai_compatible_embed_models: str = "" # Comma-separated embedding model names for OpenAI-compatible cloud
90
90
  local_compatible_embed_models: str = "" # Comma-separated embedding model names for local v1-compatible
91
+ huggingface_token: str = "" # Optional HF access token (required for gated models like Llama, Gemma)
92
+ huggingface_models: str = "" # Comma- or newline-separated HF model IDs (e.g. Qwen/Qwen2.5-7B-Instruct)
93
+ huggingface_max_new_tokens: int = 1024 # Max tokens to generate per HF call
94
+ # Transform step Python execution runtime: "docker" (sandboxed, default) or
95
+ # "host" (unsandboxed subprocess on host — needed for torch/GPU workloads,
96
+ # removes the sandbox security boundary).
97
+ transform_runtime: str = "docker"
91
98
  bedrock_api_key: str = "" # e.g. ABSK... (Amazon Bedrock API key)
92
99
  # Optional: required for some Bedrock models that don't support on-demand throughput.
93
100
  # Can be an inference profile ID or full ARN.
@@ -5,6 +5,7 @@ Each executor is an async generator that yields SSE-compatible events.
5
5
  import asyncio
6
6
  import datetime
7
7
  import json
8
+ import os
8
9
  import re
9
10
  import subprocess
10
11
  import sys
@@ -1098,7 +1099,12 @@ class TransformStepExecutor:
1098
1099
  yield {"type": "_log_prompt", "orch_step_id": step.id, "prompt": f"[Transform Code]\n{code}"}
1099
1100
 
1100
1101
  try:
1101
- result = await self._run_sandboxed(code, run.shared_state, timeout=step.timeout_seconds)
1102
+ from core.config import load_settings
1103
+ runtime = (load_settings().get("transform_runtime") or "docker").strip().lower()
1104
+ if runtime == "host":
1105
+ result = await self._run_host(code, run.shared_state, timeout=step.timeout_seconds)
1106
+ else:
1107
+ result = await self._run_sandboxed(code, run.shared_state, timeout=step.timeout_seconds)
1102
1108
 
1103
1109
  if step.output_key and result is not None:
1104
1110
  run.shared_state[step.output_key] = result
@@ -1111,6 +1117,82 @@ class TransformStepExecutor:
1111
1117
  except Exception as e:
1112
1118
  yield {"type": "step_error", "orch_step_id": step.id, "error": f"Transform error: {e}"}
1113
1119
 
1120
+ def _build_script(self, code: str, state: dict) -> str:
1121
+ """Build the JSON-in/JSON-out script wrapper used by both runtimes."""
1122
+ state_json = json.dumps(state, default=str)
1123
+ escaped = state_json.replace("\\", "\\\\").replace('"""', '\\"\\"\\"')
1124
+ return (
1125
+ f'import json\nstate = json.loads("""{escaped}""")\nresult = None\n\n'
1126
+ + code
1127
+ + '\n\nif result is not None:\n print(json.dumps({"result": result}, default=str))\nelse:\n print(json.dumps({"result": None}))\n'
1128
+ )
1129
+
1130
+ def _parse_script_output(self, stdout_text: str):
1131
+ """Parse the JSON output of the wrapped script. Falls back to plain text."""
1132
+ try:
1133
+ output = json.loads(stdout_text)
1134
+ return output.get("result")
1135
+ except json.JSONDecodeError:
1136
+ return stdout_text.strip() if stdout_text.strip() else None
1137
+
1138
+ async def _run_host(self, code: str, state: dict, timeout: int = 30):
1139
+ """Run Python code directly on the host via subprocess (NO sandbox).
1140
+
1141
+ Used when transform_runtime == "host". Gives the script full access to
1142
+ the host's Python env, GPU, filesystem, and network. Required for
1143
+ torch/transformers workloads that can't run in the 512MB Docker sandbox.
1144
+
1145
+ Same JSON-in/JSON-out contract as the docker path. The script inherits
1146
+ the backend process env (so CUDA_VISIBLE_DEVICES, HF_HOME, etc. flow
1147
+ through naturally) and runs under the same Python interpreter as the
1148
+ backend (sys.executable).
1149
+
1150
+ Timeout: caps at 30 minutes to bound runaway loads/inference. The docker
1151
+ path's 60s cap is too low for model loading, so we deliberately lift it
1152
+ here. Step-level timeout_seconds still applies.
1153
+ """
1154
+ import sys
1155
+ import shutil
1156
+ import tempfile
1157
+ from pathlib import Path
1158
+
1159
+ script_content = self._build_script(code, state)
1160
+ effective_timeout = min(max(int(timeout or 30), 1), 1800)
1161
+
1162
+ tmp_dir = tempfile.mkdtemp(prefix="transform_host_")
1163
+ script_path = str(Path(tmp_dir) / "transform.py")
1164
+ try:
1165
+ with open(script_path, "w") as f:
1166
+ f.write(script_content)
1167
+
1168
+ proc = await asyncio.create_subprocess_exec(
1169
+ sys.executable, script_path,
1170
+ stdout=asyncio.subprocess.PIPE,
1171
+ stderr=asyncio.subprocess.PIPE,
1172
+ env=os.environ.copy(),
1173
+ )
1174
+ try:
1175
+ stdout_b, stderr_b = await asyncio.wait_for(
1176
+ proc.communicate(), timeout=effective_timeout + 5
1177
+ )
1178
+ except asyncio.TimeoutError:
1179
+ try:
1180
+ proc.kill()
1181
+ await proc.wait()
1182
+ except Exception:
1183
+ pass
1184
+ raise RuntimeError(f"Transform (host) timed out after {effective_timeout}s")
1185
+
1186
+ stdout_text = stdout_b.decode("utf-8", errors="replace")
1187
+ stderr_text = stderr_b.decode("utf-8", errors="replace")
1188
+
1189
+ if proc.returncode != 0:
1190
+ raise RuntimeError(f"Transform (host) failed (exit {proc.returncode}): {stderr_text[:500]}")
1191
+
1192
+ return self._parse_script_output(stdout_text)
1193
+ finally:
1194
+ shutil.rmtree(tmp_dir, ignore_errors=True)
1195
+
1114
1196
  async def _run_sandboxed(self, code: str, state: dict, timeout: int = 30):
1115
1197
  """Run Python code in the Docker sandbox (sandbox-python:latest)."""
1116
1198
  import shutil
@@ -1120,13 +1202,7 @@ class TransformStepExecutor:
1120
1202
  if not shutil.which("docker"):
1121
1203
  raise RuntimeError("Docker is not available. Cannot execute transform code in sandbox.")
1122
1204
 
1123
- state_json = json.dumps(state, default=str)
1124
- escaped = state_json.replace("\\", "\\\\").replace('"""', '\\"\\"\\"')
1125
- script_content = (
1126
- f'import json\nstate = json.loads("""{escaped}""")\nresult = None\n\n'
1127
- + code
1128
- + '\n\nif result is not None:\n print(json.dumps({"result": result}, default=str))\nelse:\n print(json.dumps({"result": None}))\n'
1129
- )
1205
+ script_content = self._build_script(code, state)
1130
1206
 
1131
1207
  DATA_DIR_PATH = Path(__file__).resolve().parent.parent.parent / "data"
1132
1208
  vault_root = DATA_DIR_PATH / "vault"
@@ -1172,11 +1248,7 @@ class TransformStepExecutor:
1172
1248
  if proc.returncode != 0:
1173
1249
  raise RuntimeError(f"Transform failed (exit {proc.returncode}): {stderr_text[:500]}")
1174
1250
 
1175
- try:
1176
- output = json.loads(stdout_text)
1177
- return output.get("result")
1178
- except json.JSONDecodeError:
1179
- return stdout_text.strip() if stdout_text.strip() else None
1251
+ return self._parse_script_output(stdout_text)
1180
1252
  finally:
1181
1253
  shutil.rmtree(tmp_dir, ignore_errors=True)
1182
1254
 
@@ -190,6 +190,7 @@ async def get_models():
190
190
  local_compatible_models_str = (settings.get("local_compatible_models") or "").strip()
191
191
  openai_compatible_embed_models_str = (settings.get("openai_compatible_embed_models") or "").strip()
192
192
  local_compatible_embed_models_str = (settings.get("local_compatible_embed_models") or "").strip()
193
+ huggingface_models_str = (settings.get("huggingface_models") or "").strip()
193
194
  bedrock_available = bool((settings.get("bedrock_api_key") or "").strip() or
194
195
  (settings.get("aws_access_key_id") or "").strip())
195
196
 
@@ -466,6 +467,22 @@ async def get_models():
466
467
  prefixed_embed = [f"locv1.{m}" for m in all_embed]
467
468
  return available, prefixed, prefixed_embed
468
469
 
470
+ async def fetch_huggingface() -> tuple[bool, list[str], list[str]]:
471
+ """List user-configured HuggingFace model IDs, prefixed with hf.
472
+
473
+ No API discovery — HF model namespace is enormous and not enumerable.
474
+ The user lists the specific model IDs they want available (one per line
475
+ or comma-separated) in Settings → Models → HuggingFace Models.
476
+ """
477
+ models = [
478
+ m.strip() for m in huggingface_models_str.replace("\n", ",").split(",")
479
+ if m.strip()
480
+ ]
481
+ if not models:
482
+ return False, [], []
483
+ prefixed = [f"hf.{m}" if not m.startswith("hf.") else m for m in models]
484
+ return True, prefixed, []
485
+
469
486
  # Run all fetches concurrently; return_exceptions=True ensures one provider failure
470
487
  # doesn't cancel the others.
471
488
  _PROVIDER_FALLBACKS = [
@@ -482,8 +499,9 @@ async def get_models():
482
499
  (False, [], []), # gemini_cli
483
500
  (False, [], []), # codex_cli
484
501
  (False, [], []), # github_copilot_cli
502
+ (False, [], []), # huggingface
485
503
  ]
486
- _PROVIDER_NAMES = ["ollama", "openai", "anthropic", "gemini", "grok", "deepseek", "bedrock", "openai_compatible", "local_compatible", "anthropic_cli", "gemini_cli", "codex_cli", "github_copilot_cli"]
504
+ _PROVIDER_NAMES = ["ollama", "openai", "anthropic", "gemini", "grok", "deepseek", "bedrock", "openai_compatible", "local_compatible", "anthropic_cli", "gemini_cli", "codex_cli", "github_copilot_cli", "huggingface"]
487
505
 
488
506
  raw = await asyncio.gather(
489
507
  fetch_ollama(), fetch_openai(), fetch_anthropic(),
@@ -491,6 +509,7 @@ async def get_models():
491
509
  fetch_openai_compatible(), fetch_local_compatible(),
492
510
  fetch_claude_cli(), fetch_gemini_cli(), fetch_codex_cli(),
493
511
  fetch_github_copilot_cli(),
512
+ fetch_huggingface(),
494
513
  return_exceptions=True,
495
514
  )
496
515
 
@@ -515,6 +534,7 @@ async def get_models():
515
534
  c_gemini_avail, c_gemini_chat, _ = results[10]
516
535
  c_codex_avail, c_codex_chat, _ = results[11]
517
536
  c_copilot_avail, c_copilot_chat, _ = results[12]
537
+ hf_avail, hf_chat, _ = results[13]
518
538
 
519
539
  # --- Build provider map ---
520
540
  providers = {
@@ -531,6 +551,7 @@ async def get_models():
531
551
  "gemini_cli": {"available": c_gemini_avail, "models": c_gemini_chat, "embedding_models": []},
532
552
  "codex_cli": {"available": c_codex_avail, "models": c_codex_chat, "embedding_models": []},
533
553
  "github_copilot_cli": {"available": c_copilot_avail, "models": c_copilot_chat, "embedding_models": []},
554
+ "huggingface": {"available": hf_avail, "models": hf_chat, "embedding_models": []},
534
555
  }
535
556
 
536
557
  # --- Flat list of all available models ---
@@ -540,7 +561,7 @@ async def get_models():
540
561
  all_available.extend(info["models"])
541
562
 
542
563
  # --- Backward compat ---
543
- cloud_models = gemini_chat + anthropic_chat + openai_chat + grok_chat + deepseek_chat + BEDROCK_FALLBACK + oaic_chat + locv1_chat + c_claude_chat + c_gemini_chat + c_codex_chat + c_copilot_chat
564
+ cloud_models = gemini_chat + anthropic_chat + openai_chat + grok_chat + deepseek_chat + BEDROCK_FALLBACK + oaic_chat + locv1_chat + c_claude_chat + c_gemini_chat + c_codex_chat + c_copilot_chat + hf_chat
544
565
 
545
566
  return {
546
567
  "providers": providers,
@@ -1 +1 @@
1
- KmMiWG6kMRXgyyjASDx-7
1
+ x16v_ANP7BMUp9pXEqX4f
@@ -7,9 +7,9 @@
7
7
  "static/chunks/03~yq9q893hmn.js"
8
8
  ],
9
9
  "lowPriorityFiles": [
10
- "static/KmMiWG6kMRXgyyjASDx-7/_buildManifest.js",
11
- "static/KmMiWG6kMRXgyyjASDx-7/_ssgManifest.js",
12
- "static/KmMiWG6kMRXgyyjASDx-7/_clientMiddlewareManifest.js"
10
+ "static/x16v_ANP7BMUp9pXEqX4f/_buildManifest.js",
11
+ "static/x16v_ANP7BMUp9pXEqX4f/_ssgManifest.js",
12
+ "static/x16v_ANP7BMUp9pXEqX4f/_clientMiddlewareManifest.js"
13
13
  ],
14
14
  "rootMainFiles": [
15
15
  "static/chunks/0qmh881w772ef.js",
@@ -131,8 +131,8 @@
131
131
  "dynamicRoutes": {},
132
132
  "notFoundRoutes": [],
133
133
  "preview": {
134
- "previewModeId": "89f402e42004e0da09baec632f7b60b6",
135
- "previewModeSigningKey": "ca9170312f5324db6896369f0af70cf5cf6bccc625247272f05e2581acb1ad69",
136
- "previewModeEncryptionKey": "8e875766cd49e56f6e99251d3c19c330245ad90e62692139888186b61a36eb2e"
134
+ "previewModeId": "056fc6d2553cf3dd26658aac1cab5c3e",
135
+ "previewModeSigningKey": "ceac2672dd6161f5712b0be054723c687b713bd288e9847919512c58ae07c4ab",
136
+ "previewModeEncryptionKey": "467e63a136ba9a1f9fac048949a5ac57d67a11b1caf6930a7e212a3ecd7463aa"
137
137
  }
138
138
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0qmh881w772ef.js"/><script src="/_next/static/chunks/02i7dfk78~t~2.js" async=""></script><script src="/_next/static/chunks/0c7h~x4_chf35.js" async=""></script><script src="/_next/static/chunks/00xhsd~k-wxkl.js" async=""></script><script src="/_next/static/chunks/turbopack-09lztqg9--eu..js" async=""></script><script src="/_next/static/chunks/01xlw8hd842-c.js" async=""></script><script src="/_next/static/chunks/0m92kh92cfh_..js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/0qmh881w772ef.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"KmMiWG6kMRXgyyjASDx-7\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0qmh881w772ef.js"/><script src="/_next/static/chunks/02i7dfk78~t~2.js" async=""></script><script src="/_next/static/chunks/0c7h~x4_chf35.js" async=""></script><script src="/_next/static/chunks/00xhsd~k-wxkl.js" async=""></script><script src="/_next/static/chunks/turbopack-09lztqg9--eu..js" async=""></script><script src="/_next/static/chunks/01xlw8hd842-c.js" async=""></script><script src="/_next/static/chunks/0m92kh92cfh_..js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/0qmh881w772ef.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/01xlw8hd842-c.js\",\"/_next/static/chunks/0m92kh92cfh_..js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"x16v_ANP7BMUp9pXEqX4f\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
@@ -6,7 +6,7 @@
6
6
  8:I[97367,["/_next/static/chunks/01xlw8hd842-c.js","/_next/static/chunks/0m92kh92cfh_..js"],"ViewportBoundary"]
7
7
  a:I[97367,["/_next/static/chunks/01xlw8hd842-c.js","/_next/static/chunks/0m92kh92cfh_..js"],"MetadataBoundary"]
8
8
  c:I[68027,["/_next/static/chunks/01xlw8hd842-c.js","/_next/static/chunks/0m92kh92cfh_..js"],"default",1]
9
- 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"KmMiWG6kMRXgyyjASDx-7"}
9
+ 0:{"P":null,"c":["","_global-error"],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]}],[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"],["$","$1","h",{"children":[null,["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"x16v_ANP7BMUp9pXEqX4f"}
10
10
  d:[]
11
11
  7:"$Wd"
12
12
  9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
@@ -1,5 +1,5 @@
1
1
  1:"$Sreact.fragment"
2
2
  2:I[97367,["/_next/static/chunks/01xlw8hd842-c.js","/_next/static/chunks/0m92kh92cfh_..js"],"OutletBoundary"]
3
3
  3:"$Sreact.suspense"
4
- 0:{"rsc":["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"KmMiWG6kMRXgyyjASDx-7"}
4
+ 0:{"rsc":["$","$1","c",{"children":[["$","html",null,{"id":"__next_error__","children":[["$","head",null,{"children":[["$","title",null,{"children":"500: This page couldn’t load"}],["$","style",null,{"dangerouslySetInnerHTML":{"__html":":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }"}}]]}],["$","body",null,{"children":["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","display":"flex","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"style":{"marginTop":"-32px","maxWidth":"325px","padding":"32px 28px","textAlign":"left"},"children":[["$","svg",null,{"width":"32","height":"32","viewBox":"-0.2 -1.5 32 32","fill":"none","style":{"marginBottom":"24px"},"children":["$","path",null,{"d":"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z","fill":"var(--next-error-title)"}]}],["$","h1",null,{"style":{"fontSize":"24px","fontWeight":500,"letterSpacing":"-0.02em","lineHeight":"32px","margin":"0 0 12px 0","color":"var(--next-error-title)"},"children":"This page couldn’t load"}],["$","p",null,{"style":{"fontSize":"14px","fontWeight":400,"lineHeight":"21px","margin":"0 0 20px 0","color":"var(--next-error-message)"},"children":"A server error occurred. Reload to try again."}],["$","form",null,{"style":{"margin":0},"children":["$","button",null,{"type":"submit","style":{"display":"inline-flex","alignItems":"center","justifyContent":"center","height":"32px","padding":"0 12px","fontSize":"14px","fontWeight":500,"lineHeight":"20px","borderRadius":"6px","cursor":"pointer","color":"var(--next-error-btn-text)","background":"var(--next-error-btn-bg)","border":"var(--next-error-btn-border)"},"children":"Reload"}]}]]}]}]}]]}],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"x16v_ANP7BMUp9pXEqX4f"}
5
5
  4:null