From 8cd8c6f7b48608498c02ba8fdff12f591dcb2eab Mon Sep 17 00:00:00 2001 From: sibuchen Date: Fri, 6 Feb 2026 16:15:11 +0800 Subject: [PATCH 1/2] feat: add deployment monitor page - Add /deploy_status API endpoint for system status check - Create Monitor.tsx component with real-time status display - Support CUDA, FFmpeg, Whisper model status monitoring - Auto-refresh every 30 seconds with manual refresh option --- BillNote_frontend/src/App.tsx | 6 +- .../src/pages/SettingPage/Menu.tsx | 10 +- .../src/pages/SettingPage/Monitor.tsx | 241 ++++++++++++++++++ BillNote_frontend/src/services/system.ts | 26 +- backend/app/routers/config.py | 35 ++- 5 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 BillNote_frontend/src/pages/SettingPage/Monitor.tsx diff --git a/BillNote_frontend/src/App.tsx b/BillNote_frontend/src/App.tsx index d9ecd69..83334ea 100644 --- a/BillNote_frontend/src/App.tsx +++ b/BillNote_frontend/src/App.tsx @@ -7,12 +7,9 @@ import { Route } from 'react-router-dom' import Index from '@/pages/Index.tsx' import NotFoundPage from '@/pages/NotFoundPage' import Model from '@/pages/SettingPage/Model.tsx' -import Transcriber from '@/pages/SettingPage/transcriber.tsx' import ProviderForm from '@/components/Form/modelForm/Form.tsx' -import StepBar from '@/pages/HomePage/components/StepBar.tsx' -import Downloading from '@/components/Lottie/download.tsx' -import Prompt from '@/pages/SettingPage/Prompt.tsx' import AboutPage from '@/pages/SettingPage/about.tsx' +import Monitor from '@/pages/SettingPage/Monitor.tsx' import Downloader from '@/pages/SettingPage/Downloader.tsx' import DownloaderForm from '@/components/Form/DownloaderForm/Form.tsx' import { useEffect } from 'react' @@ -56,6 +53,7 @@ function App() { }> } /> + }> }> } /> diff --git a/BillNote_frontend/src/pages/SettingPage/Menu.tsx b/BillNote_frontend/src/pages/SettingPage/Menu.tsx index 11b5f32..3618ab0 100644 --- a/BillNote_frontend/src/pages/SettingPage/Menu.tsx +++ b/BillNote_frontend/src/pages/SettingPage/Menu.tsx @@ -1,10 +1,8 @@ import { BotMessageSquare, - SquareChevronRight, - Captions, HardDriveDownload, - Wrench, Info, + Activity, } from 'lucide-react' import MenuBar, { IMenuProps } from '@/pages/SettingPage/components/menuBar.tsx' @@ -37,6 +35,12 @@ const Menu = () => { // icon: , // path: '/settings/prompt', // }, + { + id: 'monitor', + name: '部署监控', + icon: , + path: '/settings/monitor', + }, { id: 'about', name: '关于', diff --git a/BillNote_frontend/src/pages/SettingPage/Monitor.tsx b/BillNote_frontend/src/pages/SettingPage/Monitor.tsx new file mode 100644 index 0000000..f806606 --- /dev/null +++ b/BillNote_frontend/src/pages/SettingPage/Monitor.tsx @@ -0,0 +1,241 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { ScrollArea } from '@/components/ui/scroll-area' +import { + Server, + Cpu, + AudioLines, + Film, + RefreshCw, + CheckCircle2, + XCircle, + Loader2 +} from 'lucide-react' +import { useState, useEffect, useCallback } from 'react' +import { getDeployStatus, DeployStatus } from '@/services/system' + +export default function Monitor() { + const [status, setStatus] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [lastUpdated, setLastUpdated] = useState(null) + + const fetchStatus = useCallback(async () => { + try { + setLoading(true) + setError(null) + const data = await getDeployStatus() + setStatus(data) + setLastUpdated(new Date()) + } catch (err) { + setError('无法连接到后端服务') + setStatus(null) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + fetchStatus() + // 自动刷新(每 30 秒) + const interval = setInterval(fetchStatus, 30000) + return () => clearInterval(interval) + }, [fetchStatus]) + + const StatusBadge = ({ ok, label }: { ok: boolean; label?: string }) => ( + + {ok ? ( + <>{label || '正常'} + ) : ( + <>{label || '异常'} + )} + + ) + + return ( + +
+ {/* Header */} +
+
+

部署监控

+

+ 实时监控系统各组件运行状态 +

+
+
+ {lastUpdated && ( + + 最后更新: {lastUpdated.toLocaleTimeString()} + + )} + +
+
+ + {error && ( +
+ {error} +
+ )} + + {/* Status Cards */} +
+ {/* Backend FastAPI */} + + + + + 后端 FastAPI + + {status && } + + + {loading && !status ? ( +
+ + 加载中... +
+ ) : status ? ( +
+
+ 状态: + + {status.backend.status === 'running' ? '运行中' : status.backend.status} + +
+
+ 端口: + {status.backend.port} +
+
+ ) : null} +
+
+ + {/* CUDA GPU */} + + + + + CUDA GPU + + {status && } + + + {loading && !status ? ( +
+ + 加载中... +
+ ) : status ? ( +
+ {status.cuda.available ? ( + <> +
+ GPU: + {status.cuda.gpu_name} +
+
+ CUDA 版本: + {status.cuda.version} +
+ + ) : ( +
+ CUDA 不可用,将使用 CPU 模式 +
+ )} +
+ ) : null} +
+
+ + {/* Whisper Model */} + + + + + Whisper 模型 + + {status && } + + + {loading && !status ? ( +
+ + 加载中... +
+ ) : status ? ( +
+
+ 模型大小: + {status.whisper.model_size} +
+
+ 转写引擎: + {status.whisper.transcriber_type} +
+
+ ) : null} +
+
+ + {/* FFmpeg */} + + + + + FFmpeg + + {status && } + + + {loading && !status ? ( +
+ + 加载中... +
+ ) : status ? ( +
+
+ 状态: + + {status.ffmpeg.available ? '已安装' : '未安装'} + +
+ {!status.ffmpeg.available && ( +
+ 请安装 FFmpeg 并添加到系统 PATH +
+ )} +
+ ) : null} +
+
+
+ + {/* Footer Info */} +
+ 状态每 30 秒自动刷新 +
+
+
+ ) +} diff --git a/BillNote_frontend/src/services/system.ts b/BillNote_frontend/src/services/system.ts index 99a6b7c..4f54c97 100644 --- a/BillNote_frontend/src/services/system.ts +++ b/BillNote_frontend/src/services/system.ts @@ -1,5 +1,29 @@ import request from '@/utils/request' -export const systemCheck=async()=>{ +export const systemCheck = async () => { return await request.get('/sys_health') } + +export interface DeployStatus { + backend: { + status: string + port: number + } + cuda: { + available: boolean + version: string | null + gpu_name: string | null + } + whisper: { + model_size: string + transcriber_type: string + } + ffmpeg: { + available: boolean + } +} + +export const getDeployStatus = async (): Promise => { + return await request.get('/deploy_status') +} + diff --git a/backend/app/routers/config.py b/backend/app/routers/config.py index 350f104..eee01da 100644 --- a/backend/app/routers/config.py +++ b/backend/app/routers/config.py @@ -42,4 +42,37 @@ async def sys_health(): @router.get("/sys_check") async def sys_check(): - return R.success() \ No newline at end of file + return R.success() + + +@router.get("/deploy_status") +async def deploy_status(): + """返回部署监控所需的所有状态信息""" + import torch + import os + + # CUDA 状态 + cuda_available = torch.cuda.is_available() + cuda_info = { + "available": cuda_available, + "version": torch.version.cuda if cuda_available else None, + "gpu_name": torch.cuda.get_device_name(0) if cuda_available else None, + } + + # Whisper 模型状态 + model_size = os.getenv("WHISPER_MODEL_SIZE", "base") + transcriber_type = os.getenv("TRANSCRIBER_TYPE", "fast-whisper") + + # FFmpeg 状态 + try: + ensure_ffmpeg_or_raise() + ffmpeg_ok = True + except: + ffmpeg_ok = False + + return R.success(data={ + "backend": {"status": "running", "port": int(os.getenv("BACKEND_PORT", 8483))}, + "cuda": cuda_info, + "whisper": {"model_size": model_size, "transcriber_type": transcriber_type}, + "ffmpeg": {"available": ffmpeg_ok}, + }) \ No newline at end of file From 7fb4fcba77444ea5b3a861fc13150d45f635435a Mon Sep 17 00:00:00 2001 From: wanderer99176 Date: Wed, 25 Feb 2026 11:57:16 +0800 Subject: [PATCH 2/2] fix: update bilibili timestamp link format to - [MM:SS](URL#t=MM:SS) --- .env.example | 4 ++-- BillNote_frontend/vite.config.ts | 2 +- backend/app/utils/note_helper.py | 32 +++++++++++++++++++------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 4687929..e2ca898 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ FRONTEND_PORT=3015 BACKEND_HOST=0.0.0.0 # 默认为 0.0.0.0,表示监听所有 IP 地址 不建议动 APP_PORT= 3015 # docker 部署时用 # 前端访问后端用 (开发环境使用) -VITE_API_BASE_URL=http://127.0.0.1:8483 +VITE_API_BASE_URL=http://127.0.0.1:8000 VITE_SCREENSHOT_BASE_URL=http://127.0.0.1:8483/static/screenshots VITE_FRONTEND_PORT=3015 # 生产环境配置 @@ -19,6 +19,6 @@ FFMPEG_BIN_PATH= # transcriber 相关配置 TRANSCRIBER_TYPE=fast-whisper # fast-whisper/bcut/kuaishou/mlx-whisper(仅Apple平台)/groq -WHISPER_MODEL_SIZE=base +WHISPER_MODEL_SIZE=medium GROQ_TRANSCRIBER_MODEL=whisper-large-v3-turbo # groq提供的faster-whisper 默认为 whisper-large-v3-turbo diff --git a/BillNote_frontend/vite.config.ts b/BillNote_frontend/vite.config.ts index f42f945..a7785ba 100644 --- a/BillNote_frontend/vite.config.ts +++ b/BillNote_frontend/vite.config.ts @@ -7,7 +7,7 @@ import tailwindcss from '@tailwindcss/vite' export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd() + '/../') - const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000' + const apiBaseUrl = env.VITE_API_BASE_URL || 'http://127.0.0.1:8483' const port = parseInt(env.VITE_FRONTEND_PORT || '3015', 10) return { diff --git a/backend/app/utils/note_helper.py b/backend/app/utils/note_helper.py index 3621b9d..8987fdc 100644 --- a/backend/app/utils/note_helper.py +++ b/backend/app/utils/note_helper.py @@ -1,13 +1,9 @@ import re - -import re - -import re - def replace_content_markers(markdown: str, video_id: str, platform: str = 'bilibili') -> str: """ - 替换 *Content-04:16*、Content-04:16 或 Content-[04:16] 为超链接,跳转到对应平台视频的时间位置 + 替换 *Content-04:16*、Content-04:16 或 Content-[04:16] 为超链接 + 目标格式:- [04:16](https://www.bilibili.com/video/BVxxx?t=256#t=04:16) """ # 匹配三种形式:*Content-04:16*、Content-04:16、Content-[04:16] pattern = r"(?:\*?)Content-(?:\[(\d{2}):(\d{2})\]|(\d{2}):(\d{2}))" @@ -16,18 +12,28 @@ def replace_content_markers(markdown: str, video_id: str, platform: str = 'bilib mm = match.group(1) or match.group(3) ss = match.group(2) or match.group(4) total_seconds = int(mm) * 60 + int(ss) + time_str = f"{mm}:{ss}" if platform == 'bilibili': - video_id = video_id.replace("_p", "?p=") - url = f"https://www.bilibili.com/video/{video_id}&t={total_seconds}" + # 处理多 P 情况,如果是 BV123_p3 转换为 BV123?p=3 + actual_video_id = video_id.replace("_p", "?p=") + + # 判断连接符是 ? 还是 & (如果 video_id 里已经有了 ?p=,则时间参数用 &t=) + connector = "&t=" if "?" in actual_video_id else "?t=" + + # 拼接最终 URL,并在末尾加上 #t=MM:SS 锚点 + url = f"https://www.bilibili.com/video/{actual_video_id}{connector}{total_seconds}#t={time_str}" + return f"- [{time_str}]({url})" + elif platform == 'youtube': url = f"https://www.youtube.com/watch?v={video_id}&t={total_seconds}s" + return f"- [{time_str}]({url})" + elif platform == 'douyin': url = f"https://www.douyin.com/video/{video_id}" - return f"[原片 @ {mm}:{ss}]({url})" + return f"[原片 @ {time_str}]({url})" + else: - return f"({mm}:{ss})" + return f"({time_str})" - return f"[原片 @ {mm}:{ss}]({url})" - - return re.sub(pattern, replacer, markdown) + return re.sub(pattern, replacer, markdown) \ No newline at end of file