diff --git a/BillNote_frontend/src/App.tsx b/BillNote_frontend/src/App.tsx
index 4ee48b5..aa2ca95 100644
--- a/BillNote_frontend/src/App.tsx
+++ b/BillNote_frontend/src/App.tsx
@@ -2,10 +2,10 @@ import './App.css'
import { HomePage } from './pages/HomePage/Home.tsx'
import { useTaskPolling } from '@/hooks/useTaskPolling.ts'
import SettingPage from './pages/SettingPage/index.tsx'
-import { BrowserRouter,HashRouter, Navigate, Routes } from 'react-router-dom'
+import { BrowserRouter, HashRouter, Navigate, Routes } from 'react-router-dom'
import { Route } from 'react-router-dom'
import Index from '@/pages/Index.tsx'
-import NotFoundPage from '@/pages/NotFoundPage' //
+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'
@@ -15,15 +15,32 @@ import Prompt from '@/pages/SettingPage/Prompt.tsx'
import AboutPage from '@/pages/SettingPage/about.tsx'
import Downloader from '@/pages/SettingPage/Downloader.tsx'
import DownloaderForm from '@/components/Form/DownloaderForm/Form.tsx'
+import { useEffect } from 'react'
+import { systemCheck } from '@/services/system.ts'
+import { useCheckBackend } from '@/hooks/useCheckBackend.ts'
+import BackendInitDialog from '@/components/BackendInitDialog'
+
function App() {
useTaskPolling(3000) // 每 3 秒轮询一次
- const steps = [
- { label: '解析链接', key: 'PARSING', icon: },
- { label: '下载音频', key: 'DOWNLOADING' },
- { label: '转写文字', key: 'TRANSCRIBING' },
- { label: '总结内容', key: 'SUMMARIZING' },
- { label: '保存完成', key: 'SUCCESS' },
- ]
+ const { loading, initialized } = useCheckBackend()
+
+ // 在后端初始化完成后执行系统检查
+ useEffect(() => {
+ if (initialized) {
+ systemCheck()
+ }
+ }, [initialized])
+
+ // 如果后端还未初始化,显示初始化对话框
+ if (!initialized) {
+ return (
+ <>
+
+ >
+ )
+ }
+
+ // 后端已初始化,渲染主应用
return (
<>
@@ -34,16 +51,12 @@ function App() {
} />
}>
} />
- {/*} />*/}
} />
- {/*}>*/}
- {/*}>*/}
}>
} />
}>
-
} />
} />
@@ -54,4 +67,4 @@ function App() {
)
}
-export default App
+export default App
\ No newline at end of file
diff --git a/BillNote_frontend/src/components/BackendInitDialog.tsx b/BillNote_frontend/src/components/BackendInitDialog.tsx
new file mode 100644
index 0000000..09bd60a
--- /dev/null
+++ b/BillNote_frontend/src/components/BackendInitDialog.tsx
@@ -0,0 +1,23 @@
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Loader2 } from 'lucide-react'
+
+interface Props {
+ open: boolean
+}
+
+ function BackendInitDialog({ open }: Props) {
+ return (
+
+ )
+}
+export default BackendInitDialog
\ No newline at end of file
diff --git a/BillNote_frontend/src/components/ui/dialog.tsx b/BillNote_frontend/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..6cb123b
--- /dev/null
+++ b/BillNote_frontend/src/components/ui/dialog.tsx
@@ -0,0 +1,141 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/BillNote_frontend/src/hooks/useCheckBackend.ts b/BillNote_frontend/src/hooks/useCheckBackend.ts
new file mode 100644
index 0000000..52f0c7d
--- /dev/null
+++ b/BillNote_frontend/src/hooks/useCheckBackend.ts
@@ -0,0 +1,52 @@
+import { useEffect, useState } from 'react'
+import request from '@/utils/request'
+
+const MAX_RETRIES = 3
+const RETRY_INTERVAL = 10000 // 10秒
+
+export const useCheckBackend = () => {
+ const [loading, setLoading] = useState(false)
+ const [initialized, setInitialized] = useState(false)
+
+ useEffect(() => {
+ let retries = 0
+
+ const check = async () => {
+ try {
+ await request.get('/sys_health')
+ setInitialized(true)
+ setLoading(false)
+ } catch {
+ if (retries === 0) {
+ // 第一次失败时开始显示加载状态
+ setLoading(true)
+ }
+
+ if (retries < MAX_RETRIES) {
+ retries++
+ setTimeout(check, RETRY_INTERVAL)
+ } else {
+ // 达到重试上限,继续轮询直到后端就绪
+ waitUntilBackendReady()
+ }
+ }
+ }
+
+ const waitUntilBackendReady = async () => {
+ while (true) {
+ try {
+ await request.get('/sys_health')
+ setInitialized(true)
+ setLoading(false)
+ break
+ } catch {
+ await new Promise(res => setTimeout(res, RETRY_INTERVAL))
+ }
+ }
+ }
+
+ check()
+ }, [])
+
+ return { loading, initialized }
+}
\ No newline at end of file
diff --git a/BillNote_frontend/src/services/system.ts b/BillNote_frontend/src/services/system.ts
new file mode 100644
index 0000000..99a6b7c
--- /dev/null
+++ b/BillNote_frontend/src/services/system.ts
@@ -0,0 +1,5 @@
+import request from '@/utils/request'
+
+export const systemCheck=async()=>{
+ return await request.get('/sys_health')
+}
diff --git a/backend/app/gpt/prompt.py b/backend/app/gpt/prompt.py
index 0e11c6a..1f6011d 100644
--- a/backend/app/gpt/prompt.py
+++ b/backend/app/gpt/prompt.py
@@ -22,8 +22,8 @@ BASE_PROMPT = '''
- 或者使用 `## 1. 内容` 的形式作为标题。
请确保以下格式 **不会出现误渲染**:
-❌ `1. **xxx**`
-✅ `1\. **xxx**` 或 `## 1. xxx`
+ `1. **xxx**`
+ `1\. **xxx**` 或 `## 1. xxx`
视频分段(格式:开始时间 - 内容):
diff --git a/backend/app/gpt/universal_gpt.py b/backend/app/gpt/universal_gpt.py
index 49ced8a..568dbb1 100644
--- a/backend/app/gpt/universal_gpt.py
+++ b/backend/app/gpt/universal_gpt.py
@@ -52,7 +52,7 @@ class UniversalGPT(GPT):
}
})
- # ✅ 正确格式:整体包在一个 message 里,role + content array
+ # 正确格式:整体包在一个 message 里,role + content array
messages = [{
"role": "user",
"content": content
diff --git a/backend/app/models/audio_model.py b/backend/app/models/audio_model.py
index fb35ef1..d2808b5 100644
--- a/backend/app/models/audio_model.py
+++ b/backend/app/models/audio_model.py
@@ -11,5 +11,5 @@ class AudioDownloadResult:
platform: str # 平台,如 "bilibili"
video_id: str # 唯一视频ID
raw_info: dict # yt-dlp 的原始 info 字典
- video_path: Optional[str] = None # ✅ 新增字段:可选视频文件路径
+ video_path: Optional[str] = None # 新增字段:可选视频文件路径
diff --git a/backend/app/routers/config.py b/backend/app/routers/config.py
index f60eaec..8a43df0 100644
--- a/backend/app/routers/config.py
+++ b/backend/app/routers/config.py
@@ -4,6 +4,7 @@ from typing import Optional
from app.utils.response import ResponseWrapper as R
from app.services.cookie_manager import CookieConfigManager
+from ffmpeg_helper import ensure_ffmpeg_or_raise
router = APIRouter()
cookie_manager = CookieConfigManager()
@@ -30,3 +31,11 @@ def update_cookie(data: CookieUpdateRequest):
return R.success(
)
+
+@router.get("/sys_health")
+async def sys_health():
+ try:
+ ensure_ffmpeg_or_raise()
+ return R.success()
+ except EnvironmentError:
+ return R.error(msg="系统未安装 ffmpeg 请先进行安装")
diff --git a/backend/app/routers/note.py b/backend/app/routers/note.py
index c80d9e0..42bb992 100644
--- a/backend/app/routers/note.py
+++ b/backend/app/routers/note.py
@@ -238,7 +238,7 @@ async def image_proxy(request: Request, url: str):
resp.aiter_bytes(),
media_type=content_type,
headers={
- "Cache-Control": "public, max-age=86400", # ✅ 缓存一天
+ "Cache-Control": "public, max-age=86400", # 缓存一天
"Content-Type": content_type,
}
)
diff --git a/backend/app/routers/provider.py b/backend/app/routers/provider.py
index 5ded464..942a362 100644
--- a/backend/app/routers/provider.py
+++ b/backend/app/routers/provider.py
@@ -10,7 +10,7 @@ from app.services.provider import ProviderService
router = APIRouter()
-# ✅ 新增 type 字段
+# 新增 type 字段
class ProviderRequest(BaseModel):
name: str
api_key: str
diff --git a/backend/app/transcriber/whisper.py b/backend/app/transcriber/whisper.py
index 7b438f4..4fb4b50 100644
--- a/backend/app/transcriber/whisper.py
+++ b/backend/app/transcriber/whisper.py
@@ -79,13 +79,13 @@ class WhisperTranscriber(Transcriber):
def is_cuda() -> bool:
try:
if is_cuda_available():
- print("✅ CUDA 可用,使用 GPU")
+ print(" CUDA 可用,使用 GPU")
return True
elif is_torch_installed():
print("⚠️ 只装了 torch,但没有 CUDA,用 CPU")
return False
else:
- print("❌ 还没有安装 torch,请先安装")
+ print(" 还没有安装 torch,请先安装")
return False
except ImportError:
diff --git a/backend/ffmpeg_helper.py b/backend/ffmpeg_helper.py
index e2f3608..b116aee 100644
--- a/backend/ffmpeg_helper.py
+++ b/backend/ffmpeg_helper.py
@@ -31,7 +31,7 @@ def ensure_ffmpeg_or_raise():
if not check_ffmpeg_exists():
logger.error("未检测到 ffmpeg,请先安装后再使用本功能。")
raise EnvironmentError(
- "❌ 未检测到 ffmpeg,请先安装后再使用本功能。\n"
+ " 未检测到 ffmpeg,请先安装后再使用本功能。\n"
"👉 下载地址:https://ffmpeg.org/download.html\n"
"🪟 Windows 推荐:https://www.gyan.dev/ffmpeg/builds/\n"
"💡 如果你已安装,请将其路径写入 `.env` 文件,例如:\n"
diff --git a/backend/main.py b/backend/main.py
index 981df38..fc15226 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -39,7 +39,6 @@ if not os.path.exists(out_dir):
@asynccontextmanager
async def lifespan(app: FastAPI):
register_handler()
- ensure_ffmpeg_or_raise()
init_db()
get_transcriber(transcriber_type=os.getenv("TRANSCRIBER_TYPE", "fast-whisper"))
seed_default_providers()
@@ -48,7 +47,7 @@ async def lifespan(app: FastAPI):
app = create_app(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
- allow_origins=["tauri://localhost"], # ✅ 加上 Tauri 的 origin
+ allow_origins=["tauri://localhost"], # 加上 Tauri 的 origin
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
diff --git a/doc/wechat.png b/doc/wechat.png
index 4e2e4a9..f88ccf1 100644
Binary files a/doc/wechat.png and b/doc/wechat.png differ