Files
BiliNote/BillNote_frontend/src/App.tsx
huangjianwu 1329390f98 feat(desktop): Sidecar 健康度面板 + 重启后端能力
P1 已经把 backend-warning / backend-terminated 横幅做出来了;P2 把
lib.rs 那条 stdout/stderr/terminated 信息流真正落到一个常驻 UI 上:
- 右下角浮动状态点(绿/黄/红),轮询 /api/sys_health 决定颜色
- 点开抽屉看最近 200 行日志(ring buffer),含「重启后端」「复制日志」按钮

Rust:
- src-tauri/src/lib.rs:把 sidecar 启动抽出 spawn_backend_sidecar(),
  CommandChild 存进 SidecarHandle(Mutex<Option<CommandChild>>) 这个 state
- 新增 #[tauri::command] restart_backend_sidecar:kill 旧 child + 重新 spawn +
  emit 'backend-restarted' 给前端
- 监听任务 stdout/stderr emit 时不再用 format!("'{}'", ...) 包引号,原文直传;
  前端 hook 同时兼容旧形式(兜底剥引号)

前端:
- components/BackendHealth/useBackendEvents.ts:listen 四个事件 +
  ring buffer (MAX 200 行) + invoke restart + clipboard 复制日志
- BackendHealthIndicator.tsx:右下角浮动状态点,5s 轮询 /api/sys_health;
  连续 3 次失败或 backend-terminated 触发 → 红
- BackendLogPanel.tsx:右侧抽屉,深色 monospace 日志区 + 操作按钮
- 纯 web 环境(无 __TAURI_INTERNALS__)下静默不挂载

P3 / P4 还在路上。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:27:02 +08:00

79 lines
3.1 KiB
TypeScript

import './App.css'
import { lazy, Suspense, useEffect } from 'react'
import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom'
import { useTaskPolling } from '@/hooks/useTaskPolling.ts'
import { useCheckBackend } from '@/hooks/useCheckBackend.ts'
import { systemCheck } from '@/services/system.ts'
import BackendInitDialog from '@/components/BackendInitDialog'
import StartupBanner from '@/components/SystemDiagnostic/StartupBanner'
import BackendHealthIndicator from '@/components/BackendHealth/BackendHealthIndicator'
import Index from '@/pages/Index.tsx'
import { HomePage } from './pages/HomePage/Home.tsx'
// 非首屏页面使用 React.lazy 按需加载
const SettingPage = lazy(() => import('./pages/SettingPage/index.tsx'))
const Model = lazy(() => import('@/pages/SettingPage/Model.tsx'))
const ProviderForm = lazy(() => import('@/components/Form/modelForm/Form.tsx'))
const AboutPage = lazy(() => import('@/pages/SettingPage/about.tsx'))
const Monitor = lazy(() => import('@/pages/SettingPage/Monitor.tsx'))
const Downloader = lazy(() => import('@/pages/SettingPage/Downloader.tsx'))
const DownloaderForm = lazy(() => import('@/components/Form/DownloaderForm/Form.tsx'))
const TranscriberPage = lazy(() => import('@/pages/SettingPage/transcriber.tsx'))
const NotFoundPage = lazy(() => import('@/pages/NotFoundPage'))
function App() {
useTaskPolling(3000) // 每 3 秒轮询一次
const { loading, initialized } = useCheckBackend()
// 在后端初始化完成后执行系统检查
useEffect(() => {
if (initialized) {
systemCheck()
}
}, [initialized])
// 如果后端还未初始化,显示初始化对话框
if (!initialized) {
return (
<>
<StartupBanner />
<BackendInitDialog open={loading} />
</>
)
}
// 后端已初始化,渲染主应用
return (
<>
<StartupBanner />
<BackendHealthIndicator />
<BrowserRouter>
<Suspense fallback={<div className="flex h-screen items-center justify-center"></div>}>
<Routes>
<Route path="/" element={<Index />}>
<Route index element={<HomePage />} />
<Route path="settings" element={<SettingPage />}>
<Route index element={<Navigate to="model" replace />} />
<Route path="model" element={<Model />}>
<Route path="new" element={<ProviderForm isCreate />} />
<Route path=":id" element={<ProviderForm />} />
</Route>
<Route path="download" element={<Downloader />}>
<Route path=":id" element={<DownloaderForm />} />
</Route>
<Route path="transcriber" element={<TranscriberPage />} />
<Route path="monitor" element={<Monitor />}></Route>
<Route path="about" element={<AboutPage />}></Route>
<Route path="*" element={<NotFoundPage />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</Suspense>
</BrowserRouter>
</>
)
}
export default App