Merge pull request #282 from SuanmoSuanyangTechnology/feature/ontology_v2_zy

Feature/ontology v2 zy
This commit is contained in:
yingzhao
2026-02-03 14:13:01 +08:00
committed by GitHub
25 changed files with 832 additions and 95 deletions

View File

@@ -1,33 +1,39 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 13:59:41
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 13:59:41
*/
import { request } from '@/utils/request'
import type { ApiKey } from '@/views/ApiKeyManagement/types'
// API Key列表
// API Key list
export const getApiKeyListUrl = '/apikeys'
export const getApiKeyList = (data: Record<string, unknown>) => {
return request.get(getApiKeyListUrl, data)
}
// API Key详情
// API Key details
export const getApiKey = (id: string) => {
return request.get(`/apikeys/${id}`)
}
// 创建API Key
// Create API Key
export const createApiKey = (values: ApiKey) => {
return request.post('/apikeys', values)
}
// 更新API Key
// Update API Key
export const updateApiKey = (id: string, values: ApiKey) => {
return request.put(`/apikeys/${id}`, values)
}
// 删除 API Key
// Delete API Key
export const deleteApiKey = (id: string) => {
return request.delete(`/apikeys/${id}`)
}
// 使用统计
// Usage statistics
export const getApiKeyStats = (app_key_id: string) => {
return request.get(`/apikeys/${app_key_id}/stats`)
}

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 13:59:45
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 13:59:45
*/
import { request } from '@/utils/request'
import type { ApplicationModalData } from '@/views/ApplicationManagement/types'
import type { Config } from '@/views/ApplicationConfig/types'
@@ -5,71 +11,72 @@ import { handleSSE, type SSEMessage } from '@/utils/stream'
import type { QueryParams } from '@/views/Conversation/types'
import type { WorkflowConfig } from '@/views/Workflow/types'
// 应用列表
// Application list
export const getApplicationListUrl = '/apps'
export const getApplicationList = (data: Record<string, unknown>) => {
return request.get(getApplicationListUrl, data)
}
// 获取应用配置
// Get application config
export const getApplicationConfig = (id: string) => {
return request.get(`/apps/${id}/config`)
}
// 获取集群应用配置
// Get multi-agent config
export const getMultiAgentConfig = (id: string) => {
return request.get(`/apps/${id}/multi-agent`)
}
// 获取 workflow应用配置
// Get workflow config
export const getWorkflowConfig = (id: string) => {
return request.get(`/apps/${id}/workflow`)
}
// 应用详情
// Application details
export const getApplication = (id: string) => {
return request.get(`/apps/${id}`)
}
// 更新应用
// Update application
export const updateApplication = (id: string, values: ApplicationModalData) => {
return request.put(`/apps/${id}`, values)
}
// 创建应用
// Create application
export const addApplication = (values: ApplicationModalData) => {
return request.post('/apps', values)
}
// 保存Agent配置
// Save agent config
export const saveAgentConfig = (app_id: string, values: Config) => {
return request.put(`/apps/${app_id}/config`, values)
}
// 保存集群配置
// Save multi-agent config
export const saveMultiAgentConfig = (app_id: string, values: Config) => {
return request.put(`/apps/${app_id}/multi-agent`, values)
}
// 保存workflow配置
// Save workflow config
export const saveWorkflowConfig = (app_id: string, values: WorkflowConfig) => {
return request.put(`/apps/${app_id}/workflow`, values)
}
// 模型比对试运行
// Model comparison test run
export const runCompare = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run/compare`, values, onMessage)
}
// Test run
export const draftRun = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run`, values, onMessage)
}
// 删除应用
// Delete application
export const deleteApplication = (app_id: string) => {
return request.delete(`/apps/${app_id}`)
}
// 发布版本列表
// Release version list
export const getReleaseList = (app_id: string) => {
return request.get(`/apps/${app_id}/releases`)
}
// 发布版本
// Publish release
export const publishRelease = (app_id: string, values: Record<string, unknown>) => {
return request.post(`/apps/${app_id}/publish`, values)
}
// 回滚版本
// Rollback release
export const rollbackRelease = (app_id: string, version: string) => {
return request.post(`/apps/${app_id}/rollback/${version}`)
}
// 发布版本分享
// Share release
export const shareRelease = (app_id: string, release_id: string) => {
return request.post(`/apps/${app_id}/releases/${release_id}/share`, {
"is_enabled": true,
@@ -77,7 +84,7 @@ export const shareRelease = (app_id: string, release_id: string) => {
"allow_embed": true
})
}
// 获取体验对话历史
// Get conversation history
export const getConversationHistory = (share_token: string, data: { page: number; pagesize: number }) => {
return request.get(`/public/share/conversations`, data, {
headers: {
@@ -85,7 +92,7 @@ export const getConversationHistory = (share_token: string, data: { page: number
}
})
}
// 发送体验对话
// Send conversation
export const sendConversation = (values: QueryParams, onMessage: (data: SSEMessage[]) => void, shareToken: string) => {
return handleSSE(`/public/share/chat`, values, onMessage, {
headers: {
@@ -93,7 +100,7 @@ export const sendConversation = (values: QueryParams, onMessage: (data: SSEMessa
}
})
}
// 获取体验会话详情
// Get conversation details
export const getConversationDetail = (share_token: string, conversation_id: string) => {
return request.get(`/public/share/conversations/${conversation_id}`, {}, {
headers: {
@@ -101,15 +108,15 @@ export const getConversationDetail = (share_token: string, conversation_id: stri
}
})
}
// 获取体验对话token
// Get share token
export const getShareToken = (share_token: string, user_id: string) => {
return request.post(`/public/share/${share_token}/token`, { user_id })
}
// 复制应用
// Copy application
export const copyApplication = (app_id: string, new_name: string) => {
return request.post(`/apps/${app_id}/copy?new_name=${new_name}`)
}
// 数据统计
// Data statistics
export const getAppStatistics = (app_id: string, data: { start_date: number; end_date: number; }) => {
return request.get(`/apps/${app_id}/statistics`, data)
}

View File

@@ -1,5 +1,5 @@
import { request } from "@/utils/request";
// 列表查询参数
// List query parameters
export interface Query {
page?: number;
pagesize?: number;
@@ -38,15 +38,15 @@ export interface versionResponse{
codeName: string;
};
}
// 首页数据统计
// Dashboard data statistics
export const getDashboardData = `/home-page/workspaces`
// 首页数据看板统计
// Dashboard statistics
export const getDashboardStatistics = async () => {
const response = await request.get(`/home-page/statistics`);
return response as DataResponse;
};
// 获取版本号
// Get version
export const getVersion = async () => {
const response = await request.get(`/home-page/version`);
return response as versionResponse;

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 13:59:56
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 13:59:56
*/
import { request, API_PREFIX } from '@/utils/request'
// Upload filefile storage has expiration period

View File

@@ -1,20 +1,26 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:01
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:01
*/
import { request } from '@/utils/request'
// 成员列表
// Member list
export const memberListUrl = '/workspaces/members'
// 邀请成员
// Invite member
export const inviteMember = (values: { email: string }) => {
return request.post(`/workspaces/invites`, values)
}
// 删除成员
// Delete member
export const deleteMember = (id: string) => {
return request.delete(`/workspaces/members/${id}`)
}
// 更新成员
// Update member
export const updateMember = (values: { id: string, role: string }) => {
return request.put(`/workspaces/members`, [values])
}
// 验证邀请token
// Validate invite token
export const validateInviteToken = (token: string) => {
return request.get(`/workspaces/invites/validate/${token}`)
}

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:06
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:06
*/
import { request } from '@/utils/request'
import type {
MemoryFormData,

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:09
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:09
*/
import { request } from '@/utils/request'
import type { MultiKeyForm, Query, KeyConfigModalForm, CompositeModelForm, CustomModelForm } from '@/views/ModelManagement/types'

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

@@ -1,16 +1,23 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:14
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:14
*/
import { request } from '@/utils/request'
import type { VoucherForm } from '@/views/OrderPayment/types'
export const getOrderListUrl = '/v1/orders/customer'
// 提交支付凭证API
// Submit payment voucher API
export const submitPaymentVoucherAPI = (voucherData: VoucherForm) => {
return request.post('/v1/orders/', voucherData)
}
// 订单详情
// Order details
export const getOrderDetail = (order_no: string) => {
return request.get(`/v1/orders/customer/${order_no}`)
}
// Order status enum
export const orderStatusUrl = '/v1/order-status/'
export const getOrderStatus = () => {
return request.get(orderStatusUrl)

View File

@@ -1,3 +1,9 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:17
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:17
*/
import { request } from '@/utils/request'
import type { AiPromptForm } from '@/views/ApplicationConfig/types'
import type { PromptReleaseData } from '@/views/Prompt/types'

View File

@@ -1,40 +1,46 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:23
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:23
*/
import { request } from '@/utils/request'
import type { CreateModalData } from '@/views/UserManagement/types'
import { cookieUtils } from '@/utils/request'
// 用户信息
// User info
export const getUsers = () => {
return request.get('/users')
}
// 用户列表
// User list
export const getUserListUrl = '/users/superusers'
// 登录
// Login
export const loginUrl = '/token'
export const login = (data: { email: string; password: string; invite?: string; username?: string }) => {
return request.post(loginUrl, data)
}
// 刷新token
// Refresh token
export const refreshTokenUrl = '/refresh'
export const refreshToken = () => {
return request.post(refreshTokenUrl, { refresh_token: cookieUtils.get('refreshToken') })
}
// 重置密码
// Reset password
export const changePassword = (data: { user_id: string; new_password: string }) => {
return request.put('/users/admin/change-password', data)
}
// 禁用用户
// Disable user
export const deleteUser = (user_id: string) => {
return request.delete(`/users/${user_id}`)
}
// 启用用户
// Enable user
export const enableUser = (user_id: string) => {
return request.post(`/users/${user_id}/activate`)
}
// 创建用户
// Create user
export const addUser = (data: CreateModalData) => {
return request.post('/users/superuser', data)
}
// 注销
// Logout
export const logoutUrl = '/logout'
export const logout = () => {
return request.post(logoutUrl)

View File

@@ -1,28 +1,34 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 14:00:26
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 14:00:26
*/
import { request } from '@/utils/request'
import type { SpaceModalData } from '@/views/SpaceManagement/types'
import type { ConfigModalData } from '@/views/UserMemory/types'
import type { SpaceConfigData } from '@/views/SpaceConfig/types'
// 空间列表
// Workspace list
export const getWorkspaces = () => {
return request.get('/workspaces')
}
// 创建空间
// Create workspace
export const createWorkspace = (values: SpaceModalData) => {
return request.post('/workspaces', values)
}
// 切换空间
// Switch workspace
export const switchWorkspace = (workspaceId: string) => {
return request.put(`/workspaces/${workspaceId}/switch`)
}
// 获取空间存储类型
// Get workspace storage type
export const getWorkspaceStorageType = () => {
return request.get(`/workspaces/storage`)
}
// 获取空间模型配置
// Get workspace model config
export const getWorkspaceModels = () => {
return request.get(`/workspaces/workspace_models`)
}
// 更新空间模型配置
export const updateWorkspaceModels = (data: ConfigModalData) => {
// Update workspace model config
export const updateWorkspaceModels = (data: SpaceConfigData) => {
return request.put(`/workspaces/workspace_models`, data)
}

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';
}