feat: 算法调度测试缓存
This commit is contained in:
parent
2cbab89273
commit
67b5e488fe
51
apps/desktop/electron/core/alg.ts
Normal file
51
apps/desktop/electron/core/alg.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import log from "electron-log";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import path from "node:path";
|
||||||
|
import { InferReq } from "./alg.type";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const ALGServerRoot = "http://127.0.0.1:5000/root";
|
||||||
|
|
||||||
|
export const getEntryPath = () => {
|
||||||
|
// 区分操作系统
|
||||||
|
return path.join(process.env.VITE_PUBLIC!, "main.exe");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startALGServer = () => {
|
||||||
|
const entryPath = getEntryPath();
|
||||||
|
const child_process = spawn(entryPath);
|
||||||
|
child_process.on("message", (data) => console.log(data));
|
||||||
|
child_process.stdout.on("data", (data) => log.info(data.toString()));
|
||||||
|
child_process.stderr.on("data", (data) => log.error(data.toString()));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行推理任务
|
||||||
|
*/
|
||||||
|
export const executeInferTask = (
|
||||||
|
task: InferReq,
|
||||||
|
onData: (data: string) => void
|
||||||
|
) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios
|
||||||
|
.post(ALGServerRoot, task, {
|
||||||
|
responseType: "stream",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
response.data.on("data", (chunk: Buffer) => {
|
||||||
|
const data = chunk.toString();
|
||||||
|
onData(data); // 实时处理数据
|
||||||
|
});
|
||||||
|
|
||||||
|
response.data.on("end", () => {
|
||||||
|
resolve(task); // 数据流结束
|
||||||
|
});
|
||||||
|
|
||||||
|
response.data.on("error", (err: Error) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
29
apps/desktop/electron/core/alg.type.ts
Normal file
29
apps/desktop/electron/core/alg.type.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
export enum InferDeviceEnum {
|
||||||
|
GPU = "GPU",
|
||||||
|
CPU = "CPU",
|
||||||
|
NPU = "NPU",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分割的结构
|
||||||
|
*/
|
||||||
|
export enum InferStructuralEnum {
|
||||||
|
PERI = "peripheral",
|
||||||
|
AORTA = "root",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InferReq = {
|
||||||
|
/**
|
||||||
|
* .dcm文件夹path
|
||||||
|
*/
|
||||||
|
img_path: string;
|
||||||
|
/**
|
||||||
|
* 保存推理文件的path
|
||||||
|
* @description {app.getPath('userData')}/{SeriesInstanceUID}/${xxxx}
|
||||||
|
*/
|
||||||
|
save_path: string;
|
||||||
|
pu: InferDeviceEnum;
|
||||||
|
module: InferStructuralEnum;
|
||||||
|
turbo?: boolean;
|
||||||
|
seg_schedule: boolean;
|
||||||
|
};
|
|
@ -1,11 +1,13 @@
|
||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { PatientInfo, SeriesInfo, StudyInfo } from "./pacs.type";
|
import { InstanceInfo, PatientInfo, SeriesInfo, StudyInfo } from "./pacs.type";
|
||||||
|
import { app } from "electron";
|
||||||
|
import pLimit from "p-limit";
|
||||||
|
|
||||||
export const OrthancServerRoot = "http://localhost:8042";
|
export const OrthancServerRoot = "http://localhost:8042";
|
||||||
|
|
||||||
|
@ -103,3 +105,77 @@ export const selectStructuredDicom = async (
|
||||||
throw error; // or handle it accordingly
|
throw error; // or handle it accordingly
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 SeriesInstanceUID 下载序列的所有 DICOM 文件并返回保存的文件夹路径
|
||||||
|
* @param seriesInstanceUID 序列的 SeriesInstanceUID
|
||||||
|
* @returns 保存的文件夹路径
|
||||||
|
*/
|
||||||
|
export const downloadSeriesDicomFiles = async (
|
||||||
|
seriesInstanceUID: string
|
||||||
|
): Promise<string> => {
|
||||||
|
try {
|
||||||
|
// 使用 Orthanc 的查询接口查找匹配的序列 ID
|
||||||
|
const findResponse = await axios.post<string[]>(
|
||||||
|
`${OrthancServerRoot}/tools/find`,
|
||||||
|
{
|
||||||
|
Level: "Series",
|
||||||
|
Query: {
|
||||||
|
SeriesInstanceUID: seriesInstanceUID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const seriesIds = findResponse.data;
|
||||||
|
|
||||||
|
if (seriesIds.length === 0) {
|
||||||
|
throw new Error("在 Orthanc 中未找到指定的 SeriesInstanceUID。");
|
||||||
|
}
|
||||||
|
|
||||||
|
const seriesId = seriesIds[0]; // 假设只有一个匹配的序列
|
||||||
|
|
||||||
|
// 获取该序列中的所有实例(DICOM 文件)
|
||||||
|
const instancesResponse = await axios.get<string[]>(
|
||||||
|
`${OrthancServerRoot}/series/${seriesId}/instances`
|
||||||
|
);
|
||||||
|
|
||||||
|
const instances = instancesResponse.data as unknown as InstanceInfo[];
|
||||||
|
|
||||||
|
// 创建保存 DICOM 文件的本地目录
|
||||||
|
const saveDir = path.join(
|
||||||
|
app.getPath("userData"),
|
||||||
|
"dicom",
|
||||||
|
seriesInstanceUID
|
||||||
|
);
|
||||||
|
if (!existsSync(saveDir)) {
|
||||||
|
mkdirSync(saveDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 并发限制
|
||||||
|
const limit = pLimit(5); // 限制同时进行的请求数为 5
|
||||||
|
|
||||||
|
// 并行下载 DICOM 文件
|
||||||
|
await Promise.all(
|
||||||
|
instances.map((instance) => {
|
||||||
|
const instanceId = instance.ID;
|
||||||
|
return limit(async () => {
|
||||||
|
const dicomResponse = await axios.get<ArrayBuffer>(
|
||||||
|
`${OrthancServerRoot}/instances/${instanceId}/file`,
|
||||||
|
{
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const filePath = path.join(saveDir, `${instanceId}.dcm`);
|
||||||
|
writeFileSync(filePath, Buffer.from(dicomResponse.data), "binary");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`所有 DICOM 文件已保存至:${saveDir}`);
|
||||||
|
return saveDir;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("下载 DICOM 文件时出错:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { ipcMain, shell } from "electron";
|
import { app, ipcMain, shell } from "electron";
|
||||||
import { mkdir, stat } from "fs/promises";
|
import { mkdir, stat } from "fs/promises";
|
||||||
import { db } from "../../core/db";
|
import { db } from "../../core/db";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { downloadSeriesDicomFiles } from "../../core/pacs";
|
||||||
|
import { executeInferTask } from "../../core/alg";
|
||||||
|
import { InferDeviceEnum, InferStructuralEnum } from "../../core/alg.type";
|
||||||
|
|
||||||
export const registerCommonHandler = () => {
|
export const registerCommonHandler = () => {
|
||||||
ipcMain.handle("output:open", async () => {
|
ipcMain.handle("output:open", async () => {
|
||||||
|
@ -30,4 +33,24 @@ export const registerCommonHandler = () => {
|
||||||
return { success: false, msg: `操作失败` };
|
return { success: false, msg: `操作失败` };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("model:infer", async (_event, SeriesInstanceUIDs) => {
|
||||||
|
// 构造推理任务参数列表
|
||||||
|
const save_path = path.join(app.getPath("userData"), "output");
|
||||||
|
const pu = InferDeviceEnum.GPU;
|
||||||
|
const module = InferStructuralEnum.AORTA;
|
||||||
|
const turbo = true;
|
||||||
|
const seg_schedule = true;
|
||||||
|
for (let i = 0; i < SeriesInstanceUIDs.length; i++) {
|
||||||
|
const SeriesInstanceUID = SeriesInstanceUIDs[i];
|
||||||
|
// 下载dicom到本地,获取文件夹路径
|
||||||
|
const img_path = await downloadSeriesDicomFiles(SeriesInstanceUID);
|
||||||
|
const task = { save_path, pu, module, turbo, seg_schedule, img_path };
|
||||||
|
console.log(task);
|
||||||
|
const result = await executeInferTask(task, (data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
console.log("end: ", result);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { createDatabase } from "./core/db";
|
||||||
import { getMachineId } from "./core/auth";
|
import { getMachineId } from "./core/auth";
|
||||||
import { getPacsPath, runOrthancServer } from "./core/pacs";
|
import { getPacsPath, runOrthancServer } from "./core/pacs";
|
||||||
import { registerIpcMainHandlers } from "./ipcEvent";
|
import { registerIpcMainHandlers } from "./ipcEvent";
|
||||||
|
import { startALGServer } from "./core/alg";
|
||||||
|
|
||||||
// const require = createRequire(import.meta.url);
|
// const require = createRequire(import.meta.url);
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
@ -37,7 +38,6 @@ const themeTitleBarStyles = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const platform = process.platform === "darwin" ? "macos" : "windows";
|
export const platform = process.platform === "darwin" ? "macos" : "windows";
|
||||||
|
|
||||||
app.commandLine.appendSwitch("disable-web-security");
|
app.commandLine.appendSwitch("disable-web-security");
|
||||||
app.commandLine.appendSwitch("ignore-gpu-blocklist");
|
app.commandLine.appendSwitch("ignore-gpu-blocklist");
|
||||||
app.commandLine.appendSwitch("use-angle", "gl");
|
app.commandLine.appendSwitch("use-angle", "gl");
|
||||||
|
@ -74,10 +74,7 @@ function createWindow() {
|
||||||
win.loadURL(VITE_DEV_SERVER_URL);
|
win.loadURL(VITE_DEV_SERVER_URL);
|
||||||
registerIpcMainHandlers(win);
|
registerIpcMainHandlers(win);
|
||||||
runOrthancServer(getPacsPath(platform, true));
|
runOrthancServer(getPacsPath(platform, true));
|
||||||
|
startALGServer();
|
||||||
// if (platform !== "macos") {
|
|
||||||
// python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
// if (platform !== "macos") {
|
// if (platform !== "macos") {
|
||||||
// python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
|
// python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
|
||||||
|
|
|
@ -76,7 +76,8 @@
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "3.23.8",
|
"zod": "3.23.8",
|
||||||
"mitt": "3.0.1"
|
"mitt": "3.0.1",
|
||||||
|
"p-limit": "6.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -15,22 +15,22 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
const Boot = () => {
|
const Boot = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const selectDicoms =
|
|
||||||
location.state?.selectDicoms ??
|
|
||||||
JSON.parse(localStorage.getItem("selectDicoms") ?? "[]");
|
|
||||||
const [messageText, setMessageText] = useState(["进度信息简化"]);
|
const [messageText, setMessageText] = useState(["进度信息简化"]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* windows系统右键启动应用菜单的入口路径
|
* windows系统右键启动应用菜单的入口路径
|
||||||
*/
|
*/
|
||||||
const [bootDirectoryPath, setBootDirectoryPath] = useState("");
|
const [bootDirectoryPath, setBootDirectoryPath] = useState("");
|
||||||
const [tasks, setTasks] = useState(selectDicoms);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传到electron主进程
|
* 上传到electron主进程
|
||||||
*/
|
*/
|
||||||
const handleTasks = () => {
|
const handleTasks = () => {
|
||||||
window.ipcRenderer.send("ai:task", { selectDicoms: tasks });
|
const SeriesInstanceUIDs = [
|
||||||
|
// "1.2.156.112605.14038010222575.230518044041.3.4344.300061",
|
||||||
|
"1.3.12.2.1107.5.1.4.73399.30000020080900171669200001479",
|
||||||
|
];
|
||||||
|
window.ipcRenderer.invoke("model:infer", SeriesInstanceUIDs);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -42,30 +42,11 @@ const Boot = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bootDirectoryPath) {
|
if (bootDirectoryPath) {
|
||||||
window.ipcRenderer.send("one-step");
|
// TODO: 启动分析
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {};
|
||||||
window.ipcRenderer.off("one-step", () => {});
|
|
||||||
};
|
|
||||||
}, [bootDirectoryPath]);
|
}, [bootDirectoryPath]);
|
||||||
|
|
||||||
const handleEmptyTasks = () => {
|
|
||||||
setTasks([]);
|
|
||||||
localStorage.removeItem("selectDicoms");
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.ipcRenderer.on("tasksFinished", (_event, data) => {
|
|
||||||
setMessageText((p) => [...p, `总用时: ${data}`]);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.ipcRenderer.on("taskFinished", (_event, data) => {
|
|
||||||
setMessageText((p) => [...p, data]);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 10, opacity: 0 }}
|
initial={{ y: 10, opacity: 0 }}
|
||||||
|
@ -79,51 +60,9 @@ const Boot = () => {
|
||||||
<ResizablePanel defaultSize={38.2}>
|
<ResizablePanel defaultSize={38.2}>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
|
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
|
||||||
<div className="w-full flex flex-col gap-y-2">
|
<div className="w-full flex flex-col gap-y-2">啦啦啦</div>
|
||||||
{tasks.map((dicom: Series, index: number) => (
|
|
||||||
<Card
|
|
||||||
key={index}
|
|
||||||
className="flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"
|
|
||||||
>
|
|
||||||
<div className="flex w-full flex-col gap-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="font-semibold">
|
|
||||||
{dicom.PatientName}
|
|
||||||
</div>
|
|
||||||
<span className="flex h-2 w-2 rounded-full bg-blue-600"></span>
|
|
||||||
</div>
|
|
||||||
<div className="ml-auto text-xs text-foreground">
|
|
||||||
{dicom.PatientSex}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs font-medium">
|
|
||||||
{dicom.PatientAge}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="line-clamp-2 text-xs text-muted-foreground">
|
|
||||||
这里是一些额外的信息,如果可以增加一些AI的判断结果,根据metadata扔给大语言模型,理想情况可以自动鉴别出能够使用哪些模型,优化掉用户手动选择分析的过程
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80">
|
|
||||||
主动脉瓣
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
|
|
||||||
二尖瓣
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
|
|
||||||
外周入路
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<div className="flex-shrink-0 flex items-center justify-end gap-2 px-4 py-4">
|
<div className="flex-shrink-0 flex items-center justify-end gap-2 px-4 py-4">
|
||||||
<Button onClick={handleEmptyTasks} variant="ghost">
|
|
||||||
清空
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleTasks}>
|
<Button onClick={handleTasks}>
|
||||||
<SparkleIcon className="mr-2 h-4 w-4" />
|
<SparkleIcon className="mr-2 h-4 w-4" />
|
||||||
启动分析
|
启动分析
|
||||||
|
|
|
@ -166,6 +166,9 @@ importers:
|
||||||
openvino-node:
|
openvino-node:
|
||||||
specifier: 2024.3.0
|
specifier: 2024.3.0
|
||||||
version: 2024.3.0
|
version: 2024.3.0
|
||||||
|
p-limit:
|
||||||
|
specifier: 6.1.0
|
||||||
|
version: 6.1.0
|
||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
|
@ -4188,6 +4191,10 @@ packages:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-limit@6.1.0:
|
||||||
|
resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -5510,6 +5517,10 @@ packages:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
yocto-queue@1.1.1:
|
||||||
|
resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
|
||||||
|
engines: {node: '>=12.20'}
|
||||||
|
|
||||||
zip-stream@4.1.1:
|
zip-stream@4.1.1:
|
||||||
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
|
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
@ -10011,6 +10022,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
|
p-limit@6.1.0:
|
||||||
|
dependencies:
|
||||||
|
yocto-queue: 1.1.1
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
@ -11532,6 +11547,8 @@ snapshots:
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
yocto-queue@1.1.1: {}
|
||||||
|
|
||||||
zip-stream@4.1.1:
|
zip-stream@4.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
archiver-utils: 3.0.4
|
archiver-utils: 3.0.4
|
||||||
|
|
Loading…
Reference in New Issue
Block a user