feat: python exe
This commit is contained in:
parent
6bf4ec52ec
commit
f6329ff1f7
100
electron/core/PythonManager.ts
Normal file
100
electron/core/PythonManager.ts
Normal file
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
BIN
public/flask_app
Executable file
BIN
public/flask_app
Executable file
Binary file not shown.
16
public/logo.svg
Normal file
16
public/logo.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="27"
|
||||
height="27"
|
||||
viewBox="0 0 45 47"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M24.273 7.7653L19.6087 11.3716L15.3793 5.98863L11 7L30 35.5L34.1633 30.0428L32.7808 28.1213C32.1142 27.1213 31.6711 25.3464 33.2711 24.1464C34.8711 22.9464 36.6061 23.6397 37.4394 24.473L40.7354 28.7305L30 47L0 0H18.0882L24.273 7.7653Z"
|
||||
fill="rgb(3, 148, 130)"
|
||||
></path>
|
||||
<path
|
||||
d="M43.645 9.14625C42.6195 7.84489 40.7332 7.62124 39.4319 8.64673L30.4992 15.6858L28.646 13.3226C27.6235 12.0188 25.7378 11.7907 24.434 12.8132C23.1302 13.8356 22.9022 15.7213 23.9246 17.0251L29.4784 24.1071L29.4954 24.0939L29.506 24.1074L43.1455 13.3594C44.4468 12.3339 44.6705 10.4476 43.645 9.14625Z"
|
||||
fill="rgb(3, 148, 130)"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 850 B |
10
src/App.tsx
10
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 (
|
||||
<ThemeProvider defaultTheme={theme} storageKey="vite-ui-theme">
|
||||
|
@ -18,6 +21,7 @@ function App() {
|
|||
<Routes>
|
||||
<Route path="/" element={<LayoutMain />}>
|
||||
<Route index element={<Aorta />} />
|
||||
<Route path="env" element={<Env />} />
|
||||
<Route path="setting" element={<Setting />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
|
76
src/components/ui/card.tsx
Normal file
76
src/components/ui/card.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
@ -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 (
|
||||
<motion.div
|
||||
initial={{ y: 10, opacity: 0 }}
|
||||
|
@ -10,7 +44,25 @@ const Aorta = () => {
|
|||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="w-full h-[250px] rounded-md" style={{ backgroundImage: `url('/banner.png')`, backgroundSize: 'cover' }}></div>
|
||||
<div
|
||||
className="w-full h-[250px] rounded-md"
|
||||
style={{
|
||||
backgroundImage: `url('/banner.png')`,
|
||||
backgroundSize: "cover",
|
||||
}}
|
||||
></div>
|
||||
<div className="mt-4">
|
||||
<pre>{flaskInfo}</pre>
|
||||
</div>
|
||||
<Button
|
||||
disabled={flaskWaiting}
|
||||
className="bg"
|
||||
size={"lg"}
|
||||
onClick={handleBootPythonServer}
|
||||
>
|
||||
<IoCheckmarkCircleSharp className={`mr-2 h-4 w-4}`} />
|
||||
{flaskRunning ? "运行中" : "启动"} python
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
5
src/pages/Env/index.tsx
Normal file
5
src/pages/Env/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React, { useEffect } from "react";
|
||||
|
||||
export const Env = () => {
|
||||
return <div>index</div>;
|
||||
};
|
|
@ -10,12 +10,17 @@ const LayoutMain = () => {
|
|||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className={`title-bar drag h-[36px] flex`}>
|
||||
<div
|
||||
className={`title-bar drag h-[36px] flex ${
|
||||
platform === "macos" ? "justify-center" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`inline-flex no-drag items-center justify-between ${titleBarStyles}`}
|
||||
>
|
||||
<img src="AI.png" className="h-[24px]" />
|
||||
<MenuBar />
|
||||
<img src="logo.svg" className="h-[20px]" />
|
||||
<span className="pl-2 text-xs">CVPilot 启动器</span>
|
||||
{/* <MenuBar /> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-[calc(100%-36px)]">
|
||||
|
|
|
@ -22,7 +22,7 @@ type MenuItem = {
|
|||
const menuItems: MenuItem[] = [
|
||||
{ to: "/", name: "一键启动", icon: <IoPlayOutline size={24} /> },
|
||||
{ to: "/datalist", name: "数据列表", icon: <IoListOutline size={24} /> },
|
||||
{ to: "/version", name: "版本管理", icon: <IoLayersOutline size={24} /> },
|
||||
{ to: "/env", name: "环境管理", icon: <IoLayersOutline size={24} /> },
|
||||
{ to: "/models", name: "模型管理", icon: <IoCubeOutline size={24} /> },
|
||||
{ to: "/tools", name: "小工具", icon: <IoHammerOutline size={24} /> },
|
||||
{ to: "/help", name: "帮助", icon: <IoHandLeftOutline size={24} /> },
|
||||
|
|
Loading…
Reference in New Issue
Block a user