{['memoryExtractionEngine', 'forgottenEngine', 'emotionEngine', 'reflectionEngine'].map((key) => (
-
handleClick(item.config_id, key)}
>
{t(`memory.${key}`)}
diff --git a/web/src/views/MemoryManagement/types.ts b/web/src/views/MemoryManagement/types.ts
index 55524462..c632d101 100644
--- a/web/src/views/MemoryManagement/types.ts
+++ b/web/src/views/MemoryManagement/types.ts
@@ -3,6 +3,7 @@ export interface MemoryFormData {
config_id?: number;
config_name: string;
config_desc?: string;
+ scene_id?: string;
}
// 内存数据类型
@@ -29,6 +30,8 @@ export interface Memory {
updated_at: string;
config_desc: string;
workspace_id: string;
+ scene_id: string;
+ scene_name: string;
[key: string]: string | number | boolean;
}
// 定义组件暴露的方法接口
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[]
+}