feat: lots of work

This commit is contained in:
mozzie 2024-08-30 16:58:44 +08:00
parent 10f4253954
commit abf1cc06b5
15 changed files with 761 additions and 50 deletions

View File

@ -1,9 +1,6 @@
import path from "node:path";
import { JSONFilePreset } from "lowdb/node";
import { app } from "electron";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const initDb = async () => {
// Read or create db.json

View File

@ -3,18 +3,41 @@ import * as dicomParser from "dicom-parser";
import fs from "fs";
export interface StructuredData {
[StudyInstanceUID: string]: {
[SeriesInstanceUID: string]: ExtractMetadata[];
};
[SeriesInstanceUID: string]: ExtractMetadata[];
}
export interface ExtractMetadata {
filePath: string;
StudyInstanceUID?: string;
SeriesInstanceUID?: string;
PatientName?: string;
pixelData?: Uint16Array;
}
/**
* DICOM Magic Number
* @param filePath
* @returns DICOM
*/
const isDICOMFile = async (filePath: string) => {
try {
// 打开文件以进行读取
const fileHandle = await fs.promises.open(filePath, "r");
const buffer = Buffer.alloc(132); // 创建一个 132 字节的缓冲区
// 从文件中读取前 132 个字节
await fileHandle.read(buffer, 0, 132, 0);
await fileHandle.close(); // 关闭文件
// 检查 "DICM" 标识 (偏移 128-131 字节)
const magicNumber = buffer.toString("utf-8", 128, 132);
return magicNumber === "DICM";
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
return false;
}
};
/**
* .dcm文件
* @param dir
@ -31,8 +54,8 @@ export const findDcmFiles = async (
const filePath = path.join(dir, file.name);
if (file.isDirectory()) {
await findDcmFiles(filePath, fileList); // 递归调用以遍历子目录
} else if (file.name.endsWith(".dcm")) {
fileList.push(filePath); // 如果文件是.dcm文件,添加到列表中
} else if (await isDICOMFile(filePath)) {
fileList.push(filePath); // 如果文件是 DICOM 文件,添加到列表中
}
})
);
@ -52,6 +75,7 @@ export const parseDICOMFile = async (
const dataSet = dicomParser.parseDicom(byteArray, options);
const StudyInstanceUID = dataSet.string("x0020000d");
const SeriesInstanceUID = dataSet.string("x0020000e");
const PatientName = dataSet.string("x00100030");
const pixelDataElement = dataSet.elements.x7fe00010;
const pixelData = new Uint16Array(
dataSet.byteArray.buffer,
@ -63,6 +87,7 @@ export const parseDICOMFile = async (
filePath,
StudyInstanceUID,
SeriesInstanceUID,
PatientName,
// pixelData,
};
} catch (error) {
@ -98,26 +123,40 @@ export const processFilesInBatches = async (
return results;
};
export const structureMetadata = (data: ExtractMetadata[]): StructuredData => {
const structured: StructuredData = {};
export interface StructuredMetadata {
filePaths: string[];
StudyInstanceUID?: string;
SeriesInstanceUID?: string;
PatientName?: string;
}
export const structureMetadata = (
data: ExtractMetadata[]
): StructuredMetadata[] => {
const result: StructuredMetadata[] = [];
data.forEach((item) => {
// 确保每个元素都有有效的 StudyInstanceUID 和 SeriesInstanceUID
if (item.StudyInstanceUID && item.SeriesInstanceUID) {
// 如果还没有为这个 StudyInstanceUID 创建记录,则初始化一个空对象
if (!structured[item.StudyInstanceUID]) {
structured[item.StudyInstanceUID] = {};
}
// 查找是否已经有相同 UID 和 PatientName 的条目
const existingEntry = result.find(
(entry) =>
entry.StudyInstanceUID === item.StudyInstanceUID &&
entry.SeriesInstanceUID === item.SeriesInstanceUID &&
entry.PatientName === item.PatientName
);
// 如果这个 StudyInstanceUID 下还没有这个 SeriesInstanceUID 的记录,则初始化一个空数组
if (!structured[item.StudyInstanceUID][item.SeriesInstanceUID]) {
structured[item.StudyInstanceUID][item.SeriesInstanceUID] = [];
}
// 将当前元素添加到对应的数组中
structured[item.StudyInstanceUID][item.SeriesInstanceUID].push(item);
if (existingEntry) {
// 如果找到了相同的条目,合并 filePath
existingEntry.filePaths.push(item.filePath);
} else {
// 如果没有找到,创建一个新的条目
result.push({
filePaths: [item.filePath],
StudyInstanceUID: item.StudyInstanceUID,
SeriesInstanceUID: item.SeriesInstanceUID,
PatientName: item.PatientName,
});
}
});
return structured;
return result;
};

View File

@ -46,6 +46,19 @@ const registerIpcMainHandlers = (
const { running } = data;
running ? pythonManager.startFlask() : pythonManager.stopFlask();
});
ipcMain.on("scan-dicom", async () => {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"],
});
if (result.canceled) return null;
const filePaths = result.filePaths[0];
const dcmPaths = await findDcmFiles(filePaths);
const items = await processFilesInBatches(dcmPaths, 4);
const structDicom = await structureMetadata(items);
Database.getDb().data.posts.push("hello world");
return structDicom;
});
};
export default registerIpcMainHandlers;

View File

@ -11,7 +11,6 @@ import { fileURLToPath } from "node:url";
import path from "node:path";
import registerIpcMainHandlers from "./ipcMainHandlers";
import PythonManager from "./core/PythonManager";
import "./core/db";
const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));

View File

@ -13,12 +13,15 @@
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@google-cloud/spanner": "^7.12.0",
"@hookform/resolvers": "3.9.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
@ -48,12 +51,14 @@
"react-day-picker": "^8.10.1",
"react-desktop": "^0.3.9",
"react-dom": "^18.2.0",
"react-dropzone": "14.2.3",
"react-hook-form": "7.53.0",
"react-icons": "^5.2.1",
"react-resizable-panels": "^2.0.20",
"react-router-dom": "^6.26.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"react-dropzone": "14.2.3"
"zod": "3.23.8"
},
"devDependencies": {
"@radix-ui/react-icons": "^1.3.0",

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@ -0,0 +1,178 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

View File

@ -0,0 +1,44 @@
"use client"
import * as React from "react"
import { CheckIcon } from "@radix-ui/react-icons"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<CheckIcon className="h-3.5 w-3.5 fill-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }

View File

@ -0,0 +1,164 @@
"use client"
import * as React from "react"
import {
CaretSortIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@ -1,5 +1,5 @@
import { Button } from "@/components/ui/button";
import React from "react";
import { Button } from "@/components/ui/button";
import { useDropzone } from "react-dropzone";
interface FileUploadProps {
@ -9,22 +9,20 @@ interface FileUploadProps {
const FileUpload: React.FC<FileUploadProps> = ({ onFilesSelected }) => {
const onDrop = (acceptedFiles: File[]) => {
console.log("Accepted files:", acceptedFiles);
// 调用父组件传入的回调函数处理文件
if (onFilesSelected) {
onFilesSelected(acceptedFiles);
}
};
const { getRootProps, getInputProps, isDragActive } = useDropzone({
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
onDrop,
multiple: true,
accept: {
"file/dicom": [".dcm"],
"file/model": [".stl"],
},
noClick: false, // 启用点击选择文件
noKeyboard: true, // 禁用键盘选择(可选)
directory: true, // 启用文件夹上传支持
noClick: true,
noKeyboard: true,
});
return (
@ -32,13 +30,15 @@ const FileUpload: React.FC<FileUploadProps> = ({ onFilesSelected }) => {
{...getRootProps()}
className="border-dashed border-2 p-4 py-8 rounded-md text-center cursor-pointer"
>
<input {...getInputProps()} webkitdirectory="" directory />
<input {...getInputProps({ webkitdirectory: true })} />
{isDragActive ? (
<p>/</p>
) : (
<p>/</p>
)}
<Button className="mt-2"></Button>
<Button className="mt-2" onClick={open}>
</Button>
</div>
);
};

View File

@ -1,9 +1,13 @@
import FileUpload from "./FileUpload";
import { Button } from "@/components/ui/button";
export const Datasource = () => {
return (
<div className="p-4">
<FileUpload />
<div>
<Button onClick={() => window.ipcRenderer.send("scan-dicom")}>
</Button>
</div>
</div>
);
};

View File

@ -1,3 +1,4 @@
import { Toaster } from "@/components/ui/toaster";
import { LeftDocker } from "./LeftDocker";
import { Outlet } from "react-router-dom";
@ -6,8 +7,7 @@ const LayoutMain = () => {
document.querySelector("html")?.getAttribute("platform") ?? "macos";
// const titleBarStyles =
// platform === "macos" ? "pl-[5rem] pr-[.5rem]" : "pl-[.5rem]";
const titleBarStyles =
platform === "macos" ? "px-[.5rem]" : "pl-[.5rem]";
const titleBarStyles = platform === "macos" ? "px-[.5rem]" : "pl-[.5rem]";
return (
<div className="h-full">
@ -35,6 +35,7 @@ const LayoutMain = () => {
</div>
</div>
</div>
<Toaster />
</div>
);
};

View File

@ -1,5 +1,15 @@
import { motion } from "framer-motion";
import { ModelTable } from "./ModelTable";
export const Models = () => {
return <ModelTable />;
return (
<motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
>
<ModelTable />
</motion.div>
);
};

View File

@ -1,12 +1,98 @@
import { motion } from 'framer-motion';
import { motion } from "framer-motion";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toast } from "@/components/ui/use-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
const FormSchema = z.object({
inferEngine: z.string({
required_error: "Please select an email to display.",
}),
workspace: z.string({ required_error: "请设置工作空间" }),
});
export const Setting = () => {
return <motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
>
<div></div>
</motion.div>
}
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
const onSubmit = (data: z.infer<typeof FormSchema>) => {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
};
return (
<motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
>
<div className="p-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="w-full space-y-3"
>
<FormField
control={form.control}
name="inferEngine"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel></FormLabel>
<FormDescription></FormDescription>
</div>
<div className="flex">
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="iGPU(推荐)" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">
iGPU()
</SelectItem>
<SelectItem value="m@google.com">NPU</SelectItem>
<SelectItem value="m@support.com">CPU</SelectItem>
</SelectContent>
</Select>
</div>
</FormItem>
)}
/>
</form>
</Form>
</div>
</motion.div>
);
};

View File

@ -16,6 +16,9 @@ importers:
'@google-cloud/spanner':
specifier: ^7.12.0
version: 7.14.0
'@hookform/resolvers':
specifier: 3.9.0
version: 3.9.0(react-hook-form@7.53.0(react@18.3.1))
'@radix-ui/react-checkbox':
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -31,9 +34,15 @@ importers:
'@radix-ui/react-menubar':
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-radio-group':
specifier: ^1.2.0
version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-scroll-area':
specifier: ^1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-select':
specifier: ^2.1.1
version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot':
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.4)(react@18.3.1)
@ -124,6 +133,9 @@ importers:
react-dropzone:
specifier: 14.2.3
version: 14.2.3(react@18.3.1)
react-hook-form:
specifier: 7.53.0
version: 7.53.0(react@18.3.1)
react-icons:
specifier: ^5.2.1
version: 5.3.0(react@18.3.1)
@ -139,6 +151,9 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.10)
zod:
specifier: 3.23.8
version: 3.23.8
devDependencies:
'@radix-ui/react-icons':
specifier: ^1.3.0
@ -569,6 +584,11 @@ packages:
engines: {node: '>=6'}
hasBin: true
'@hookform/resolvers@3.9.0':
resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
peerDependencies:
react-hook-form: ^7.0.0
'@huggingface/jinja@0.2.2':
resolution: {integrity: sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==}
engines: {node: '>=18'}
@ -1024,6 +1044,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-radio-group@1.2.0':
resolution: {integrity: sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.0':
resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
peerDependencies:
@ -1050,6 +1083,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.1.1':
resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.0.2':
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies:
@ -3418,6 +3464,12 @@ packages:
peerDependencies:
react: '>= 16.8 || 18.0.0'
react-hook-form@7.53.0:
resolution: {integrity: sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
react-icons@5.3.0:
resolution: {integrity: sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==}
peerDependencies:
@ -4058,6 +4110,9 @@ packages:
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
engines: {node: '>= 10'}
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
snapshots:
7zip-bin@5.2.0: {}
@ -4483,6 +4538,10 @@ snapshots:
protobufjs: 7.4.0
yargs: 17.7.2
'@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))':
dependencies:
react-hook-form: 7.53.0(react@18.3.1)
'@huggingface/jinja@0.2.2': {}
'@humanwhocodes/config-array@0.11.14':
@ -4930,6 +4989,24 @@ snapshots:
'@types/react': 18.3.4
'@types/react-dom': 18.3.0
'@radix-ui/react-radio-group@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-context': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.0(@types/react@18.3.4)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.4
'@types/react-dom': 18.3.0
'@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
@ -4964,6 +5041,35 @@ snapshots:
'@types/react': 18.3.4
'@types/react-dom': 18.3.0
'@radix-ui/react-select@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-context': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.4)(react@18.3.1)
'@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
aria-hidden: 1.2.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.5.7(@types/react@18.3.4)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.4
'@types/react-dom': 18.3.0
'@radix-ui/react-slot@1.0.2(@types/react@18.3.4)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.4
@ -7666,6 +7772,10 @@ snapshots:
prop-types: 15.8.1
react: 18.3.1
react-hook-form@7.53.0(react@18.3.1):
dependencies:
react: 18.3.1
react-icons@5.3.0(react@18.3.1):
dependencies:
react: 18.3.1
@ -8332,3 +8442,5 @@ snapshots:
archiver-utils: 3.0.4
compress-commons: 4.1.2
readable-stream: 3.6.2
zod@3.23.8: {}