diff --git a/web/src/api/ontology.ts b/web/src/api/ontology.ts new file mode 100644 index 00000000..4213d362 --- /dev/null +++ b/web/src/api/ontology.ts @@ -0,0 +1,39 @@ +import { request } from '@/utils/request' +import type { Query, OntologyModalData, OntologyClassModalData, OntologyClassExtractModalData } from '@/views/Ontology/types' + +// Scene list +export const getOntologyScenesUrl = '/memory/ontology/scenes' +export const getOntologyScenesList = (data: Query) => { + return request.get(getOntologyScenesUrl, data) +} + +// Create scene +export const createOntologyScene = (data: OntologyModalData) => { + return request.post('/memory/ontology/scene', data) +} +// Update scene +export const updateOntologyScene = (scene_id: string, data: OntologyModalData) => { + return request.put(`/memory/ontology/scene/${scene_id}`, data) +} +// Delete scene +export const deleteOntologyScene = (scene_id: string) => { + return request.delete(`/memory/ontology/scene/${scene_id}`) +} + +// Get class list +export const getOntologyclassesUrl = '/memory/ontology/classes' +export const getOntologyClassList = (data: { scene_id: string; class_name?: string; }) => { + return request.get(getOntologyclassesUrl, data) +} +// Extract ontology types +export const extractOntologyTypes = (data: OntologyClassExtractModalData) => { + return request.post('/memory/ontology/extract', data) +} +// Create ontology class +export const createOntologyClass = (data: OntologyClassModalData) => { + return request.post('/memory/ontology/class', data) +} +// Delete ontology class +export const deleteOntologyClass = (class_id: string) => { + return request.delete(`/memory/ontology/class/${class_id}`) +} diff --git a/web/src/assets/images/menu/ontology.svg b/web/src/assets/images/menu/ontology.svg new file mode 100644 index 00000000..9bfda42b --- /dev/null +++ b/web/src/assets/images/menu/ontology.svg @@ -0,0 +1,11 @@ + + + 本体管理备份 + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menu/ontology_active.svg b/web/src/assets/images/menu/ontology_active.svg new file mode 100644 index 00000000..1271c2c3 --- /dev/null +++ b/web/src/assets/images/menu/ontology_active.svg @@ -0,0 +1,11 @@ + + + 本体管理 + + + + + + + + \ No newline at end of file diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index e3ba4448..601022d1 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -42,6 +42,8 @@ import pricingIcon from '@/assets/images/menu/pricing.svg' import pricingActiveIcon from '@/assets/images/menu/pricing_active.svg' import spaceConfigIcon from '@/assets/images/menu/spaceConfig.svg' import spaceConfigActiveIcon from '@/assets/images/menu/spaceConfig_active.svg' +import ontologyIcon from '@/assets/images/menu/ontology.svg' +import ontologyActiveIcon from '@/assets/images/menu/ontology_active.svg' // 图标路径映射表 const iconPathMap: Record = { @@ -73,6 +75,8 @@ const iconPathMap: Record = { 'pricingActive': pricingActiveIcon, 'spaceConfig': spaceConfigIcon, 'spaceConfigActive': spaceConfigActiveIcon, + 'ontology': ontologyIcon, + 'ontologyActive': ontologyActiveIcon, }; const { Sider } = Layout; @@ -115,7 +119,7 @@ const Menu: FC<{ // 叶子节点 if (!subs || subs.length === 0) { if (!menu.path) return null; - + return { key: menu.path, title: menu.i18nKey ? t(menu.i18nKey) : menu.label, @@ -124,13 +128,13 @@ const Menu: FC<{ {menu.i18nKey ? t(menu.i18nKey) : menu.label} ), - icon: iconSrc ? : null, }; } - + // 有子菜单的节点 const menuLabel = menu.i18nKey ? t(menu.i18nKey) : menu.label; @@ -138,15 +142,15 @@ const Menu: FC<{ key: `submenu-${menu.id}`, title: menuLabel, label: menuLabel, - icon: iconSrc ? : , children: generateMenuItems(subs), }; }).filter(Boolean); }; - + // 生成菜单项 const menuItems = generateMenuItems(menus); // 初始加载菜单 @@ -164,17 +168,17 @@ const Menu: FC<{ for (const menu of menuList) { if (menu.path) { const menuPath = menu.path[0] !== '/' ? '/' + menu.path : menu.path; - + // 精确匹配或路径前缀匹配(确保是完整路径段匹配) const isExactMatch = menuPath === currentPath; - const isPrefixMatch = currentPath.startsWith(menuPath + '/') || - currentPath === menuPath; - + const isPrefixMatch = currentPath.startsWith(menuPath + '/') || + currentPath === menuPath; + if (isExactMatch || isPrefixMatch) { return { key: menu.path }; } } - + // 递归检查子菜单 if (menu.subs && menu.subs.length > 0) { const newParentPaths = [...parentPaths, `submenu-${menu.id}`]; @@ -201,7 +205,7 @@ const Menu: FC<{ } return ( - - : !collapsed - ?
+ : !collapsed + ?
{t('title')}
- : null + : null }
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 1df2eb6d..d4c15e2b 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -112,7 +112,8 @@ export const en = { pricing: 'Pricing Management', orderPayment: 'Order Payment', orderHistory: 'Order History', - spaceConfig: 'Space Configuration' + spaceConfig: 'Space Configuration', + ontology: 'Ontology Engineering', }, dashboard: { total_models: 'Available Models', @@ -807,7 +808,8 @@ export const en = { inactive: 'Inactive', configurationName: 'Configuration Name', emotionEngine: 'Emotion Engine', - reflectionEngine: 'Self-Reflection Engine' + reflectionEngine: 'Self-Reflection Engine', + scene_id: 'Ontology Scenario', }, member: { username: 'Username', @@ -2355,6 +2357,34 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re question: 'Lessons Learned', summary: 'Core Insights', none: 'None' - } + }, + ontology: { + searchPlaceholder: 'Search scenarios', + create: 'Create Project', + edit: 'Edit Project', + scene_name: 'Scenario Name', + scene_description: 'Scenario Description', + descriptionPlaceholder: 'Describe the purpose of this scenario and the entity types to extract', + typeCount: 'types', + created_at: 'Created At', + updated_at: 'Updated At', + entityTypes: 'Entity Types', + + addClass: 'Add Type', + class_name: 'Type Name', + class_description: 'Type Definition', + classDescriptionPlaceholder: 'Describe the meaning and purpose of this type', + + llm_id: 'Select Model', + scenario: 'Scenario Description', + scenarioPlaceholder: 'Please describe your business requirements', + run: 'Inference', + loadingConfirm: 'Inferring', + extractConfirm: 'Add Selected Types', + classType: 'Project Type', + extract: 'Project Inference', + source: 'Not Added', + target: 'Added', + }, }, }; diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 39908757..9eef0e5a 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -111,7 +111,8 @@ export const zh = { pricing: '收费管理', orderPayment: '订单支付', orderHistory: '订单记录', - spaceConfig: '空间配置' + spaceConfig: '空间配置', + ontology: '本体工程', }, knowledgeBase: { home: '首页', @@ -1172,7 +1173,8 @@ export const zh = { inactive: '不活跃', configurationName: '配置名称', emotionEngine: '情感引擎', - reflectionEngine: '反思引擎' + reflectionEngine: '反思引擎', + scene_id: '本体场景', }, member: { username: '用户名', @@ -2449,6 +2451,34 @@ export const zh = { question: '踩过的坑', summary: '核心洞察', none: '无' + }, + ontology: { + searchPlaceholder: '搜索场景', + create: '新增工程', + edit: '编辑工程', + scene_name: '场景名称', + scene_description: '场景描述', + descriptionPlaceholder: '描述该场景的用途和提取的实体类型', + typeCount: '个类型', + created_at: '创建时间', + updated_at: '更新时间', + entityTypes: '实体类型', + + addClass: '添加类型', + class_name: '类型名称', + class_description: '类型定义', + classDescriptionPlaceholder: '描述该类型的含义和用途', + + llm_id: '选择模型', + scenario: '场景描述', + scenarioPlaceholder: '请描述您的业务需求', + run: '推理', + loadingConfirm: '推断中', + extractConfirm: '添加选中类型', + classType: '工程类型', + extract: '工程推理', + source: '未添加项', + target: '已添加项', } }, } \ No newline at end of file diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 7189dc9c..d0891e07 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -3,6 +3,7 @@ import { createHashRouter, createRoutesFromElements, Route } from 'react-router- // 导入路由配置JSON import routesConfig from './routes.json'; +import Ontology from '@/views/Ontology'; // 递归函数,用于生成路由元素 @@ -68,6 +69,8 @@ const componentMap: Record>> = Pricing: lazy(() => import('@/views/Pricing')), ToolManagement: lazy(() => import('@/views/ToolManagement')), SpaceConfig: lazy(() => import('@/views/SpaceConfig')), + Ontology: lazy(() => import('@/views/Ontology')), + OntologyDetail: lazy(() => import('@/views/Ontology/pages/Detail')), Login: lazy(() => import('@/views/Login')), InviteRegister: lazy(() => import('@/views/InviteRegister')), NoPermission: lazy(() => import('@/views/NoPermission')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index 1c317033..6dc6fc5a 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -34,6 +34,7 @@ { "path": "/emotion-engine/:id", "element": "EmotionEngine" }, { "path": "/reflection-engine/:id", "element": "SelfReflectionEngine" }, { "path": "/space-config", "element": "SpaceConfig" }, + { "path": "/ontology", "element": "Ontology" }, { "path": "/no-permission", "element": "NoPermission" }, { "path": "/*", "element": "NotFound" } ] @@ -44,7 +45,8 @@ { "path": "/application/config/:id", "element": "ApplicationConfig" }, { "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }, { "path": "/statement/:id", "element": "StatementDetail" }, - { "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" } + { "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" }, + { "path": "/ontology/:id", "element": "OntologyDetail" } ] }, { diff --git a/web/src/store/menu.json b/web/src/store/menu.json index 62f6c13c..8eae5ce7 100644 --- a/web/src/store/menu.json +++ b/web/src/store/menu.json @@ -332,6 +332,21 @@ } ] }, + { + "id": 21, + "parent": 0, + "code": "ontology", + "label": "本体工程", + "i18nKey": "menu.ontology", + "path": "/ontology", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, { "id": 10, "parent": 0, diff --git a/web/src/views/Ontology/components/OntologyClassExtractModal.tsx b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx new file mode 100644 index 00000000..1aba9c67 --- /dev/null +++ b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx @@ -0,0 +1,173 @@ +import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Form, Input, App, Transfer, type TransferProps, Flex } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { OntologyClassData, ExtractData, OntologyClassExtractModalData, OntologyClassExtractModalRef } from '../types' +import RbModal from '@/components/RbModal' +import { extractOntologyTypes, createOntologyClass } from '@/api/ontology' +import CustomSelect from '@/components/CustomSelect'; +import { getModelListUrl } from '@/api/models' +import RbCard from '@/components/RbCard/Card'; +import Tag from '@/components/Tag'; + +const FormItem = Form.Item; + +interface OntologyClassExtractModalProps { + refresh: () => void; +} + +const OntologyClassExtractModal = forwardRef(({ + refresh +}, ref) => { + const { t } = useTranslation(); + const { message } = App.useApp(); + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false) + const [data, setData] = useState(null) + const [extractData, setExtractData] = useState(null) + const [targetKeys, setTargetKeys] = useState([]); + const [selectedKeys, setSelectedKeys] = useState([]); + + // 封装取消方法,添加关闭弹窗逻辑 + const handleClose = () => { + setVisible(false); + form.resetFields(); + setLoading(false) + setData(null) + setExtractData(null) + }; + + const handleOpen = (vo: OntologyClassData) => { + form.resetFields(); + setVisible(true); + setData(vo) + }; + // 封装保存方法,添加提交逻辑 + const handleSave = () => { + if (!data?.scene_id) return; + form + .validateFields() + .then((values) => { + setLoading(true) + extractOntologyTypes({ + ...values, + scene_id: data.scene_id, + domain: data.scene_name, + }).then((res) => { + const response = res as ExtractData + setExtractData(response) + setSelectedKeys([]) + setTargetKeys(response.classes.map(vo => vo.id)) + }) + .finally(() => { + setLoading(false) + }) + }) + .catch((err) => { + console.log('err', err) + }); + } + + const handleConfirm = () => { + if (!extractData) { + handleSave() + } else { + if (!data?.scene_id) return; + if (!targetKeys || targetKeys.length === 0) { + message.warning(t('common.selectPlaceholder', { title: t('ontology.classType') })) + return + } + console.log('targetKeys', targetKeys) + createOntologyClass({ + scene_id: data?.scene_id, + classes: extractData.classes.filter(vo => targetKeys?.includes(vo.id)).map(vo => ({ class_name: vo.name, class_description: vo.description })) + }).then(() => { + message.success(t('common.createSuccess')) + refresh() + handleClose() + }).finally(() => { + setLoading(false) + }) + } + } + + + const onChange: TransferProps['onChange'] = (nextTargetKeys) => { + setTargetKeys(nextTargetKeys.filter(Boolean)); + }; + + const onSelectChange: TransferProps['onSelectChange'] = ( + sourceSelectedKeys, + targetSelectedKeys, + ) => { + setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys].filter(Boolean)); + }; + + // 暴露给父组件的方法 + useImperativeHandle(ref, () => ({ + handleOpen, + })); + + return ( + +
+ + + + + + + +
+ + {extractData && + ({ ...vo, key: vo.id }))} + targetKeys={targetKeys} + selectedKeys={selectedKeys} + onChange={onChange} + onSelectChange={onSelectChange} + render={(item) => (
+ {item.name} + {item.examples.map((vo, index) => {vo})} +
)} + listStyle={{ width: '400px', height: '100%' }} + /> +
} +
+ ); +}); + +export default OntologyClassExtractModal; \ No newline at end of file diff --git a/web/src/views/Ontology/components/OntologyClassModal.tsx b/web/src/views/Ontology/components/OntologyClassModal.tsx new file mode 100644 index 00000000..73e45060 --- /dev/null +++ b/web/src/views/Ontology/components/OntologyClassModal.tsx @@ -0,0 +1,96 @@ +import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Form, Input, App } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { AddClassItem, OntologyClassModalRef } from '../types' +import RbModal from '@/components/RbModal' +import { createOntologyClass } from '@/api/ontology' + +const FormItem = Form.Item; + +interface OntologyClassModalProps { + refresh: () => void; +} + +const OntologyClassModal = forwardRef(({ + refresh +}, ref) => { + const { t } = useTranslation(); + const { message } = App.useApp(); + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false) + const [scene_id, setSceneId] = useState(null) + + // 封装取消方法,添加关闭弹窗逻辑 + const handleClose = () => { + setVisible(false); + form.resetFields(); + setLoading(false) + }; + + const handleOpen = (scene_id: string) => { + form.resetFields(); + setVisible(true); + setSceneId(scene_id) + }; + // 封装保存方法,添加提交逻辑 + const handleSave = () => { + if (!scene_id) return; + form + .validateFields() + .then((values) => { + setLoading(true) + createOntologyClass({ + scene_id: scene_id, + classes: [{ ...values }] + }).then(() => { + message.success(t('common.saveSuccess')); + handleClose(); + refresh(); + }) + .finally(() => setLoading(false)) + }) + .catch((err) => { + console.log('err', err) + }); + } + + // 暴露给父组件的方法 + useImperativeHandle(ref, () => ({ + handleOpen, + })); + + return ( + +
+ + + + + + + +
+
+ ); +}); + +export default OntologyClassModal; \ No newline at end of file diff --git a/web/src/views/Ontology/components/OntologyModal.tsx b/web/src/views/Ontology/components/OntologyModal.tsx new file mode 100644 index 00000000..82eebbd2 --- /dev/null +++ b/web/src/views/Ontology/components/OntologyModal.tsx @@ -0,0 +1,99 @@ +import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Form, Input, App } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { OntologyItem, OntologyModalData, OntologyModalRef } from '../types' +import RbModal from '@/components/RbModal' +import { createOntologyScene, updateOntologyScene } from '@/api/ontology' + +const FormItem = Form.Item; + +interface OntologyModalProps { + refresh: () => void; +} + +const OntologyModal = forwardRef(({ + refresh +}, ref) => { + const { t } = useTranslation(); + const { message } = App.useApp(); + const [visible, setVisible] = useState(false); + const [editVo, setEditVo] = useState(null) + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false) + + // 封装取消方法,添加关闭弹窗逻辑 + const handleClose = () => { + setVisible(false); + form.resetFields(); + setLoading(false) + setEditVo(null) + }; + + const handleOpen = (vo?: OntologyItem) => { + if (vo) { + setEditVo(vo); + form.setFieldsValue(vo); + } else { + form.resetFields(); + } + setVisible(true); + }; + // 封装保存方法,添加提交逻辑 + const handleSave = () => { + form + .validateFields() + .then((values) => { + setLoading(true) + const request = editVo?.scene_id ? updateOntologyScene(editVo.scene_id, values) : createOntologyScene(values) + request + .then(() => { + message.success(t('common.saveSuccess')); + handleClose(); + refresh(); + }) + .finally(() => setLoading(false)) + }) + .catch((err) => { + console.log('err', err) + }); + } + + // 暴露给父组件的方法 + useImperativeHandle(ref, () => ({ + handleOpen, + })); + + return ( + +
+ + + + + + + +
+
+ ); +}); + +export default OntologyModal; \ No newline at end of file diff --git a/web/src/views/Ontology/components/PageHeader.tsx b/web/src/views/Ontology/components/PageHeader.tsx new file mode 100644 index 00000000..81009596 --- /dev/null +++ b/web/src/views/Ontology/components/PageHeader.tsx @@ -0,0 +1,45 @@ +import { type FC, type ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Layout, Button } from 'antd'; +import { useTranslation } from 'react-i18next'; +import logoutIcon from '@/assets/images/logout_hover.svg' + +const { Header } = Layout; + +interface ConfigHeaderProps { + name?: string; + subTitle?: ReactNode | string; + extra?: ReactNode; +} +const PageHeader: FC = ({ + name, + subTitle, + extra +}) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const goBack = () => { + navigate(-1) + } + return ( +
+
+
+ {name} +
+
{subTitle}
+
+ +
+ + {extra} +
+
+ ); +}; + +export default PageHeader; \ No newline at end of file diff --git a/web/src/views/Ontology/index.tsx b/web/src/views/Ontology/index.tsx new file mode 100644 index 00000000..4d32dfa6 --- /dev/null +++ b/web/src/views/Ontology/index.tsx @@ -0,0 +1,133 @@ +import { type FC, useState, useRef, type MouseEvent } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +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 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' + +const Ontology: FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate() + const { modal, message } = App.useApp(); + const [query, setQuery] = useState({}); + const scrollListRef = useRef(null) + const entityModalRef = useRef(null) + + const handleCreate = () => { + entityModalRef.current?.handleOpen() + } + const handleEdit = (record: OntologyItem, e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + entityModalRef.current?.handleOpen(record) + } + const handleDelete = (item: OntologyItem, e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + modal.confirm({ + title: t('common.confirmDeleteDesc', { name: item.scene_name }), + okText: t('common.delete'), + cancelText: t('common.cancel'), + okType: 'danger', + onOk: () => { + deleteOntologyScene(item.scene_id) + .then(() => { + message.success(t('common.deleteSuccess')) + scrollListRef.current?.refresh() + }) + } + }) + } + const handleJump = (record: OntologyItem) => { + navigate(`/ontology/${record.scene_id}`) + } + + return ( + <> + + + setQuery({ scene_name: value })} + className="rb:w-full!" + /> + + + + + + + + ref={scrollListRef} + url={getOntologyScenesUrl} + query={query} + column={3} + renderItem={(item) =>( + {item.type_num} {t('ontology.typeCount')}} + onClick={() => handleJump(item)} + className="rb:cursor-pointer" + > +
+ {t(`ontology.scene_description`)} + + {item.scene_description} + +
+ {(['created_at', 'updated_at'] as const).map(key => ( +
+ {t(`ontology.${key}`)} + {formatDateTime(item[key])} +
+ ))} + + +
{t('ontology.entityTypes')}:
+ {item.entity_type?.map((type, i) => ( + {type} + ))} + {item.type_num > 3 && ( + +{item.type_num - 3} + )} +
+ +
+ +
handleEdit(item, e)} + >
+
handleDelete(item, e)} + >
+
+
+
+ )} + /> + + scrollListRef.current?.refresh()} + /> + + ) +} + +export default Ontology \ No newline at end of file diff --git a/web/src/views/Ontology/pages/Detail.tsx b/web/src/views/Ontology/pages/Detail.tsx new file mode 100644 index 00000000..b7123c5c --- /dev/null +++ b/web/src/views/Ontology/pages/Detail.tsx @@ -0,0 +1,122 @@ +import { type FC, useEffect, useState, useRef } from 'react' +import { useParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { App, Row, Col, Tooltip, Space, Button } from 'antd' + +import PageHeader from '../components/PageHeader' +import { getOntologyClassList, deleteOntologyClass } from '@/api/ontology' +import type { OntologyClassData, OntologyClassModalRef, OntologyClassExtractModalRef, OntologyClassItem } from '@/views/Ontology/types' +import RbCard from '@/components/RbCard/Card'; +import OntologyClassModal from '../components/OntologyClassModal' +import SearchInput from '@/components/SearchInput'; +import OntologyClassExtractModal from '../components/OntologyClassExtractModal' +import BodyWrapper from '@/components/Empty/BodyWrapper' + +const Detail: FC = () => { + const { t } = useTranslation(); + const { id } = useParams() + const { modal, message } = App.useApp() + const ontologyClassModalRef = useRef(null) + const ontologyClassExtractModalRef = useRef(null) + const [query, setQuery] = useState<{ + class_name?: string; + }>({}); + const [loading, setLoading] = useState(false) + const [data, setData] = useState({} as OntologyClassData) + + useEffect(() => { + getData() + }, [id, query]) + + const getData = () => { + if (!id) return; + setLoading(true) + getOntologyClassList({ + ...query, + scene_id: id + }) + .then(res => { + setData(res as OntologyClassData) + }) + .finally(() => { + setLoading(false) + }) + } + const handleDelete = (item: OntologyClassItem) => { + modal.confirm({ + title: t('common.confirmDeleteDesc', { name: item.class_name }), + okText: t('common.delete'), + cancelText: t('common.cancel'), + okType: 'danger', + onOk: () => { + deleteOntologyClass(item.class_id) + .then(() => { + getData(); + message.success(t('common.deleteSuccess')) + }) + } + }) + } + const handleAdd = () => { + ontologyClassModalRef.current?.handleOpen(data.scene_id) + } + const handleExtract = () => { + ontologyClassExtractModalRef.current?.handleOpen(data) + } + + return ( + <> + {data.scene_description}} + extra={ + + + } + /> + +
+ + + setQuery({ class_name: value })} + className="rb:w-full!" + /> + + + + + {data.items?.map(item => ( + + handleDelete(item)} + >
} + className="rb:bg-transparent!" + > + +
{item.class_description}
+
+ + + ))} + + + + + + + + ) +} + +export default Detail \ No newline at end of file diff --git a/web/src/views/Ontology/types.ts b/web/src/views/Ontology/types.ts new file mode 100644 index 00000000..fc9af2ea --- /dev/null +++ b/web/src/views/Ontology/types.ts @@ -0,0 +1,79 @@ +export interface Query { + pagesize?: number; + page?: number; + scene_name?: string; +} + +export interface OntologyItem { + scene_id: string; + scene_name: string; + scene_description: string; + type_num: number; + entity_type: string[]; + workspace_id: string; + created_at: number; + updated_at: number; + classes_count: number; +} + +export interface OntologyModalData { + scene_name: string; + scene_description: string; +} + +export interface OntologyModalRef { + handleOpen: (data?: OntologyItem) => void; +} + +export interface OntologyClassItem { + class_id: string; + class_name: string; + class_description: string; + scene_id: string; + created_at: number; + updated_at: number; +} +export interface OntologyClassData { + total: number; + scene_id: string; + scene_name: string; + scene_description: string; + items: OntologyClassItem[]; +} + +export interface AddClassItem { + class_name: string; + class_description: string; +} +export interface OntologyClassModalData { + scene_id: string; + classes: AddClassItem[] +} +export interface OntologyClassModalRef { + handleOpen: (scene_id: string) => void; +} +export interface OntologyClassExtractModalData { + llm_id: string; + scene_id: string; + scenario: string; + domain: string; // scene_name +} +export interface OntologyClassExtractModalRef { + handleOpen: (vo: OntologyClassData) => void; +} + +export interface ExtractClassItem { + id: string; + name: string; + name_chinese: string; + description: string; + examples: string[]; + parent_class: string | null; + entity_type: string; + domain: string; +} +export interface ExtractData { + domain: string; + extracted_count: number; + classes: ExtractClassItem[] +}