migrate knowledge atlas to llama.cpp
This commit is contained in:
+8
-7
@@ -15,10 +15,11 @@ web/manage.sh status
|
||||
python3 web/app.py --host 100.114.68.27 --port 18080
|
||||
```
|
||||
|
||||
默认 Ollama 地址:
|
||||
默认 llama.cpp 地址:
|
||||
|
||||
```bash
|
||||
OLLAMA_URL=http://192.168.1.10:11434
|
||||
MODEL_API_LUNA_URL=http://192.168.1.10:11435/v1
|
||||
MODEL_API_SOL_URL=http://192.168.1.10:11436/v1
|
||||
```
|
||||
|
||||
访问地址:
|
||||
@@ -44,16 +45,16 @@ https://lab.k1412.top/
|
||||
- 主题导读、阅读路径、主题比较:`ChatGPT-5.6:Sol`,只在前端手动确认后调用
|
||||
- Search Agent 查询理解:`ChatGPT-5.6:Luna`
|
||||
- Search Agent 结果简报、搜索追问、论文对话:`ChatGPT-5.6:Terra`
|
||||
- Ollama 请求单并发执行,并缓存结果到 `web/cache/ai/`
|
||||
- 应用侧不下发 `num_ctx` / `keep_alive`,上下文长度和模型常驻由 Ollama 服务端统一控制
|
||||
- llama.cpp 请求单并发执行,并缓存结果到 `web/cache/ai/`
|
||||
- 应用侧不覆盖上下文长度,Luna 固定 8K,Terra/Sol 固定 32K
|
||||
- arXiv 摘要缓存到 `web/cache/arxiv/`
|
||||
|
||||
模型 tier 可以覆盖:
|
||||
|
||||
```bash
|
||||
OLLAMA_MODEL_LIGHT=ChatGPT-5.6:Luna \
|
||||
OLLAMA_MODEL_FAST=ChatGPT-5.6:Terra \
|
||||
OLLAMA_MODEL_LARGE=ChatGPT-5.6:Sol \
|
||||
MODEL_LIGHT=ChatGPT-5.6:Luna \
|
||||
MODEL_FAST=ChatGPT-5.6:Terra \
|
||||
MODEL_LARGE=ChatGPT-5.6:Sol \
|
||||
web/manage.sh restart
|
||||
```
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ Wants=network-online.target
|
||||
Type=simple
|
||||
WorkingDirectory=%h/Code/agent
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
Environment=OLLAMA_URL=http://192.168.1.10:11434
|
||||
Environment=OLLAMA_MODEL_LIGHT=ChatGPT-5.6:Luna
|
||||
Environment=OLLAMA_MODEL_FAST=ChatGPT-5.6:Terra
|
||||
Environment=OLLAMA_MODEL_LARGE=ChatGPT-5.6:Sol
|
||||
Environment=MODEL_API_LUNA_URL=http://192.168.1.10:11435/v1
|
||||
Environment=MODEL_API_SOL_URL=http://192.168.1.10:11436/v1
|
||||
Environment=MODEL_LIGHT=ChatGPT-5.6:Luna
|
||||
Environment=MODEL_FAST=ChatGPT-5.6:Terra
|
||||
Environment=MODEL_LARGE=ChatGPT-5.6:Sol
|
||||
Environment=HOST=100.114.68.27
|
||||
Environment=PORT=18080
|
||||
EnvironmentFile=-%h/Code/agent/web/cache/service.env
|
||||
|
||||
+98
-70
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Serve a local paper browser with lightweight Ollama-backed actions."""
|
||||
"""Serve a local paper browser with lightweight llama.cpp-backed actions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -30,8 +30,13 @@ INDEX_PATH = ROOT / "data" / "index.json"
|
||||
CACHE_ROOT = WEB_ROOT / "cache"
|
||||
ARXIV_CACHE = CACHE_ROOT / "arxiv"
|
||||
AI_CACHE = CACHE_ROOT / "ai"
|
||||
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://192.168.1.10:11434").rstrip("/")
|
||||
ASSET_VERSION = "atlas-20260712-01"
|
||||
MODEL_API_LUNA_URL = os.environ.get(
|
||||
"MODEL_API_LUNA_URL", "http://192.168.1.10:11435/v1"
|
||||
).rstrip("/")
|
||||
MODEL_API_SOL_URL = os.environ.get(
|
||||
"MODEL_API_SOL_URL", "http://192.168.1.10:11436/v1"
|
||||
).rstrip("/")
|
||||
ASSET_VERSION = "atlas-20260728-01"
|
||||
|
||||
|
||||
def detect_app_version() -> str:
|
||||
@@ -51,9 +56,9 @@ def detect_app_version() -> str:
|
||||
|
||||
APP_VERSION = detect_app_version()
|
||||
|
||||
MODEL_LIGHT = os.environ.get("OLLAMA_MODEL_LIGHT", "ChatGPT-5.6:Luna")
|
||||
MODEL_FAST = os.environ.get("OLLAMA_MODEL_FAST", "ChatGPT-5.6:Terra")
|
||||
MODEL_LARGE = os.environ.get("OLLAMA_MODEL_LARGE", "ChatGPT-5.6:Sol")
|
||||
MODEL_LIGHT = os.environ.get("MODEL_LIGHT", "ChatGPT-5.6:Luna")
|
||||
MODEL_FAST = os.environ.get("MODEL_FAST", "ChatGPT-5.6:Terra")
|
||||
MODEL_LARGE = os.environ.get("MODEL_LARGE", "ChatGPT-5.6:Sol")
|
||||
|
||||
DEFAULT_MODELS = {
|
||||
"translate": MODEL_LIGHT,
|
||||
@@ -75,7 +80,7 @@ NS = {
|
||||
}
|
||||
|
||||
INDEX_LOCK = threading.Lock()
|
||||
OLLAMA_LOCK = threading.Lock()
|
||||
MODEL_API_LOCK = threading.Lock()
|
||||
SEARCH_LOCK = threading.Lock()
|
||||
INDEX_CACHE: dict[str, Any] = {"mtime": 0.0, "papers": [], "by_id": {}}
|
||||
ATLAS_CACHE: dict[str, Any] = {"mtime": 0.0, "atlas": None}
|
||||
@@ -512,8 +517,8 @@ def plan_search_query(query: str, scope_topic: str = "") -> tuple[dict[str, Any]
|
||||
model = DEFAULT_MODELS["search_plan"]
|
||||
result: dict[str, Any] | None = None
|
||||
try:
|
||||
with OLLAMA_LOCK:
|
||||
result = call_ollama(model, search_plan_prompt(query, scope_topic), "search_plan")
|
||||
with MODEL_API_LOCK:
|
||||
result = call_model(model, search_plan_prompt(query, scope_topic), "search_plan")
|
||||
plan = extract_json_object(result["response"])
|
||||
merged = {
|
||||
**fallback,
|
||||
@@ -676,8 +681,8 @@ def fallback_search_summary(query: str, plan: dict[str, Any], aggregation: dict[
|
||||
def generate_search_summary(query: str, plan: dict[str, Any], items: list[dict[str, Any]], aggregation: dict[str, Any]) -> dict[str, Any]:
|
||||
model = DEFAULT_MODELS["search_summary"]
|
||||
try:
|
||||
with OLLAMA_LOCK:
|
||||
result = call_ollama(model, search_summary_prompt(query, plan, items, aggregation), "search_summary")
|
||||
with MODEL_API_LOCK:
|
||||
result = call_model(model, search_summary_prompt(query, plan, items, aggregation), "search_summary")
|
||||
return {
|
||||
"text": result["response"],
|
||||
"model": result["model"],
|
||||
@@ -1240,45 +1245,84 @@ def clean_model_response(value: str) -> str:
|
||||
return text or value.strip()
|
||||
|
||||
|
||||
def call_ollama(model: str, prompt: str, mode: str) -> dict[str, Any]:
|
||||
def model_api_url(model: str) -> str:
|
||||
if model in {MODEL_LIGHT, "ChatGPT-5.6:auto", "gpt-4o"}:
|
||||
return MODEL_API_LUNA_URL
|
||||
return MODEL_API_SOL_URL
|
||||
|
||||
|
||||
def model_api_request(url: str, *, data: bytes | None = None) -> urllib.request.Request:
|
||||
headers = {"content-type": "application/json"} if data is not None else {}
|
||||
return urllib.request.Request(url, data=data, headers=headers)
|
||||
|
||||
|
||||
def available_model_inventory(timeout: float = 5) -> list[dict[str, Any]]:
|
||||
aliases: set[str] = set()
|
||||
for base_url in (MODEL_API_LUNA_URL, MODEL_API_SOL_URL):
|
||||
request = model_api_request(f"{base_url}/models")
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
for item in payload.get("data", []):
|
||||
if item.get("id"):
|
||||
aliases.add(str(item["id"]))
|
||||
aliases.update(str(alias) for alias in item.get("aliases", []) if alias)
|
||||
|
||||
context_lengths = {
|
||||
MODEL_LIGHT: 8192,
|
||||
MODEL_FAST: 32768,
|
||||
MODEL_LARGE: 32768,
|
||||
}
|
||||
return [
|
||||
{
|
||||
"name": model,
|
||||
"parameter_size": None,
|
||||
"context_length": context_lengths.get(model),
|
||||
}
|
||||
for model in sorted(aliases)
|
||||
]
|
||||
|
||||
|
||||
def call_model(model: str, prompt: str, mode: str) -> dict[str, Any]:
|
||||
options = {
|
||||
"temperature": 0.2,
|
||||
"num_predict": 520,
|
||||
"max_tokens": 520,
|
||||
}
|
||||
if mode == "translate":
|
||||
options.update({"num_predict": 700, "temperature": 0.1})
|
||||
options.update({"max_tokens": 700, "temperature": 0.1})
|
||||
elif mode == "deep":
|
||||
options.update({"num_predict": 900, "temperature": 0.25})
|
||||
options.update({"max_tokens": 900, "temperature": 0.25})
|
||||
elif mode in {"atlas", "topic", "path", "compare"}:
|
||||
options.update({"num_predict": 950, "temperature": 0.22})
|
||||
options.update({"max_tokens": 950, "temperature": 0.22})
|
||||
elif mode == "search_plan":
|
||||
options.update({"num_predict": 420, "temperature": 0.1})
|
||||
options.update({"max_tokens": 420, "temperature": 0.1})
|
||||
elif mode in {"search_summary", "search_followup", "paper_chat"}:
|
||||
options.update({"num_predict": 780, "temperature": 0.18})
|
||||
options.update({"max_tokens": 780, "temperature": 0.18})
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"stream": False,
|
||||
"options": options,
|
||||
"chat_template_kwargs": {"enable_thinking": model == MODEL_LARGE},
|
||||
**options,
|
||||
}
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
f"{OLLAMA_URL}/api/generate",
|
||||
request = model_api_request(
|
||||
f"{model_api_url(model)}/chat/completions",
|
||||
data=data,
|
||||
headers={"content-type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
started = time.time()
|
||||
with urllib.request.urlopen(request, timeout=240) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
choice = (result.get("choices") or [{}])[0]
|
||||
message = choice.get("message") or {}
|
||||
usage = result.get("usage") or {}
|
||||
return {
|
||||
"model": model,
|
||||
"mode": mode,
|
||||
"response": clean_model_response(str(result.get("response") or "")),
|
||||
"response": clean_model_response(str(message.get("content") or "")),
|
||||
"duration_seconds": round(time.time() - started, 2),
|
||||
"eval_count": result.get("eval_count"),
|
||||
"prompt_eval_count": result.get("prompt_eval_count"),
|
||||
"eval_count": usage.get("completion_tokens"),
|
||||
"prompt_eval_count": usage.get("prompt_tokens"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1379,20 +1423,14 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
def handle_health(self) -> None:
|
||||
papers, _ = load_papers()
|
||||
ollama_ok = False
|
||||
model_api_ok = False
|
||||
available_models: list[str] = []
|
||||
try:
|
||||
request = urllib.request.Request(f"{OLLAMA_URL}/api/tags")
|
||||
with urllib.request.urlopen(request, timeout=3) as response:
|
||||
ollama_ok = response.status == 200
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
available_models = sorted(
|
||||
str(item.get("name"))
|
||||
for item in payload.get("models", [])
|
||||
if item.get("name")
|
||||
)
|
||||
inventory = available_model_inventory(timeout=3)
|
||||
available_models = [str(item["name"]) for item in inventory]
|
||||
model_api_ok = True
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
|
||||
ollama_ok = False
|
||||
model_api_ok = False
|
||||
missing_default_models = sorted(set(DEFAULT_MODELS.values()) - set(available_models))
|
||||
write_json(
|
||||
self,
|
||||
@@ -1401,9 +1439,9 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
"paper_count": len(papers),
|
||||
"app_version": APP_VERSION,
|
||||
"asset_version": ASSET_VERSION,
|
||||
"ollama_url": OLLAMA_URL,
|
||||
"ollama_ok": ollama_ok,
|
||||
"ollama_ready": ollama_ok and not missing_default_models,
|
||||
"model_api_urls": [MODEL_API_LUNA_URL, MODEL_API_SOL_URL],
|
||||
"model_api_ok": model_api_ok,
|
||||
"model_api_ready": model_api_ok and not missing_default_models,
|
||||
"available_models": available_models,
|
||||
"missing_default_models": missing_default_models,
|
||||
"default_models": DEFAULT_MODELS,
|
||||
@@ -1412,20 +1450,10 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
def handle_models(self) -> None:
|
||||
try:
|
||||
request = urllib.request.Request(f"{OLLAMA_URL}/api/tags")
|
||||
with urllib.request.urlopen(request, timeout=5) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
models = [
|
||||
{
|
||||
"name": item.get("name"),
|
||||
"parameter_size": (item.get("details") or {}).get("parameter_size"),
|
||||
"context_length": (item.get("details") or {}).get("context_length"),
|
||||
}
|
||||
for item in payload.get("models", [])
|
||||
]
|
||||
models = available_model_inventory()
|
||||
write_json(self, {"models": models, "default_models": DEFAULT_MODELS})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
write_error(self, HTTPStatus.BAD_GATEWAY, f"ollama unavailable: {exc}")
|
||||
write_error(self, HTTPStatus.BAD_GATEWAY, f"model API unavailable: {exc}")
|
||||
|
||||
def handle_paper_search(self, query: dict[str, list[str]]) -> None:
|
||||
papers, _ = load_papers()
|
||||
@@ -1508,11 +1536,11 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
if not job or job.get("status") != "done":
|
||||
write_error(self, HTTPStatus.NOT_FOUND, "completed search job not found")
|
||||
return
|
||||
if not OLLAMA_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later")
|
||||
if not MODEL_API_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later")
|
||||
return
|
||||
try:
|
||||
result = call_ollama(model, search_followup_prompt(job, question), "search_followup")
|
||||
result = call_model(model, search_followup_prompt(job, question), "search_followup")
|
||||
write_json(
|
||||
self,
|
||||
{
|
||||
@@ -1524,7 +1552,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
},
|
||||
)
|
||||
finally:
|
||||
OLLAMA_LOCK.release()
|
||||
MODEL_API_LOCK.release()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
|
||||
|
||||
@@ -1581,8 +1609,8 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
if not paper:
|
||||
write_error(self, HTTPStatus.NOT_FOUND, "paper not found")
|
||||
return
|
||||
if not OLLAMA_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later")
|
||||
if not MODEL_API_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later")
|
||||
return
|
||||
try:
|
||||
abstract = None
|
||||
@@ -1592,7 +1620,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
abstract = fetch_arxiv(arxiv_id)
|
||||
except Exception:
|
||||
abstract = None
|
||||
result = call_ollama(model, paper_chat_prompt(paper, abstract, question, history), "paper_chat")
|
||||
result = call_model(model, paper_chat_prompt(paper, abstract, question, history), "paper_chat")
|
||||
write_json(
|
||||
self,
|
||||
{
|
||||
@@ -1604,7 +1632,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
},
|
||||
)
|
||||
finally:
|
||||
OLLAMA_LOCK.release()
|
||||
MODEL_API_LOCK.release()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
|
||||
|
||||
@@ -1633,8 +1661,8 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
write_json(self, result)
|
||||
return
|
||||
|
||||
if not OLLAMA_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later")
|
||||
if not MODEL_API_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later")
|
||||
return
|
||||
try:
|
||||
abstract = None
|
||||
@@ -1642,7 +1670,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
if arxiv_id:
|
||||
abstract = fetch_arxiv(arxiv_id)
|
||||
context = paper_context(paper, abstract)
|
||||
result = call_ollama(model, prompt_for(mode, context), mode)
|
||||
result = call_model(model, prompt_for(mode, context), mode)
|
||||
result.update(
|
||||
{
|
||||
"id": paper_id,
|
||||
@@ -1654,7 +1682,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
cache_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
write_json(self, result)
|
||||
finally:
|
||||
OLLAMA_LOCK.release()
|
||||
MODEL_API_LOCK.release()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
|
||||
|
||||
@@ -1685,12 +1713,12 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
write_json(self, result)
|
||||
return
|
||||
|
||||
if not OLLAMA_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "ollama is busy; try again later")
|
||||
if not MODEL_API_LOCK.acquire(blocking=False):
|
||||
write_error(self, HTTPStatus.TOO_MANY_REQUESTS, "model API is busy; try again later")
|
||||
return
|
||||
try:
|
||||
context = atlas_context(topic, topic_b)
|
||||
result = call_ollama(model, atlas_prompt_for(mode, context), mode)
|
||||
result = call_model(model, atlas_prompt_for(mode, context), mode)
|
||||
result.update(
|
||||
{
|
||||
"scope": cache_key,
|
||||
@@ -1701,7 +1729,7 @@ class PaperBrowserHandler(SimpleHTTPRequestHandler):
|
||||
cache_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
write_json(self, result)
|
||||
finally:
|
||||
OLLAMA_LOCK.release()
|
||||
MODEL_API_LOCK.release()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
write_error(self, HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
|
||||
|
||||
@@ -1719,7 +1747,7 @@ def main() -> int:
|
||||
load_papers()
|
||||
server = ThreadingHTTPServer((args.host, args.port), PaperBrowserHandler)
|
||||
print(f"Paper browser: http://{args.host}:{args.port}")
|
||||
print(f"Ollama: {OLLAMA_URL}")
|
||||
print(f"Model APIs: Luna={MODEL_API_LUNA_URL}, Sol/Terra={MODEL_API_SOL_URL}")
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
|
||||
+5
-4
@@ -15,10 +15,11 @@ write_environment() {
|
||||
{
|
||||
printf 'HOST=%s\n' "$HOST"
|
||||
printf 'PORT=%s\n' "$PORT"
|
||||
printf 'OLLAMA_URL=%s\n' "${OLLAMA_URL:-http://192.168.1.10:11434}"
|
||||
printf 'OLLAMA_MODEL_LIGHT=%s\n' "${OLLAMA_MODEL_LIGHT:-ChatGPT-5.6:Luna}"
|
||||
printf 'OLLAMA_MODEL_FAST=%s\n' "${OLLAMA_MODEL_FAST:-ChatGPT-5.6:Terra}"
|
||||
printf 'OLLAMA_MODEL_LARGE=%s\n' "${OLLAMA_MODEL_LARGE:-ChatGPT-5.6:Sol}"
|
||||
printf 'MODEL_API_LUNA_URL=%s\n' "${MODEL_API_LUNA_URL:-http://192.168.1.10:11435/v1}"
|
||||
printf 'MODEL_API_SOL_URL=%s\n' "${MODEL_API_SOL_URL:-http://192.168.1.10:11436/v1}"
|
||||
printf 'MODEL_LIGHT=%s\n' "${MODEL_LIGHT:-ChatGPT-5.6:Luna}"
|
||||
printf 'MODEL_FAST=%s\n' "${MODEL_FAST:-ChatGPT-5.6:Terra}"
|
||||
printf 'MODEL_LARGE=%s\n' "${MODEL_LARGE:-ChatGPT-5.6:Sol}"
|
||||
} >"$ENV_FILE"
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -141,12 +141,12 @@ async function loadHealth() {
|
||||
try {
|
||||
const health = await api("/api/health");
|
||||
state.health = health;
|
||||
const ollamaState = health.ollama_ready
|
||||
? "Ollama ready"
|
||||
: health.ollama_ok
|
||||
? "Ollama model mismatch"
|
||||
: "Ollama offline";
|
||||
elements.healthLine.textContent = `${health.paper_count} papers · ${ollamaState}`;
|
||||
const modelState = health.model_api_ready
|
||||
? "Model API ready"
|
||||
: health.model_api_ok
|
||||
? "Model API mismatch"
|
||||
: "Model API offline";
|
||||
elements.healthLine.textContent = `${health.paper_count} papers · ${modelState}`;
|
||||
} catch (error) {
|
||||
elements.healthLine.textContent = error.message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user