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 turbo = false;
|
||||
const turbo = true;
|
||||
const seg_schedule = true;
|
||||
for (let i = 0; i < SeriesInstanceUIDs.length; i++) {
|
||||
const SeriesInstanceUID = SeriesInstanceUIDs[i];
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ipcMain } from "electron";
|
|||
import { registerDicomHandler } from "./dicom/handler";
|
||||
import { registerCommonHandler } from "./common";
|
||||
import { registerAlgHandler } from "./alg";
|
||||
import { registerOllama } from "./llm";
|
||||
|
||||
export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
|
||||
ipcMain.removeAllListeners();
|
||||
|
@ -14,4 +15,5 @@ export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
|
|||
registerCommonHandler();
|
||||
registerDicomHandler();
|
||||
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 { Series } from "../Datasource/SeriesTable";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { LLM } from "./llm";
|
||||
|
||||
const Boot = () => {
|
||||
const location = useLocation();
|
||||
|
@ -68,7 +69,9 @@ const Boot = () => {
|
|||
<ResizablePanel defaultSize={38.2}>
|
||||
<div className="flex flex-col h-full">
|
||||
<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>
|
||||
<div className="flex-shrink-0 flex items-center justify-end gap-2 px-4 py-4">
|
||||
<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]);
|
||||
|
||||
const initMeasurement = useCallback(() => {
|
||||
console.log(measurement)
|
||||
for (const prop in measurement) {
|
||||
if (prop in valveMapping) {
|
||||
const pointArray = measurement[prop].points;
|
||||
const pointArray = measurement[prop].less_points;
|
||||
const curve = new THREE.CatmullRomCurve3(
|
||||
pointArray.map((p) => new THREE.Vector3(p[0], p[1], p[2]))
|
||||
);
|
||||
curve.curveType = "chordal"; // 曲线类型
|
||||
curve.curveType = "centripetal"; // 曲线类型
|
||||
curve.closed = true; // 曲线是否闭合
|
||||
// 50等分获取曲线点数组
|
||||
const points = curve.getPoints(50);
|
||||
const points = curve.getPoints(100);
|
||||
const [r, g, b] = valveMapping[prop].color;
|
||||
const line = new THREE.LineLoop(
|
||||
new THREE.BufferGeometry().setFromPoints(points),
|
||||
|
|
|
@ -13,4 +13,4 @@ export interface AlgAssets {
|
|||
* 结构
|
||||
*/
|
||||
module: InferStructuralEnum;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import {
|
|||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { ResetIcon } from "@radix-ui/react-icons";
|
||||
import { MoonIcon, PersonStanding } from "lucide-react";
|
||||
import { MoonIcon, PersonStanding, SparkleIcon } from "lucide-react";
|
||||
|
||||
export interface ToolBarMenuProps {
|
||||
onToolButtonClick?: (key: string) => void;
|
||||
|
@ -30,12 +30,16 @@ export const ToolBarMenu = (props: ToolBarMenuProps) => {
|
|||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<PersonStanding className="h-4 w-4" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => props.onToolButtonClick?.("view3DModel")}
|
||||
>
|
||||
<SparkleIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<p>MIP</p>
|
||||
<p>AI能力</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
|
|
@ -261,8 +261,21 @@ export const Viewer = () => {
|
|||
viewport.resetCamera(true, true, true, true, false);
|
||||
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 (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="flex-shrink-0 border-b border-secondary">
|
||||
|
|
Loading…
Reference in New Issue
Block a user