feat(desktop): 后端健康监控韧性 + onboarding 修复 + 全局代理 UI

- useCheckBackend 重写:60s 总超时取代 while(true) 死轮询,订阅 Tauri
  backend-ready/terminated/startup-timeout 事件,裸 fetch 探测避免
  启动期 toast 叠堆
- Tauri lib.rs:spawn 后 HTTP 探针轮询 /api/sys_check 拿 200 才算就绪
  (之前 TCP connect 会被孤儿进程误判);RunEvent::Exit 钩子退出前
  kill sidecar,修孤儿进程占端口;restart 前发 backend-restarting
  让前端忽略主动 kill 引发的 terminated
- BackendInitDialog:失败态展示原因 + 最近 stderr + 重启/复制日志按钮
- StartupBanner:收到 restarted/ready 自动清「已退出」横幅
- BackendHealthIndicator:修 /api/api/sys_health 双前缀 404
- Onboarding:step1 后端连通改自动重试 + 事件触发 + 手动按钮;step2
  撞预置供应商名时改为更新已存在供应商;errText 统一错误文案
- 全局代理 UI:下载配置页新增代理卡片(services/proxy.ts + ProxyConfig)
- request.ts 加 suppressToast 配置位,预期失败不弹全局红 toast
- NoteForm/taskStore:捕获就绪门禁错误,引导去音频转写配置页下载
- providerCard:整行可点切换(之前只有 icon 区域响应)
- Monitor 页 Whisper 卡显示模型本地下载状态
- tauri/api 升级对齐 2.11,修 vite build 版本不匹配

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
huangjianwu
2026-05-14 19:01:37 +08:00
parent 41f17592c2
commit 37f7ee6e15
22 changed files with 1273 additions and 651 deletions

View File

@@ -1,21 +1,26 @@
import request from '@/utils/request.ts'
export const getProviderList = async () => {
return await request.get('/get_all_providers')
// opts.silent: 让本次请求失败时不弹全局红 toast调用方自行 catch 处理,
// 比如 onboarding 撞名重试这种预期内失败)
interface CallOpts { silent?: boolean }
const cfg = (o?: CallOpts) => (o?.silent ? { suppressToast: true } : undefined)
export const getProviderList = async (opts?: CallOpts) => {
return await request.get('/get_all_providers', cfg(opts))
}
export const getProviderById = async (id: string) => {
return await request.get(`/get_provider_by_id/${id}`)
}
export const updateProviderById = async (data: any) => {
return await request.post('/update_provider', data)
export const updateProviderById = async (data: any, opts?: CallOpts) => {
return await request.post('/update_provider', data, cfg(opts))
}
export const addProvider = async (data: any) => {
return await request.post('/add_provider', data)
export const addProvider = async (data: any, opts?: CallOpts) => {
return await request.post('/add_provider', data, cfg(opts))
}
export const testConnection = async (data: any) => {
return await request.post('/connect_test', data)
export const testConnection = async (data: any, opts?: CallOpts) => {
return await request.post('/connect_test', data, cfg(opts))
}
export const fetchModels = async (providerId: string) => {
@@ -26,8 +31,11 @@ export const fetchEnableModelById = async (id: string) => {
return await request.get('/model_enable/' + id)
}
export async function addModel(data: { provider_id: string; model_name: string }) {
return request.post('/models', data)
export async function addModel(
data: { provider_id: string; model_name: string },
opts?: CallOpts,
) {
return request.post('/models', data, cfg(opts))
}
export const fetchEnableModels = async () => {

View File

@@ -0,0 +1,19 @@
import request from '@/utils/request'
export interface ProxyConfig {
enabled: boolean
url: string
/** 后端实际生效的代理(可能来自配置,也可能来自 HTTP_PROXY 环境变量兜底) */
effective: string
}
export const getProxyConfig = async (): Promise<ProxyConfig> => {
return await request.get('/proxy_config')
}
export const updateProxyConfig = async (data: {
enabled: boolean
url?: string
}): Promise<ProxyConfig> => {
return await request.post('/proxy_config', data)
}

View File

@@ -1,9 +1,29 @@
import request from '@/utils/request'
export const systemCheck = async () => {
export interface SysHealth {
backend: 'ok' | 'error'
ffmpeg: 'ok' | 'missing'
db: 'ok' | 'error'
whisper_model: {
/** 当前选中的模型 size例如 'tiny' / 'base' / 'large-v3' */
size: string | null
/** 转写器类型 */
type: string | null
/** 是否已完整下载到本地(仅本地引擎有意义) */
downloaded: boolean
/** 是否实际检查过 —— 在线引擎跳过检查时为 false */
checked: boolean
}
}
/** 详细健康状态:用于设置页 / 启动诊断。后端始终返回 200按字段判断各项。 */
export const getSysHealth = async (): Promise<SysHealth> => {
return await request.get('/sys_health')
}
/** 保留旧 systemCheck 函数名App.tsx 启动时仍调用),返回值同 getSysHealth。 */
export const systemCheck = getSysHealth
export interface DeployStatus {
backend: {
status: string
@@ -11,12 +31,16 @@ export interface DeployStatus {
}
cuda: {
available: boolean
/** 新增torch 是否安装。轻量部署没装 torch 时为 false避免误判为 CUDA 故障 */
torch_installed?: boolean
version: string | null
gpu_name: string | null
}
whisper: {
model_size: string
transcriber_type: string
/** 新增模型是否已完整下载fast-whisper 看 model.bin / mlx 看 config.json */
downloaded: boolean
}
ffmpeg: {
available: boolean