feat: 阅片页面增加ai能力
This commit is contained in:
parent
a033c83cff
commit
0b3ee1790e
|
@ -27,6 +27,10 @@ pnpm config set virtual-store-dir-max-length 70
|
||||||
|
|
||||||
- 多结构的阅片
|
- 多结构的阅片
|
||||||
|
|
||||||
|
- 算法服务状态、pacs服务状态的检测、重启、停止
|
||||||
|
|
||||||
|
- 算法分析失败情况总结,进度增加报错状态字段,解决vtk warning的弹窗
|
||||||
|
|
||||||
## 窗宽创维相关的一些小问题
|
## 窗宽创维相关的一些小问题
|
||||||
|
|
||||||
- 心脏软组织窗
|
- 心脏软组织窗
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const registerAlgHandler = () => {
|
||||||
// 构造推理任务参数列表
|
// 构造推理任务参数列表
|
||||||
const pu = InferDeviceEnum.GPU;
|
const pu = InferDeviceEnum.GPU;
|
||||||
|
|
||||||
const turbo = false;
|
const turbo = true;
|
||||||
const seg_schedule = true;
|
const seg_schedule = true;
|
||||||
for (let i = 0; i < SeriesInstanceUIDs.length; i++) {
|
for (let i = 0; i < SeriesInstanceUIDs.length; i++) {
|
||||||
const SeriesInstanceUID = SeriesInstanceUIDs[i];
|
const SeriesInstanceUID = SeriesInstanceUIDs[i];
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { ipcMain } from "electron";
|
||||||
import { registerDicomHandler } from "./dicom/handler";
|
import { registerDicomHandler } from "./dicom/handler";
|
||||||
import { registerCommonHandler } from "./common";
|
import { registerCommonHandler } from "./common";
|
||||||
import { registerAlgHandler } from "./alg";
|
import { registerAlgHandler } from "./alg";
|
||||||
|
import { registerOllama } from "./llm";
|
||||||
|
|
||||||
export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
|
export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
|
||||||
ipcMain.removeAllListeners();
|
ipcMain.removeAllListeners();
|
||||||
|
@ -14,4 +15,5 @@ export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
|
||||||
registerCommonHandler();
|
registerCommonHandler();
|
||||||
registerDicomHandler();
|
registerDicomHandler();
|
||||||
registerAlgHandler();
|
registerAlgHandler();
|
||||||
|
registerOllama();
|
||||||
};
|
};
|
||||||
|
|
11
apps/desktop/electron/ipcEvent/llm/index.ts
Normal file
11
apps/desktop/electron/ipcEvent/llm/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { ollama, run } from "../../core/ollama";
|
||||||
|
export const registerOllama = async () => {
|
||||||
|
// const list = await ollama.list();
|
||||||
|
// console.log(list);
|
||||||
|
|
||||||
|
ipcMain.handle("chat", async (_event, input: string) => {
|
||||||
|
const answer = await run("qwen2.5:3B", input);
|
||||||
|
return answer;
|
||||||
|
});
|
||||||
|
};
|
24
apps/desktop/src/components/ui/textarea.tsx
Normal file
24
apps/desktop/src/components/ui/textarea.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface TextareaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Series } from "../Datasource/SeriesTable";
|
import { Series } from "../Datasource/SeriesTable";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { LLM } from "./llm";
|
||||||
|
|
||||||
const Boot = () => {
|
const Boot = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -68,7 +69,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>
|
<div className="w-full flex flex-col gap-y-2">
|
||||||
|
<LLM />
|
||||||
|
</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={handleTasks}>
|
<Button onClick={handleTasks}>
|
||||||
|
|
56
apps/desktop/src/pages/Boot/llm.tsx
Normal file
56
apps/desktop/src/pages/Boot/llm.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface LLmProps {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LLM = (props: LLmProps) => {
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [data, setData] = useState<string>();
|
||||||
|
|
||||||
|
// 处理输入变化
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setInputValue(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听回车键发送内容
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (event.key === "Enter" && !event.shiftKey) {
|
||||||
|
event.preventDefault(); // 阻止默认的换行操作
|
||||||
|
// 执行发送逻辑
|
||||||
|
handleSend();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟发送操作
|
||||||
|
const handleSend = () => {
|
||||||
|
if (inputValue.trim() !== "") {
|
||||||
|
window.ipcRenderer.invoke("chat", inputValue).then((data) => {
|
||||||
|
setData(data);
|
||||||
|
});
|
||||||
|
setInputValue("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="grid w-full gap-1.5">
|
||||||
|
<Label htmlFor="message-2">Your Message</Label>
|
||||||
|
<Textarea
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="请输入内容,按 Enter 发送,Shift+Enter 换行"
|
||||||
|
rows={5}
|
||||||
|
id="message-2"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Your message will be copied to the support team.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>{data}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -61,16 +61,17 @@ export const AortaViewer = (props: AortaViewerProps) => {
|
||||||
}, [stlFiles]);
|
}, [stlFiles]);
|
||||||
|
|
||||||
const initMeasurement = useCallback(() => {
|
const initMeasurement = useCallback(() => {
|
||||||
|
console.log(measurement)
|
||||||
for (const prop in measurement) {
|
for (const prop in measurement) {
|
||||||
if (prop in valveMapping) {
|
if (prop in valveMapping) {
|
||||||
const pointArray = measurement[prop].points;
|
const pointArray = measurement[prop].less_points;
|
||||||
const curve = new THREE.CatmullRomCurve3(
|
const curve = new THREE.CatmullRomCurve3(
|
||||||
pointArray.map((p) => new THREE.Vector3(p[0], p[1], p[2]))
|
pointArray.map((p) => new THREE.Vector3(p[0], p[1], p[2]))
|
||||||
);
|
);
|
||||||
curve.curveType = "chordal"; // 曲线类型
|
curve.curveType = "centripetal"; // 曲线类型
|
||||||
curve.closed = true; // 曲线是否闭合
|
curve.closed = true; // 曲线是否闭合
|
||||||
// 50等分获取曲线点数组
|
// 50等分获取曲线点数组
|
||||||
const points = curve.getPoints(50);
|
const points = curve.getPoints(100);
|
||||||
const [r, g, b] = valveMapping[prop].color;
|
const [r, g, b] = valveMapping[prop].color;
|
||||||
const line = new THREE.LineLoop(
|
const line = new THREE.LineLoop(
|
||||||
new THREE.BufferGeometry().setFromPoints(points),
|
new THREE.BufferGeometry().setFromPoints(points),
|
||||||
|
|
|
@ -13,4 +13,4 @@ export interface AlgAssets {
|
||||||
* 结构
|
* 结构
|
||||||
*/
|
*/
|
||||||
module: InferStructuralEnum;
|
module: InferStructuralEnum;
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@ import {
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { ResetIcon } from "@radix-ui/react-icons";
|
import { ResetIcon } from "@radix-ui/react-icons";
|
||||||
import { MoonIcon, PersonStanding } from "lucide-react";
|
import { MoonIcon, PersonStanding, SparkleIcon } from "lucide-react";
|
||||||
|
|
||||||
export interface ToolBarMenuProps {
|
export interface ToolBarMenuProps {
|
||||||
onToolButtonClick?: (key: string) => void;
|
onToolButtonClick?: (key: string) => void;
|
||||||
|
@ -30,12 +30,16 @@ export const ToolBarMenu = (props: ToolBarMenuProps) => {
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon">
|
<Button
|
||||||
<PersonStanding className="h-4 w-4" />
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => props.onToolButtonClick?.("view3DModel")}
|
||||||
|
>
|
||||||
|
<SparkleIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom">
|
<TooltipContent side="bottom">
|
||||||
<p>MIP</p>
|
<p>AI能力</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
|
@ -261,8 +261,21 @@ export const Viewer = () => {
|
||||||
viewport.resetCamera(true, true, true, true, false);
|
viewport.resetCamera(true, true, true, true, false);
|
||||||
viewport.render();
|
viewport.render();
|
||||||
}
|
}
|
||||||
|
if (key === "view3DModel") {
|
||||||
|
window.ipcRenderer.send("model:infer", [SeriesInstanceUID]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const receiveInfer = (_event: any, data: string) => {
|
||||||
|
console.log(data);
|
||||||
|
};
|
||||||
|
window.ipcRenderer.on("infer:progress", receiveInfer);
|
||||||
|
return () => {
|
||||||
|
window.ipcRenderer.off("infer:progress", receiveInfer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col">
|
<div className="w-full h-full flex flex-col">
|
||||||
<div className="flex-shrink-0 border-b border-secondary">
|
<div className="flex-shrink-0 border-b border-secondary">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user