Optimize tablet load: defer health check, lighten service worker, drop Google Fonts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -197,9 +197,14 @@ PWA_HEAD = """
|
||||
var deferredPrompt = null;
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function () {
|
||||
function registerSW() {
|
||||
navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(function () {});
|
||||
});
|
||||
}
|
||||
if ("requestIdleCallback" in window) {
|
||||
requestIdleCallback(registerSW, { timeout: 5000 });
|
||||
} else {
|
||||
setTimeout(registerSW, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function isStandalone() {
|
||||
@@ -711,24 +716,37 @@ def _status_html(title: str, message: str, level: str = "warn") -> str:
|
||||
)
|
||||
|
||||
|
||||
def ui_check_ollama_html() -> str:
|
||||
ok, msg = check_ollama_health()
|
||||
def ui_check_ollama_html(force: bool = False) -> str:
|
||||
ok, msg = check_ollama_health(force=force)
|
||||
return _status_html("Ollama 节点", msg, "ok" if ok else "err")
|
||||
|
||||
|
||||
def ui_initial_load() -> tuple[str, str]:
|
||||
"""首屏立即返回,不发起网络请求,避免平板白屏等待。"""
|
||||
return (
|
||||
_status_html("Ollama 节点", "后台检测中,请稍候…", "warn"),
|
||||
ui_speaker_status_html(),
|
||||
)
|
||||
|
||||
|
||||
def ui_refresh_status_html(force: bool = False) -> tuple[str, str]:
|
||||
"""刷新 Ollama + 音色状态(供 Timer / 按钮调用)。"""
|
||||
return ui_check_ollama_html(force=force), ui_speaker_status_html()
|
||||
|
||||
|
||||
def ui_speaker_status_html() -> str:
|
||||
ok, msg = speaker_is_ready()
|
||||
return _status_html("音色状态", msg, "ok" if ok else "warn")
|
||||
|
||||
|
||||
def build_theme() -> gr.themes.Base:
|
||||
"""高对比度暗色主题(Gradio 6.0 需在 launch() 传入)。"""
|
||||
"""高对比度暗色主题;使用系统字体,避免平板拉取 Google Fonts 卡顿。"""
|
||||
return gr.themes.Base(
|
||||
primary_hue="blue",
|
||||
secondary_hue="blue",
|
||||
neutral_hue="slate",
|
||||
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
||||
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "Consolas", "monospace"],
|
||||
font=["system-ui", "-apple-system", "Segoe UI", "Roboto", "sans-serif"],
|
||||
font_mono=["Consolas", "Monaco", "Courier New", "monospace"],
|
||||
).set(
|
||||
body_background_fill="#0f1419",
|
||||
body_background_fill_dark="#0f1419",
|
||||
@@ -780,7 +798,25 @@ def build_app() -> gr.Blocks:
|
||||
refresh_btn = gr.Button("🔄 刷新状态", variant="secondary", scale=0, min_width=120)
|
||||
|
||||
refresh_btn.click(
|
||||
fn=lambda: (ui_check_ollama_html(), ui_speaker_status_html()),
|
||||
fn=lambda: ui_refresh_status_html(force=True),
|
||||
outputs=[ollama_status, speaker_status],
|
||||
)
|
||||
|
||||
# 首屏秒开:仅本地检测音色,Ollama 延后到 Timer
|
||||
demo.load(
|
||||
fn=ui_initial_load,
|
||||
outputs=[ollama_status, speaker_status],
|
||||
)
|
||||
|
||||
# 1 秒后后台检测 Ollama;之后每 30s 刷新(30s 内走缓存)
|
||||
status_timer = gr.Timer(value=1, active=True)
|
||||
status_timer.tick(
|
||||
fn=lambda: ui_refresh_status_html(force=False),
|
||||
outputs=[ollama_status, speaker_status],
|
||||
)
|
||||
status_timer_slow = gr.Timer(value=30, active=True)
|
||||
status_timer_slow.tick(
|
||||
fn=lambda: ui_refresh_status_html(force=True),
|
||||
outputs=[ollama_status, speaker_status],
|
||||
)
|
||||
|
||||
@@ -886,11 +922,6 @@ def build_app() -> gr.Blocks:
|
||||
[pipe_raw, pipe_polished, pipe_output, pipeline_log],
|
||||
)
|
||||
|
||||
demo.load(
|
||||
fn=lambda: (ui_check_ollama_html(), ui_speaker_status_html()),
|
||||
outputs=[ollama_status, speaker_status],
|
||||
)
|
||||
|
||||
return demo
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ PORT = 5683
|
||||
# HTTP 请求超时(秒)
|
||||
OLLAMA_TIMEOUT = 60
|
||||
|
||||
# 健康检查快速超时(秒)— 避免平板首屏被长时间阻塞
|
||||
HEALTH_CHECK_CONNECT_TIMEOUT = 2
|
||||
HEALTH_CHECK_READ_TIMEOUT = 3
|
||||
HEALTH_CHECK_CACHE_SECONDS = 30
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LLM 系统 Prompt
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
+44
-8
@@ -6,14 +6,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Tuple
|
||||
|
||||
import requests
|
||||
|
||||
from config import MODEL_NAME, OLLAMA_TIMEOUT, OLLAMA_URL, SYSTEM_PROMPT
|
||||
from config import (
|
||||
HEALTH_CHECK_CACHE_SECONDS,
|
||||
HEALTH_CHECK_CONNECT_TIMEOUT,
|
||||
HEALTH_CHECK_READ_TIMEOUT,
|
||||
MODEL_NAME,
|
||||
OLLAMA_TIMEOUT,
|
||||
OLLAMA_URL,
|
||||
SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 健康检查短时缓存,避免平板/手机反复打开页面时重复等待
|
||||
_health_cache: dict = {"ts": 0.0, "ok": False, "msg": ""}
|
||||
|
||||
|
||||
def _build_payload(raw_text: str) -> dict:
|
||||
"""构造 Ollama /api/chat 非流式请求体。"""
|
||||
@@ -139,24 +151,48 @@ def polish_text(raw_text: str) -> Tuple[bool, str]:
|
||||
return False, err
|
||||
|
||||
|
||||
def check_ollama_health() -> Tuple[bool, str]:
|
||||
def check_ollama_health(force: bool = False) -> Tuple[bool, str]:
|
||||
"""
|
||||
快速检测 Ollama 节点是否在线(不触发完整推理)。
|
||||
默认 2+3 秒超时,结果缓存 30 秒,避免平板首屏长时间白屏。
|
||||
|
||||
Returns:
|
||||
(online, message)
|
||||
"""
|
||||
global _health_cache
|
||||
|
||||
now = time.time()
|
||||
if (
|
||||
not force
|
||||
and _health_cache["msg"]
|
||||
and (now - _health_cache["ts"]) < HEALTH_CHECK_CACHE_SECONDS
|
||||
):
|
||||
return _health_cache["ok"], _health_cache["msg"]
|
||||
|
||||
base_url = OLLAMA_URL.rsplit("/api/", 1)[0]
|
||||
timeout = (HEALTH_CHECK_CONNECT_TIMEOUT, HEALTH_CHECK_READ_TIMEOUT)
|
||||
|
||||
try:
|
||||
resp = requests.get(f"{base_url}/api/tags", timeout=10)
|
||||
resp = requests.get(f"{base_url}/api/tags", timeout=timeout)
|
||||
resp.raise_for_status()
|
||||
tags = resp.json().get("models", [])
|
||||
model_names = [m.get("name", "") for m in tags]
|
||||
if any(MODEL_NAME.split(":")[0] in name for name in model_names):
|
||||
return True, f"Ollama 在线,已检测到模型: {MODEL_NAME}"
|
||||
return True, (
|
||||
f"Ollama 在线,但未找到模型 {MODEL_NAME},"
|
||||
f"请执行: ollama pull {MODEL_NAME}"
|
||||
msg = f"Ollama 在线,已检测到模型: {MODEL_NAME}"
|
||||
ok = True
|
||||
else:
|
||||
ok = True
|
||||
msg = (
|
||||
f"Ollama 在线,但未找到模型 {MODEL_NAME},"
|
||||
f"请执行: ollama pull {MODEL_NAME}"
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
ok, msg = False, (
|
||||
f"Ollama 检测超时(>{HEALTH_CHECK_READ_TIMEOUT}s)。"
|
||||
"页面已加载,可稍后点击「刷新状态」重试。"
|
||||
)
|
||||
except Exception as exc:
|
||||
return False, f"Ollama 不可达: {exc}"
|
||||
ok, msg = False, f"Ollama 不可达: {exc}"
|
||||
|
||||
_health_cache.update({"ts": now, "ok": ok, "msg": msg})
|
||||
return ok, msg
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
/**
|
||||
* Trading Studio PWA Service Worker
|
||||
* 缓存应用壳,支持离线打开已访问页面;API 请求始终走网络。
|
||||
* Trading Studio PWA Service Worker(轻量版)
|
||||
* 仅处理导航请求,不拦截 Gradio 静态资源 — 避免平板端加载变慢。
|
||||
*/
|
||||
const CACHE_NAME = "trading-studio-v1";
|
||||
const SHELL = ["/", "/manifest.webmanifest"];
|
||||
const CACHE_NAME = "trading-studio-v2";
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL)).catch(() => {})
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
@@ -23,28 +19,13 @@ self.addEventListener("activate", (event) => {
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// API / 文件上传 / Gradio 动态接口不走缓存
|
||||
if (
|
||||
request.method !== "GET" ||
|
||||
url.pathname.startsWith("/gradio_api") ||
|
||||
url.pathname.startsWith("/file=") ||
|
||||
url.pathname.startsWith("/upload") ||
|
||||
url.pathname.includes("call")
|
||||
) {
|
||||
// 仅缓存页面导航,Gradio JS/CSS/API 全部直连网络
|
||||
if (request.method !== "GET" || request.mode !== "navigate") {
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then((response) => {
|
||||
if (response.ok && url.origin === self.location.origin) {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match(request).then((r) => r || caches.match("/")))
|
||||
fetch(request).catch(() => caches.match("/"))
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user