diff --git a/electron/core/PythonManager.ts b/electron/core/PythonManager.ts new file mode 100644 index 0000000..0e67948 --- /dev/null +++ b/electron/core/PythonManager.ts @@ -0,0 +1,100 @@ +import http from "node:http"; +import path from "node:path"; +import { spawn, ChildProcess } from "node:child_process"; +import { BrowserWindow } from "electron"; + +class PythonManager { + private flaskProcess: ChildProcess | null = null; + private intervalId: NodeJS.Timeout | null = null; + + constructor( + private mainWindow: BrowserWindow | null, + private url: string, + private interval = 5000 + ) {} + + // 启动 Python 服务 + public startFlask() { + if (this.flaskProcess) { + console.log("Flask service is already running."); + return; + } + + // 使用 spawn 启动 Flask 服务 + this.flaskProcess = spawn(path.join(process.env.VITE_PUBLIC!, "flask_app")); + + // 实时获取 stdout 日志 + this.flaskProcess.stdout?.on("data", (data) => { + const message = data.toString(); + console.log(`Flask stdout: ${message}`); + this.mainWindow?.webContents.send("flask", { stdout: message }); + }); + + // 实时获取 stderr 日志 + this.flaskProcess.stderr?.on("data", (data) => { + const message = data.toString(); + console.error(`Flask stderr: ${message}`); + this.mainWindow?.webContents.send("flask", { stderr: message }); + }); + + // 监听进程关闭事件 + this.flaskProcess.on("close", (code) => { + console.log(`Flask process exited with code ${code}`); + this.flaskProcess = null; + this.mainWindow?.webContents.send("flask", { exited: true, code }); + }); + + // 开始轮询服务状态 + // this.startCheckingFlaskStatus(); + } + + // 停止 Python 服务 + public stopFlask() { + if (this.flaskProcess) { + this.flaskProcess.kill(); + console.log("Flask service stopped."); + this.flaskProcess = null; + } + + // 停止轮询 + this.stopCheckingFlaskStatus(); + } + + // 检查 Flask 服务状态 + private checkFlaskStatus() { + if (!this.mainWindow) return; + + http + .get(this.url, (res) => { + const { statusCode } = res; + this.mainWindow?.webContents.send("flask-check", { + running: statusCode === 200, + }); + }) + .on("error", (err) => { + console.error(`Error checking Flask service: ${err.message}`); + this.mainWindow?.webContents.send("flask-check", { + running: false, + }); + }); + } + + // 开始轮询 Flask 服务状态 + private startCheckingFlaskStatus() { + if (this.intervalId) { + console.log("Already checking Flask status."); + return; + } + this.intervalId = setInterval(() => this.checkFlaskStatus(), this.interval); + } + + // 停止轮询 Flask 服务状态 + private stopCheckingFlaskStatus() { + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } +} + +export default PythonManager; diff --git a/electron/ipcMainHandlers.ts b/electron/ipcMainHandlers.ts index 1d6ee24..a4f0d7b 100644 --- a/electron/ipcMainHandlers.ts +++ b/electron/ipcMainHandlers.ts @@ -6,11 +6,15 @@ import { structureMetadata, } from "./core/dicom"; import { EVENT_PARSE_DICOM } from "./ipcEvent"; +import PythonManager from "./core/PythonManager"; /** * 渲染进程和主进程的事件调度 */ -const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow | null) => { +const registerIpcMainHandlers = ( + mainWindow: Electron.BrowserWindow | null, + pythonManager: PythonManager +) => { if (!mainWindow) return; ipcMain.removeAllListeners(); @@ -20,6 +24,9 @@ const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow | null) => { */ ipcMain.on("ipc-loaded", () => mainWindow.show()); + /** + * 解析dicoM + */ ipcMain.on(EVENT_PARSE_DICOM, async (event, file: string) => { const dirDialog = await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"], @@ -34,6 +41,12 @@ const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow | null) => { event.reply(EVENT_PARSE_DICOM + ":RES", result); } }); + + ipcMain.on("python-service", (event, data) => { + const { running } = data; + console.log("执行调用"); + running ? pythonManager.startFlask() : pythonManager.stopFlask(); + }); }; export default registerIpcMainHandlers; diff --git a/electron/main.ts b/electron/main.ts index 7dbb3fe..3565bd7 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -10,6 +10,7 @@ import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import path from "node:path"; import registerIpcMainHandlers from "./ipcMainHandlers"; +import PythonManager from "./core/PythonManager"; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -26,7 +27,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL let win: BrowserWindow | null; let tray: Tray | null = null; - +let pythonManager: PythonManager | null; const theme: "dark" | "light" = "dark"; const themeTitleBarStyles = { @@ -45,6 +46,7 @@ function createWindow() { titleBarOverlay: { height: 36, ...themeTitleBarStyles[theme] }, // 渲染进程发消息动态改变这个 webPreferences: { preload: path.join(__dirname, "preload.mjs"), + nodeIntegration: true, }, }); @@ -62,7 +64,8 @@ function createWindow() { win.loadFile(path.join(RENDERER_DIST, "index.html")); } - registerIpcMainHandlers(win); + pythonManager = new PythonManager(win, "http://127.0.0.1:15001", 3000); + registerIpcMainHandlers(win, pythonManager); } function createTray() { @@ -100,8 +103,6 @@ function createTray() { }); } -console.log(process.platform) - function registerGlobalShortcuts() { // 注册全局快捷键 'CommandOrControl+Shift+S' 来显示应用窗口 globalShortcut.register("Option+N", () => { @@ -124,6 +125,10 @@ app.on("activate", () => { } }); +app.on("before-quit", () => { + pythonManager?.stopFlask(); +}); + app.whenReady().then(() => { createWindow(); createTray(); diff --git a/public/flask_app b/public/flask_app new file mode 100755 index 0000000..996e2fb Binary files /dev/null and b/public/flask_app differ diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..0f0d929 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index caf16ff..a4d6e71 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,14 +3,17 @@ import LayoutMain from "@/pages/Layout"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Aorta from "@/pages/Aorta"; import { Setting } from "./pages/Setting"; +import { Env } from "./pages/Env"; import { useEffect } from "react"; function App() { - const theme = document.querySelector('html')!.getAttribute('theme') as 'dark' | 'light' + const theme = document.querySelector("html")!.getAttribute("theme") as + | "dark" + | "light"; useEffect(() => { - window.ipcRenderer.send('ipc-loaded') - }, []) + window.ipcRenderer.send("ipc-loaded"); + }, []); return ( @@ -18,6 +21,7 @@ function App() { }> } /> + } /> } /> diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..77e9fb7 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/pages/Aorta/index.tsx b/src/pages/Aorta/index.tsx index 4aba7fd..70d0e01 100644 --- a/src/pages/Aorta/index.tsx +++ b/src/pages/Aorta/index.tsx @@ -1,7 +1,41 @@ import { Button } from "@/components/ui/button"; -import { motion } from 'framer-motion'; +import { motion } from "framer-motion"; +import { useEffect, useState } from "react"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { IoCheckmarkCircleSharp } from "react-icons/io5"; const Aorta = () => { + const [flaskWaiting, setFlaskWaiting] = useState(false); + const [flaskRunning, setflaskRunning] = useState(false); + const [flaskInfo, setFlaskInfo] = useState(""); + + const handleBootPythonServer = () => { + if (!flaskRunning) setFlaskWaiting(true); + window.ipcRenderer.send("python-service", { running: !flaskRunning }); + }; + + useEffect(() => { + window.ipcRenderer.on("flask", (event, data) => { + console.log(data); + if (data.stdout || data.stderr) { + setflaskRunning(true); + setFlaskWaiting(false); + setFlaskInfo((p) => p + (data.stdout || data.stderr)); + } + if (data.exited) { + setFlaskInfo(""); + setflaskRunning(false); + } + }); + }, []); + return ( { transition={{ duration: 0.25 }} >
-
+
+
+
{flaskInfo}
+
+
); diff --git a/src/pages/Env/index.tsx b/src/pages/Env/index.tsx new file mode 100644 index 0000000..0e3a5a7 --- /dev/null +++ b/src/pages/Env/index.tsx @@ -0,0 +1,5 @@ +import React, { useEffect } from "react"; + +export const Env = () => { + return
index
; +}; diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx index 385359d..0c44a95 100644 --- a/src/pages/Layout.tsx +++ b/src/pages/Layout.tsx @@ -10,12 +10,17 @@ const LayoutMain = () => { return (
-
+
- - + + CVPilot 启动器 + {/* */}
diff --git a/src/pages/LeftDocker.tsx b/src/pages/LeftDocker.tsx index 61b3803..32592fc 100644 --- a/src/pages/LeftDocker.tsx +++ b/src/pages/LeftDocker.tsx @@ -22,7 +22,7 @@ type MenuItem = { const menuItems: MenuItem[] = [ { to: "/", name: "一键启动", icon: }, { to: "/datalist", name: "数据列表", icon: }, - { to: "/version", name: "版本管理", icon: }, + { to: "/env", name: "环境管理", icon: }, { to: "/models", name: "模型管理", icon: }, { to: "/tools", name: "小工具", icon: }, { to: "/help", name: "帮助", icon: },