feat(web): Ontology support import & export;

docs(web): add comments to the src/views/Ontology directory
This commit is contained in:
zhaoying
2026-02-03 14:12:06 +08:00
parent fb76f765cc
commit 5e1e5f68e1
14 changed files with 714 additions and 39 deletions

View File

@@ -1,5 +1,11 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 13:59:12
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 13:59:12
*/
import { request } from '@/utils/request'
import type { Query, OntologyModalData, OntologyClassModalData, OntologyClassExtractModalData } from '@/views/Ontology/types'
import type { Query, OntologyModalData, OntologyClassModalData, OntologyClassExtractModalData, OntologyExportModalData } from '@/views/Ontology/types'
// Scene list
export const getOntologyScenesUrl = '/memory/ontology/scenes'
@@ -37,3 +43,11 @@ export const createOntologyClass = (data: OntologyClassModalData) => {
export const deleteOntologyClass = (class_id: string) => {
return request.delete(`/memory/ontology/class/${class_id}`)
}
// Import scenario
export const ontologyImport = (data: unknown) => {
return request.uploadFile('/memory/ontology/import', data)
}
// Export scenario
export const ontologyExport = (data: OntologyExportModalData, fileName: string, callback: () => void) => {
return request.downloadFile('/memory/ontology/export', fileName, data, callback)
}

View File

@@ -57,6 +57,10 @@ const ALL_FILE_TYPE: {
htm: 'text/html',
html: 'text/html',
json: 'application/json',
owl: 'application/rdf+xml',
ttl: 'text/turtle',
rdf: 'application/rdf+xml',
xml: 'application/rdf+xml',
}
export interface UploadFilesRef {
fileList: UploadFile[];
@@ -122,7 +126,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
if (fileSize) {
const isLtMaxSize = (file.size / 1024 / 1024) < fileSize;
if (!isLtMaxSize) {
message.error(`文件大小不能超过 ${fileSize}MB`);
message.error(t('common.fileSizeTip', { size: fileSize }));
return Upload.LIST_IGNORE;
}
}
@@ -139,7 +143,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
const isValidMimeType = file.type && accept ? accept.includes(file.type) : true;
if (!isValidExtension && !isValidMimeType) {
message.error(`不支持的文件类型: ${fileExtension || file.type}`);
message.error(`${t('common.fileAcceptTip')}${fileExtension || file.type}`);
return Upload.LIST_IGNORE;
}
}
@@ -236,12 +240,12 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
fileList,
beforeUpload,
headers: {
authorization: cookieUtils.get('authToken') || '',
authorization: `Bearer ${cookieUtils.get('authToken')}`,
},
onRemove: handleRemove,
onChange: handleChange,
accept,
disabled,
disabled: disabled || fileList.length >= maxCount,
showUploadList: {
showPreviewIcon: false,
showRemoveIcon: true,
@@ -249,12 +253,12 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
},
itemRender: (_, file, __, actions) => {
return (
<div key={file.uid} className="rb:relative rb:w-full rb:pt-[8px] rb:pl-[10px] rb:pr-[10px] rb-pb-[10px] rb:border-1 rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white">
<div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]">
<div key={file.uid} className="rb:relative rb:w-full rb:pt-2 rb:pl-2.5 rb:pr-2.5 rb-pb-[10px] rb:border rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white">
<div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-0.5">
{file.name}
<span className="rb:text-[#5B6167] rb:cursor-pointer" onClick={() => actions?.remove()}>Cancel</span>
<span className="rb:text-[#5B6167] rb:cursor-pointer" onClick={() => actions?.remove()}>{t('common.cancel')}</span>
</div>
<Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />
{isAutoUpload && <Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />}
</div>
);
},
@@ -267,20 +271,20 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
clearFiles
}));
const hasProgress = fileList.some((item) => item.percent !== 100);
const hasProgress = isAutoUpload && fileList.some((item) => item.percent !== 100);
if (isCanDrag) {
return (
<div className="rb:mb-[24px] rb:w-full">
<div className="rb:mb-6 rb:w-full">
<Dragger {...uploadProps} style={{ height: '270px' }}>
<div className="rb:flex rb:justify-center rb:flex-col rb:items-center">
<img className="rb:w-[48px] rb:h-[48px]" src={CloudUploadOutlined} />
{!hasProgress && (!fileList || !fileList.length) &&
<img className="rb:w-12 rb:h-12" src={CloudUploadOutlined} />
{(!isAutoUpload || !hasProgress && (!fileList || !fileList.length)) &&
<>
<div className="rb:text-base rb:text-[14px] rb:font-medium rb:flex rb:items-center rb:mt-[8px] rb:leading-[20px]">
{t('common.dragUploadTip')}<span className="rb:ml-[4px] rb:text-[#155EEF]">{t('common.uploadClickTip')}</span>
<div className="rb:text-base rb:text-[14px] rb:font-medium rb:flex rb:items-center rb:mt-2 rb:leading-5">
{t('common.dragUploadTip')}<span className="rb:ml-1 rb:text-[#155EEF]">{t('common.uploadClickTip')}</span>
</div>
{fileType && <div className="rb:text-[12px] rb:text-[#A8A9AA] rb:leading-[14px] rb:mt-[8px] rb:cursor-pointer">{t('common.supportedFileTypes', { types: fileType.join(',') })}</div>}
{fileType && <div className="rb:text-[12px] rb:text-[#A8A9AA] rb:leading-3.5 rb:mt-2 rb:cursor-pointer">{t('common.supportedFileTypes', { types: fileType.join(',') })}</div>}
{(fileSize || fileType || maxCount > 1) && (
<div className='rb:text-xs rb:mt-2 rb:text-[#A8A9AA]'>
{t('common.uploadFileTipMax', { max: fileSize, maxCount: maxCount })}
@@ -288,7 +292,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
)}
</>
}
{hasProgress && <div className="rb:text-base rb:text-[14px] rb:font-medium rb:flex rb:items-center rb:mt-[8px] rb:mb-[24px] rb:leading-[20px]">{t('common.uploading')}</div>}
{hasProgress && <div className="rb:text-base rb:text-[14px] rb:font-medium rb:flex rb:items-center rb:mt-2 rb:mb-6 rb:leading-5">{t('common.uploading')}</div>}
</div>
</Dragger>
</div>

View File

@@ -426,6 +426,7 @@ export const en = {
fileAcceptTip: 'Unsupported file type:',
nextStep: 'Next Step',
prevStep: 'Previous Step',
exportSuccess: 'Export successful',
},
model: {
searchPlaceholder: 'search model…',
@@ -2471,6 +2472,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
extract: 'Project Inference',
source: 'Not Added',
target: 'Added',
import: 'Import Scenario',
format: 'Export Format',
export: 'Export Scenario',
scene_id: 'Scenario',
file: 'Import File',
},
prompt: {
editor: 'Prompt Generator',

View File

@@ -980,6 +980,7 @@ export const zh = {
fileAcceptTip: '不支持的文件类型:',
nextStep: '下一步',
prevStep: '上一步',
exportSuccess: '导出成功',
},
product: {
applicationManagement: '应用管理',
@@ -2560,6 +2561,11 @@ export const zh = {
extract: '工程推理',
source: '未添加项',
target: '已添加项',
import: '导入场景',
format: '导出格式',
export: '导出场景',
scene_id: '场景',
file: '导入文件',
},
prompt: {
editor: '提示词生成器',

View File

@@ -288,19 +288,20 @@ export const request = {
...config
});
},
downloadFile(url: string, fileName: string, data?: unknown) {
downloadFile(url: string, fileName: string, data?: unknown, callback?: () => void) {
service.post(url, data, {
responseType: "blob",
})
.then(res =>{
const link = document.createElement("a");
const blob = new Blob([res.data], { type: "application/vnd.ms-excel" });
const blob = new Blob([res as unknown as BlobPart]);
link.style.display = "none";
link.href = URL.createObjectURL(blob);
link.setAttribute("download", decodeURI(res.headers['filename'] || fileName));
link.setAttribute("download", decodeURI(fileName || fileName));
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
callback?.()
});
}
};

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:42
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:42
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, App, Transfer, type TransferProps, Flex } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -12,24 +18,37 @@ import Tag from '@/components/Tag';
const FormItem = Form.Item;
/**
* Props for OntologyClassExtractModal component
*/
interface OntologyClassExtractModalProps {
/** Callback function to refresh parent list after extraction */
refresh: () => void;
}
/**
* Modal component for extracting ontology classes using LLM
* Two-step process: 1) Extract classes from scenario 2) Select and confirm classes to add
*/
const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, OntologyClassExtractModalProps>(({
refresh
}, ref) => {
// Hooks
const { t } = useTranslation();
const { message } = App.useApp();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<OntologyClassExtractModalData>();
// State
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false)
const [data, setData] = useState<OntologyClassData | null>(null)
const [extractData, setExtractData] = useState<ExtractData | null>(null)
const [targetKeys, setTargetKeys] = useState<TransferProps['targetKeys']>([]);
const [selectedKeys, setSelectedKeys] = useState<TransferProps['selectedKeys']>([]);
// 封装取消方法,添加关闭弹窗逻辑
/**
* Close modal and reset all state
*/
const handleClose = () => {
setVisible(false);
form.resetFields();
@@ -38,12 +57,19 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
setExtractData(null)
};
/**
* Open modal with scene data
* @param vo - Ontology class data containing scene information
*/
const handleOpen = (vo: OntologyClassData) => {
form.resetFields();
setVisible(true);
setData(vo)
};
// 封装保存方法,添加提交逻辑
/**
* Execute LLM extraction to get class suggestions
*/
const handleSave = () => {
if (!data?.scene_id) return;
form
@@ -69,6 +95,10 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
});
}
/**
* Confirm and create selected classes
* First click runs extraction, second click creates classes
*/
const handleConfirm = () => {
if (!extractData) {
handleSave()
@@ -92,11 +122,19 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
}
}
/**
* Handle transfer component target keys change
* @param nextTargetKeys - New target keys after transfer
*/
const onChange: TransferProps['onChange'] = (nextTargetKeys) => {
setTargetKeys(nextTargetKeys.filter(Boolean));
};
/**
* Handle transfer component selection change
* @param sourceSelectedKeys - Selected keys in source list
* @param targetSelectedKeys - Selected keys in target list
*/
const onSelectChange: TransferProps['onSelectChange'] = (
sourceSelectedKeys,
targetSelectedKeys,
@@ -104,7 +142,9 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys].filter(Boolean));
};
// 暴露给父组件的方法
/**
* Expose methods to parent component via ref
*/
useImperativeHandle(ref, () => ({
handleOpen,
}));

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:39
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:39
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, App } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -8,33 +14,53 @@ import { createOntologyClass } from '@/api/ontology'
const FormItem = Form.Item;
/**
* Props for OntologyClassModal component
*/
interface OntologyClassModalProps {
/** Callback function to refresh parent list after save */
refresh: () => void;
}
/**
* Modal component for adding new ontology classes
* Provides form interface for class name and description
*/
const OntologyClassModal = forwardRef<OntologyClassModalRef, OntologyClassModalProps>(({
refresh
}, ref) => {
// Hooks
const { t } = useTranslation();
const { message } = App.useApp();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<AddClassItem>();
// State
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false)
const [scene_id, setSceneId] = useState<string | null>(null)
// 封装取消方法,添加关闭弹窗逻辑
/**
* Close modal and reset form state
*/
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false)
};
/**
* Open modal for adding a new class
* @param scene_id - Target scene identifier
*/
const handleOpen = (scene_id: string) => {
form.resetFields();
setVisible(true);
setSceneId(scene_id)
};
// 封装保存方法,添加提交逻辑
/**
* Validate and submit form data to create new class
*/
const handleSave = () => {
if (!scene_id) return;
form
@@ -56,7 +82,9 @@ const OntologyClassModal = forwardRef<OntologyClassModalRef, OntologyClassModalP
});
}
// 暴露给父组件的方法
/**
* Expose methods to parent component via ref
*/
useImperativeHandle(ref, () => ({
handleOpen,
}));

View File

@@ -0,0 +1,144 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:46
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:46
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, App, Select, type SelectProps } from 'antd';
import { useTranslation } from 'react-i18next';
import type { OntologyExportModalData, OntologyExportModalRef } from '../types'
import RbModal from '@/components/RbModal'
import { ontologyExport, getOntologyScenesUrl } from '@/api/ontology'
import CustomSelect from '@/components/CustomSelect';
const FormItem = Form.Item;
/**
* Props for OntologyExportModal component
*/
interface OntologyExportModalProps {
/** Callback function to refresh parent list after export */
refresh: () => void;
}
/**
* Modal component for exporting ontology scenes
* Supports RDF/XML (.owl) and Turtle (.ttl) formats
*/
const OntologyExportModal = forwardRef<OntologyExportModalRef, OntologyExportModalProps>(({
refresh
}, ref) => {
// Hooks
const { t } = useTranslation();
const { message } = App.useApp();
const [form] = Form.useForm<OntologyExportModalData>();
// State
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false)
const [fileName, setFileName] = useState('')
/**
* Close modal and reset form state
*/
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false)
};
/**
* Open the export modal
*/
const handleOpen = () => {
form.resetFields();
setVisible(true);
};
/**
* Handle scene selection change to set export filename
* @param _value - Selected scene ID
* @param option - Selected option containing scene name
*/
const handleChange: SelectProps['onChange'] = (_value, option) => {
const name = Array.isArray(option) ? option[0]?.children : option?.children;
setFileName(String(name || ''));
}
/**
* Validate and submit form data to export ontology
* Downloads file with appropriate extension based on format
*/
const handleSave = () => {
form
.validateFields()
.then((values) => {
setLoading(true)
ontologyExport(values, `${fileName}.${values.format === 'rdfxml' ?'owl' : 'ttl'}`, () => {
message.success(t('common.exportSuccess'));
handleClose();
refresh();
setLoading(false)
})
})
.catch((err) => {
console.log('err', err)
});
}
/**
* Expose methods to parent component via ref
*/
useImperativeHandle(ref, () => ({
handleOpen,
}));
return (
<RbModal
title={t('ontology.export')}
open={visible}
onCancel={handleClose}
okText={t('common.export')}
onOk={handleSave}
confirmLoading={loading}
>
<Form
form={form}
layout="vertical"
initialValues={{ format: 'rdfxml' }}
>
<FormItem
name="scene_id"
label={t('ontology.scene_id')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
>
<CustomSelect
url={getOntologyScenesUrl}
params={{ page: 1, pagesize: 100 }}
valueKey="scene_id"
labelKey="scene_name"
hasAll={false}
onChange={handleChange}
/>
</FormItem>
<FormItem
name="format"
label={t('ontology.format')}
>
<Select
placeholder={t('common.pleaseSelect')}
options={[
{ value: 'rdfxml', label: 'RDF/XML' },
{ value: 'turtle', label: 'Turtle' },
]}
/>
</FormItem>
</Form>
</RbModal>
);
});
export default OntologyExportModal;

View File

@@ -0,0 +1,139 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:32
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:32
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, App } from 'antd';
import { useTranslation } from 'react-i18next';
import type { OntologyImportModalData, OntologyImportModalRef } from '../types'
import RbModal from '@/components/RbModal'
import { ontologyImport } from '@/api/ontology'
import UploadFiles from '@/components/Upload/UploadFiles';
const FormItem = Form.Item;
/**
* Props for OntologyImportModal component
*/
interface OntologyImportModalProps {
/** Callback function to refresh parent list after import */
refresh: () => void;
}
/**
* Modal component for importing ontology files
* Supports OWL, TTL, RDF, XML file formats
*/
const OntologyImportModal = forwardRef<OntologyImportModalRef, OntologyImportModalProps>(({
refresh
}, ref) => {
// Hooks
const { t } = useTranslation();
const { message } = App.useApp();
const [form] = Form.useForm<OntologyImportModalData>();
// State
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false)
/**
* Close modal and reset form state
*/
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false)
};
/**
* Open the import modal
*/
const handleOpen = () => {
form.resetFields();
setVisible(true);
};
/**
* Validate and submit form data to import ontology file
* Creates FormData with file and scene information
*/
const handleSave = () => {
form
.validateFields()
.then((values) => {
const { scene_name, scene_description, file } = values
console.log('values', file);
const formData = new FormData();
formData.append('file', file[0]);
formData.append('scene_name', scene_name);
if (scene_description) {
formData.append('scene_description', scene_description);
}
setLoading(true)
ontologyImport(formData)
.then(() => {
message.success(t('common.saveSuccess'));
handleClose();
refresh();
})
.finally(() => setLoading(false))
})
.catch((err) => {
console.log('err', err)
});
}
/**
* Expose methods to parent component via ref
*/
useImperativeHandle(ref, () => ({
handleOpen,
}));
return (
<RbModal
title={t('ontology.import')}
open={visible}
onCancel={handleClose}
okText={t('common.create')}
onOk={handleSave}
confirmLoading={loading}
>
<Form
form={form}
layout="vertical"
>
<FormItem
name="scene_name"
label={t('ontology.scene_name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="scene_description"
label={t('ontology.scene_description')}
>
<Input.TextArea placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="file"
label={t('ontology.file')}
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<UploadFiles
isCanDrag={true}
fileType={['owl', 'ttl', 'rdf', 'xml']}
isAutoUpload={false}
/>
</FormItem>
</Form>
</RbModal>
);
});
export default OntologyImportModal;

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:28
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:28
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, App } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -8,21 +14,34 @@ import { createOntologyScene, updateOntologyScene } from '@/api/ontology'
const FormItem = Form.Item;
/**
* Props for OntologyModal component
*/
interface OntologyModalProps {
/** Callback function to refresh parent list after save */
refresh: () => void;
}
/**
* Modal component for creating or editing ontology scenes
* Provides form interface for scene name and description
*/
const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
refresh
}, ref) => {
// Hooks
const { t } = useTranslation();
const { message } = App.useApp();
const [form] = Form.useForm<OntologyModalData>();
// State
const [visible, setVisible] = useState(false);
const [editVo, setEditVo] = useState<OntologyItem | null>(null)
const [form] = Form.useForm<OntologyModalData>();
const [loading, setLoading] = useState(false)
// 封装取消方法,添加关闭弹窗逻辑
/**
* Close modal and reset form state
*/
const handleClose = () => {
setVisible(false);
form.resetFields();
@@ -30,6 +49,10 @@ const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
setEditVo(null)
};
/**
* Open modal for creating or editing
* @param vo - Optional ontology item data for edit mode
*/
const handleOpen = (vo?: OntologyItem) => {
if (vo) {
setEditVo(vo);
@@ -39,7 +62,11 @@ const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
}
setVisible(true);
};
// 封装保存方法,添加提交逻辑
/**
* Validate and submit form data
* Creates new scene or updates existing one based on editVo
*/
const handleSave = () => {
form
.validateFields()
@@ -59,7 +86,9 @@ const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
});
}
// 暴露给父组件的方法
/**
* Expose methods to parent component via ref
*/
useImperativeHandle(ref, () => ({
handleOpen,
}));

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:24
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:56
*/
import { type FC, type ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { Layout, Button } from 'antd';
@@ -6,11 +12,23 @@ import logoutIcon from '@/assets/images/logout_hover.svg'
const { Header } = Layout;
/**
* Props for PageHeader component
*/
interface ConfigHeaderProps {
/** Page title/name */
name?: string;
/** Subtitle content displayed below the title */
subTitle?: ReactNode | string;
/** Extra content displayed on the right side */
extra?: ReactNode;
}
/**
* Page header component for ontology pages
* Displays title, subtitle, back button and extra actions
* @param props - Component props
*/
const PageHeader: FC<ConfigHeaderProps> = ({
name,
subTitle,
@@ -19,6 +37,9 @@ const PageHeader: FC<ConfigHeaderProps> = ({
const { t } = useTranslation();
const navigate = useNavigate();
/**
* Navigate back to previous page
*/
const goBack = () => {
navigate(-1)
}

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:15
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:15
*/
import { type FC, useState, useRef, type MouseEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -5,29 +11,57 @@ import { Row, Col, Button, Flex, Divider, Space, App, Tooltip } from 'antd'
import SearchInput from '@/components/SearchInput';
import OntologyModal from './components/OntologyModal'
import type { OntologyModalRef, OntologyItem, Query } from './types'
import type { OntologyModalRef, OntologyItem, Query, OntologyImportModalRef, OntologyExportModalRef } from './types'
import RbCard from '@/components/RbCard/Card'
import Tag from '@/components/Tag'
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
import { getOntologyScenesUrl, deleteOntologyScene } from '@/api/ontology'
import { formatDateTime } from '@/utils/format'
import OntologyImportModal from './components/OntologyImportModal'
import OntologyExportModal from './components/OntologyExportModal'
/**
* Ontology management page component
* Displays a list of ontology scenes with search, create, import, export functionality
*/
const Ontology: FC = () => {
// Hooks
const { t } = useTranslation();
const navigate = useNavigate()
const { modal, message } = App.useApp();
// State
const [query, setQuery] = useState<Query>({});
// Refs
const scrollListRef = useRef<PageScrollListRef>(null)
const entityModalRef = useRef<OntologyModalRef>(null)
const ontologyImportModalRef = useRef<OntologyImportModalRef>(null)
const ontologyExportModalRef = useRef<OntologyExportModalRef>(null)
/**
* Open modal to create a new ontology scene
*/
const handleCreate = () => {
entityModalRef.current?.handleOpen()
}
/**
* Open modal to edit an existing ontology scene
* @param record - The ontology item to edit
* @param e - Mouse event to prevent propagation
*/
const handleEdit = (record: OntologyItem, e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
entityModalRef.current?.handleOpen(record)
}
/**
* Delete an ontology scene with confirmation
* @param item - The ontology item to delete
* @param e - Mouse event to prevent propagation
*/
const handleDelete = (item: OntologyItem, e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -45,9 +79,35 @@ const Ontology: FC = () => {
}
})
}
/**
* Navigate to ontology detail page
* @param record - The ontology item to view
*/
const handleJump = (record: OntologyItem) => {
navigate(`/ontology/${record.scene_id}`)
}
/**
* Refresh the ontology list
*/
const handleRefresh = () => {
scrollListRef.current?.refresh()
}
/**
* Open export modal
*/
const handleExport = () => {
ontologyExportModalRef.current?.handleOpen()
}
/**
* Open import modal
*/
const handleImport = () => {
ontologyImportModalRef.current?.handleOpen()
}
return (
<>
@@ -60,9 +120,17 @@ const Ontology: FC = () => {
/>
</Col>
<Col span={16} className="rb:text-right">
<Button type="primary" onClick={handleCreate}>
+ {t('ontology.create')}
</Button>
<Space size={12}>
<Button onClick={handleExport}>
{t('ontology.export')}
</Button>
<Button onClick={handleImport}>
{t('ontology.import')}
</Button>
<Button type="primary" onClick={handleCreate}>
+ {t('ontology.create')}
</Button>
</Space>
</Col>
</Row>
@@ -124,7 +192,15 @@ const Ontology: FC = () => {
<OntologyModal
ref={entityModalRef}
refresh={() => scrollListRef.current?.refresh()}
refresh={handleRefresh}
/>
<OntologyImportModal
ref={ontologyImportModalRef}
refresh={handleRefresh}
/>
<OntologyExportModal
ref={ontologyExportModalRef}
refresh={handleRefresh}
/>
</>
)

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:20
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:20
*/
import { type FC, useEffect, useState, useRef } from 'react'
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -12,22 +18,35 @@ import SearchInput from '@/components/SearchInput';
import OntologyClassExtractModal from '../components/OntologyClassExtractModal'
import BodyWrapper from '@/components/Empty/BodyWrapper'
/**
* Ontology detail page component
* Displays and manages classes within a specific ontology scene
*/
const Detail: FC = () => {
// Hooks
const { t } = useTranslation();
const { id } = useParams()
const { modal, message } = App.useApp()
// Refs
const ontologyClassModalRef = useRef<OntologyClassModalRef>(null)
const ontologyClassExtractModalRef = useRef<OntologyClassExtractModalRef>(null)
// State
const [query, setQuery] = useState<{
class_name?: string;
}>({});
const [loading, setLoading] = useState(false)
const [data, setData] = useState<OntologyClassData>({} as OntologyClassData)
// Fetch data when component mounts or dependencies change
useEffect(() => {
getData()
}, [id, query])
/**
* Fetch ontology class list data
*/
const getData = () => {
if (!id) return;
setLoading(true)
@@ -42,6 +61,11 @@ const Detail: FC = () => {
setLoading(false)
})
}
/**
* Delete an ontology class with confirmation
* @param item - The class item to delete
*/
const handleDelete = (item: OntologyClassItem) => {
modal.confirm({
title: t('common.confirmDeleteDesc', { name: item.class_name }),
@@ -57,9 +81,17 @@ const Detail: FC = () => {
}
})
}
/**
* Open modal to add a new class
*/
const handleAdd = () => {
ontologyClassModalRef.current?.handleOpen(data.scene_id)
}
/**
* Open modal to extract classes using LLM
*/
const handleExtract = () => {
ontologyClassExtractModalRef.current?.handleOpen(data)
}

View File

@@ -1,79 +1,214 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:10:10
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:10:10
*/
/**
* Query parameters for ontology list pagination and filtering
*/
export interface Query {
/** Number of items per page */
pagesize?: number;
/** Current page number */
page?: number;
/** Scene name for filtering */
scene_name?: string;
}
/**
* Ontology scene item data structure
*/
export interface OntologyItem {
/** Unique identifier for the scene */
scene_id: string;
/** Name of the ontology scene */
scene_name: string;
/** Description of the ontology scene */
scene_description: string;
/** Number of entity types in the scene */
type_num: number;
/** Array of entity type names */
entity_type: string[];
/** Associated workspace identifier */
workspace_id: string;
/** Creation timestamp */
created_at: number;
/** Last update timestamp */
updated_at: number;
/** Total count of classes in the scene */
classes_count: number;
}
/**
* Form data for creating/editing ontology scene
*/
export interface OntologyModalData {
/** Scene name */
scene_name: string;
/** Scene description */
scene_description: string;
}
/**
* Ref methods exposed by OntologyModal component
*/
export interface OntologyModalRef {
/**
* Open the modal for creating or editing
* @param data - Optional ontology item data for editing mode
*/
handleOpen: (data?: OntologyItem) => void;
}
/**
* Ontology class item data structure
*/
export interface OntologyClassItem {
/** Unique identifier for the class */
class_id: string;
/** Name of the class */
class_name: string;
/** Description of the class */
class_description: string;
/** Associated scene identifier */
scene_id: string;
/** Creation timestamp */
created_at: number;
/** Last update timestamp */
updated_at: number;
}
/**
* Response data structure for ontology class list
*/
export interface OntologyClassData {
/** Total number of classes */
total: number;
/** Scene identifier */
scene_id: string;
/** Scene name */
scene_name: string;
/** Scene description */
scene_description: string;
/** Array of class items */
items: OntologyClassItem[];
}
/**
* Data structure for adding a new class
*/
export interface AddClassItem {
/** Name of the class to add */
class_name: string;
/** Description of the class to add */
class_description: string;
}
/**
* Form data for creating ontology classes
*/
export interface OntologyClassModalData {
/** Target scene identifier */
scene_id: string;
/** Array of classes to create */
classes: AddClassItem[]
}
/**
* Ref methods exposed by OntologyClassModal component
*/
export interface OntologyClassModalRef {
/**
* Open the modal for adding classes
* @param scene_id - Target scene identifier
*/
handleOpen: (scene_id: string) => void;
}
/**
* Form data for extracting ontology classes using LLM
*/
export interface OntologyClassExtractModalData {
/** LLM model identifier */
llm_id: string;
/** Target scene identifier */
scene_id: string;
/** Scenario description for extraction */
scenario: string;
domain: string; // scene_name
/** Domain name (same as scene_name) */
domain: string;
}
/**
* Ref methods exposed by OntologyClassExtractModal component
*/
export interface OntologyClassExtractModalRef {
/**
* Open the modal for extracting classes
* @param vo - Ontology class data containing scene information
*/
handleOpen: (vo: OntologyClassData) => void;
}
/**
* Extracted class item from LLM
*/
export interface ExtractClassItem {
/** Unique identifier for the extracted class */
id: string;
/** English name of the class */
name: string;
/** Chinese name of the class */
name_chinese: string;
/** Description of the class */
description: string;
/** Example instances of the class */
examples: string[];
/** Parent class name if exists */
parent_class: string | null;
/** Entity type classification */
entity_type: string;
/** Domain the class belongs to */
domain: string;
}
/**
* Response data structure for class extraction
*/
export interface ExtractData {
/** Domain name */
domain: string;
/** Number of classes extracted */
extracted_count: number;
/** Array of extracted class items */
classes: ExtractClassItem[]
}
/**
* Ref methods exposed by OntologyImportModal component
*/
export interface OntologyImportModalRef {
/** Open the import modal */
handleOpen: () => void;
}
/**
* Form data for importing ontology
*/
export interface OntologyImportModalData {
/** Name for the imported scene */
scene_name: string;
/** Optional description for the imported scene */
scene_description?: string;
/** File to import (OWL, TTL, RDF, XML formats) */
file: any;
}
/**
* Ref methods exposed by OntologyExportModal component
*/
export interface OntologyExportModalRef {
/** Open the export modal */
handleOpen: () => void;
}
/**
* Form data for exporting ontology
*/
export interface OntologyExportModalData {
/** Scene identifier to export */
scene_id: string;
/** Export format: 'rdfxml' (.owl) or 'turtle' (.ttl) */
format: 'rdfxml' | 'turtle';
}