feat: 阅片页面增加ai能力

This commit is contained in:
mozzie 2024-09-24 15:18:16 +08:00
parent a033c83cff
commit 0b3ee1790e
11 changed files with 128 additions and 10 deletions

View File

@ -27,6 +27,10 @@ pnpm config set virtual-store-dir-max-length 70
- 多结构的阅片 - 多结构的阅片
- 算法服务状态、pacs服务状态的检测、重启、停止
- 算法分析失败情况总结进度增加报错状态字段解决vtk warning的弹窗
## 窗宽创维相关的一些小问题 ## 窗宽创维相关的一些小问题
- 心脏软组织窗 - 心脏软组织窗

View File

@ -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];

View File

@ -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();
}; };

View 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;
});
};

View 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 }

View File

@ -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}>

View 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>
);
};

View File

@ -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),

View File

@ -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>

View File

@ -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">