| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
9ce68b8813 | merge | 10 months ago |
|
|
67861012ee | . | 11 months ago |
|
|
5f478f8214 | merge | 11 months ago |
| @@ -0,0 +1 @@ | |||||
| v16.11.0 | |||||
| @@ -20,14 +20,20 @@ export default { | |||||
| // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | // localhost:8000/api/** -> https://preview.pro.ant.design/api/** | ||||
| '/api/': { | '/api/': { | ||||
| // 要代理的地址 | // 要代理的地址 | ||||
| target: 'http://172.20.32.181:31213', // 开发环境 | |||||
| target: 'http://172.20.32.114:8082/', // 开发环境 | |||||
| // target: 'http://172.20.32.197:31213', // 开发环境 | |||||
| // target: 'http://172.20.32.98:8082', | // target: 'http://172.20.32.98:8082', | ||||
| // target: 'http://172.20.32.150:8082', | // target: 'http://172.20.32.150:8082', | ||||
| // 配置了这个可以从 http 代理到 https | // 配置了这个可以从 http 代理到 https | ||||
| // 依赖 origin 的功能可能需要这个,比如 cookie | // 依赖 origin 的功能可能需要这个,比如 cookie | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| // pathRewrite: { '^/api': '' }, | |||||
| pathRewrite: { '^/api': '' }, | |||||
| }, | }, | ||||
| // '/cp/': { | |||||
| // target: 'http://172.20.32.14:9213', // 开发环境 | |||||
| // changeOrigin: true, | |||||
| // pathRewrite: { '^/cp': '' }, | |||||
| // }, | |||||
| '/profile/avatar/': { | '/profile/avatar/': { | ||||
| target: 'http://172.20.32.185:31213', | target: 'http://172.20.32.185:31213', | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| @@ -44,7 +44,7 @@ export default [ | |||||
| { | { | ||||
| name: 'login', | name: 'login', | ||||
| path: '/user/login', | path: '/user/login', | ||||
| component: './User/Login', | |||||
| component: './User/Login/login', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -147,6 +147,42 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '知识图谱', | |||||
| path: '/knowledge', | |||||
| routes: [ | |||||
| { | |||||
| name: '我的图谱', | |||||
| path: 'myGraph', | |||||
| component: './knowledge/MyGraph/index', | |||||
| }, | |||||
| { | |||||
| name: '我的主体', | |||||
| path: 'mySubject', | |||||
| component: './knowledge/MySubject/index', | |||||
| }, | |||||
| { | |||||
| name: '在线版本图谱', | |||||
| path: 'onlineGraph', | |||||
| component: './knowledge/OnlineGraph/index', | |||||
| }, | |||||
| { | |||||
| name: '预览主体', | |||||
| path: 'subjectView/:name/:id', | |||||
| component: './knowledge/SubjectView/index', | |||||
| }, | |||||
| { | |||||
| name: '预览图谱', | |||||
| path: 'graphView', | |||||
| component: './knowledge/GraphView/index.tsx', | |||||
| }, | |||||
| { | |||||
| name: '图谱配置', | |||||
| path: 'graphCounter', | |||||
| component: './knowledge/GraphCounter/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| name: 'AI资产', | name: 'AI资产', | ||||
| path: '/dataset', | path: '/dataset', | ||||
| @@ -0,0 +1,20 @@ | |||||
| import { defineMock } from 'umi'; | |||||
| export default defineMock({ | |||||
| 'GET /api/kg/configuration/list': { | |||||
| code: 200, | |||||
| msg: '操作成功', | |||||
| data: [ | |||||
| { | |||||
| id: '000000001', | |||||
| description:"是个好东西", | |||||
| name: '你收到了 14 份新周报', | |||||
| updateTime: '2017-08-09', | |||||
| effectiveTime: '2017-08-09', | |||||
| type: 'notification', | |||||
| } | |||||
| ], | |||||
| }, | |||||
| }); | |||||
| @@ -0,0 +1,183 @@ | |||||
| .graphCounter-page { | |||||
| :global { | |||||
| .ant-tabs .ant-tabs-tab+.ant-tabs-tab { | |||||
| margin-left: 54px; | |||||
| } | |||||
| } | |||||
| height: 100%; | |||||
| padding: 0 9px; | |||||
| &__container { | |||||
| height: 91px; | |||||
| padding:14px 30px 0 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__box{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| &__update{ | |||||
| padding: 7px 10px; | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border:1px solid rgba(22, 100, 255, 0.11); | |||||
| border-radius:4px; | |||||
| margin-right: 20px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__topCenter{ | |||||
| position: relative; | |||||
| margin: 10px 0 15px 0; | |||||
| width: 100%; | |||||
| height: calc(100% - 100px); | |||||
| background:#ffffff; | |||||
| border-radius:10px; | |||||
| box-shadow:0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| &__title{ | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| margin: 30px 0 15px 25px; | |||||
| font-weight: 600; | |||||
| } | |||||
| &__listBox{ | |||||
| padding: 0 25px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__itemBox{ | |||||
| position: relative; | |||||
| width: calc(20% - 15px); | |||||
| height:100px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.12); | |||||
| margin-right: 15px; | |||||
| &__img{ | |||||
| position: absolute; | |||||
| left: 30px; | |||||
| top: 26px; | |||||
| width: 48px; | |||||
| height: 48px; | |||||
| } | |||||
| &__wordTitle{ | |||||
| position: absolute; | |||||
| top: 26px; | |||||
| left: 102px; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:18px; | |||||
| } | |||||
| &__wordContent{ | |||||
| position: absolute; | |||||
| top: 54px; | |||||
| left: 102px; | |||||
| color:#565658; | |||||
| font-size:15px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__bottomBox{ | |||||
| padding: 23px 35px 23px 25px; | |||||
| height: 100%; | |||||
| &__leftText{ | |||||
| float: left; | |||||
| display: flex; | |||||
| align-items: start; | |||||
| margin-bottom: 25px; | |||||
| &__text{ | |||||
| width:1430px; | |||||
| min-height:70px; | |||||
| padding: 25px; | |||||
| background:rgba(96, 107, 122, 0.06); | |||||
| border:1px solid; | |||||
| border-color:rgba(96, 107, 122, 0.2); | |||||
| border-radius:0px 10px 10px 10px; | |||||
| color:rgba(29, 29, 32, 0.75); | |||||
| font-size:15px; | |||||
| } | |||||
| } | |||||
| &__rightText{ | |||||
| float: right; | |||||
| display: flex; | |||||
| align-items: start; | |||||
| margin-bottom: 25px; | |||||
| &__text{ | |||||
| width:791px; | |||||
| min-height:70px; | |||||
| padding: 25px; | |||||
| background:#1664ff; | |||||
| border:1px solid; | |||||
| border-color:rgba(22, 100, 255, 0.3); | |||||
| border-radius:10px 0px 10px 10px; | |||||
| color:#fff; | |||||
| font-size:15px; | |||||
| } | |||||
| } | |||||
| &__titleBox{ | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 39px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__word{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| margin-left: 14px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| padding: 2px 20px 2px 16px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| font-weight: normal; | |||||
| &__circle{ | |||||
| position: relative; | |||||
| width:15px; | |||||
| height:15px; | |||||
| border-radius: 50%; | |||||
| background:#ffffff; | |||||
| box-shadow:0px 1px 3px rgba(0, 82, 217, 0.09); | |||||
| margin-right: 13px; | |||||
| &__inner{ | |||||
| position: absolute; | |||||
| left: 50%; | |||||
| top: 50%; | |||||
| transform: translate(-50%,-50%); | |||||
| width:9px; | |||||
| height:9px; | |||||
| border-radius: 50%; | |||||
| background:#1664ff; | |||||
| box-shadow:0px 1px 3px rgba(22, 100, 255, 0.27); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| // width:647px; | |||||
| height:39px; | |||||
| background:rgba(138, 138, 138, 0.05); | |||||
| border-radius:4px; | |||||
| padding-left: 30px; | |||||
| } | |||||
| } | |||||
| &__queryparams{ | |||||
| margin: 17px 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,413 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { getConfigurationList } from '@/services/graph'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useSearchParams } from '@umijs/max'; | |||||
| import { Button, Input, Table, type TablePaginationConfig, type TableProps, Tabs } from 'antd'; | |||||
| // import getConfigurationList from 'mock/knowledge'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; | |||||
| code_repo_name: string; | |||||
| code_repo_vis: number; | |||||
| git_url: string; | |||||
| git_branch: string; | |||||
| git_user_name: string; | |||||
| git_password: string; | |||||
| ssh_key: string; | |||||
| verify_mode: number; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| }; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| status: string; | |||||
| computing_resource: string; | |||||
| update_by: string; | |||||
| create_time: string; | |||||
| url: string; | |||||
| }; | |||||
| function DatasetAnnotation() { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const tabActive = searchParams.get('tabActive') || ''; | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const navigate = useNavigate(); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const [total, setTotal] = useState(0); | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| setActiveTab(tabActive); | |||||
| }, []); | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: `元素链接配置`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: `问答模板配置`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: `问答体验`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: null, | |||||
| }, | |||||
| ]; | |||||
| const createCodeConfig = () => { | |||||
| const { close } = openAntdModal(AddCodeConfigModal, { | |||||
| opType: OperationType.Create, | |||||
| onOk: () => { | |||||
| // getDataList(); | |||||
| // close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const handleSubjectDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '确定要删除XXXX吗?', | |||||
| content: '删除后本体将不可恢复,是否确认删除?', | |||||
| onOk: () => {}, | |||||
| }); | |||||
| }; | |||||
| const getList = () => { | |||||
| let params = {}; | |||||
| getConfigurationList(params).then((ret) => { | |||||
| setTableData(ret.data); | |||||
| }); | |||||
| }; | |||||
| const columnsNow: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '元素链接配置名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '元素链接配置描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'updateTime', | |||||
| key: 'updateTime', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '生效时间', | |||||
| dataIndex: 'effectiveTime', | |||||
| key: 'effectiveTime', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| <Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}> | |||||
| 查看 | |||||
| </Button> | |||||
| <Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}> | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}> | |||||
| 删除 | |||||
| </Button> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| const columnsFu: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '运行编号', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '30%', | |||||
| }, | |||||
| { | |||||
| title: '事件类型', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: '10%', | |||||
| }, | |||||
| { | |||||
| title: '本体', | |||||
| dataIndex: 'computing_resource', | |||||
| key: 'computing_resource', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'update_by', | |||||
| key: 'update_by', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '开始时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '结束时间', | |||||
| dataIndex: 'create_time', | |||||
| key: 'create_time', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| // render: (_: any, record: EditorData) => ( | |||||
| // <div> | |||||
| // {record.status === DevEditorStatus.Pending || | |||||
| // record.status === DevEditorStatus.Running ? ( | |||||
| // <Button | |||||
| // type="link" | |||||
| // size="small" | |||||
| // key="stop" | |||||
| // icon={<KFIcon type="icon-tingzhi" />} | |||||
| // onClick={() => stopEditor(record.id)} | |||||
| // > | |||||
| // 停止 | |||||
| // </Button> | |||||
| // ) : ( | |||||
| // <Button | |||||
| // type="link" | |||||
| // size="small" | |||||
| // key="debug" | |||||
| // icon={<KFIcon type="icon-tiaoshi" />} | |||||
| // onClick={() => startEditor(record.id)} | |||||
| // > | |||||
| // 启动 | |||||
| // </Button> | |||||
| // )} | |||||
| // {record.status === DevEditorStatus.Running ? ( | |||||
| // <Button | |||||
| // type="link" | |||||
| // size="small" | |||||
| // key="jingxiang" | |||||
| // icon={<KFIcon type="icon-jingxiang" />} | |||||
| // onClick={() => createMirror(record.id)} | |||||
| // > | |||||
| // 制作镜像 | |||||
| // </Button> | |||||
| // ) : null} | |||||
| // <ConfigProvider | |||||
| // theme={{ | |||||
| // token: { | |||||
| // colorLink: themes['warningColor'], | |||||
| // }, | |||||
| // }} | |||||
| // > | |||||
| // <Button | |||||
| // type="link" | |||||
| // size="small" | |||||
| // key="remove" | |||||
| // icon={<KFIcon type="icon-shanchu" />} | |||||
| // onClick={() => handleEditorDelete(record)} | |||||
| // > | |||||
| // 删除 | |||||
| // </Button> | |||||
| // </ConfigProvider> | |||||
| // </div> | |||||
| // ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['graphCounter-page']}> | |||||
| <div className={styles['graphCounter-page__container']}> | |||||
| <span>552815033</span> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| </div> | |||||
| <div className={styles['graphCounter-page__topCenter']}> | |||||
| <div className={styles['graphCounter-page__bottomBox']}> | |||||
| {activeTab == '1' && ( | |||||
| <div> | |||||
| <div className={styles['graphCounter-page__bottomBox__queryparams']}> | |||||
| <Input.Search | |||||
| placeholder="按镜像名称筛选" | |||||
| allowClear | |||||
| // onSearch={onSearch} | |||||
| // onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| // value={inputText} | |||||
| /> | |||||
| <Button | |||||
| key="info" | |||||
| type="primary" | |||||
| style={{ marginLeft: '20px' }} | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 查询 | |||||
| </Button> | |||||
| <Button | |||||
| key="info" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={() => handleSubjectDelete()} | |||||
| > | |||||
| 批量删除 | |||||
| </Button> | |||||
| </div> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columnsNow} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| )} | |||||
| {activeTab == '2' && ( | |||||
| <div> | |||||
| <div className={styles['graphCounter-page__bottomBox__queryparams']}> | |||||
| <Input.Search | |||||
| placeholder="按镜像名称筛选" | |||||
| allowClear | |||||
| // onSearch={onSearch} | |||||
| // onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| // value={inputText} | |||||
| /> | |||||
| <Button | |||||
| key="info" | |||||
| type="primary" | |||||
| style={{ marginLeft: '20px' }} | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 查询 | |||||
| </Button> | |||||
| <Button | |||||
| key="info" | |||||
| style={{ marginLeft: '20px' }} | |||||
| onClick={() => handleSubjectDelete()} | |||||
| > | |||||
| 批量删除 | |||||
| </Button> | |||||
| </div> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columnsNow} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| )} | |||||
| {activeTab == '3' && ( | |||||
| <div style={{ height: '100%' }}> | |||||
| <div className={styles['graphCounter-page__bottomBox__leftText']}> | |||||
| <img | |||||
| style={{ width: '40px', marginRight: '20px' }} | |||||
| src={require('@/assets/img/computer-img.png')} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| <div className={styles['graphCounter-page__bottomBox__leftText__text']}> | |||||
| Heyow too! You sound cute! How can I help you today? Oh, you're curious about my | |||||
| smarts, huh? Well, let me assure you, I'm super up-to-date! I update every minute | |||||
| and second, so I've got the latest scoop at my digital fingertips! | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['graphCounter-page__bottomBox__rightText']}> | |||||
| <div className={styles['graphCounter-page__bottomBox__rightText__text']}> | |||||
| Heyow too! You sound cute! How can I help you today? Oh, you're curious about my | |||||
| smarts, huh? Well, let me assure you, I'm super up-to-date! I update every minute | |||||
| and second, so I've got the latest scoop at my digital fingertips! | |||||
| </div> | |||||
| <img | |||||
| style={{ width: '40px', marginLeft: '20px' }} | |||||
| src={require('@/assets/img/user-answer.png')} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['graphCounter-page__bottomBox__leftText']}> | |||||
| <img | |||||
| style={{ width: '40px', marginRight: '20px' }} | |||||
| src={require('@/assets/img/computer-img.png')} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| <div className={styles['graphCounter-page__bottomBox__leftText__text']}> | |||||
| Heyow too! You sound cute! How can I help you today? Oh, you're curious about my | |||||
| smarts, huh? Well, let me assure you, I'm super up-to-date! I update every minute | |||||
| and second, so I've got the latest scoop at my digital fingertips! | |||||
| </div> | |||||
| </div> | |||||
| <Input.TextArea | |||||
| style={{ | |||||
| position: 'absolute', | |||||
| bottom: '30px', | |||||
| left: '40px', | |||||
| width: 'calc(100% - 80px)', | |||||
| }} | |||||
| placeholder="请输入内容" | |||||
| autoSize={{ minRows: 6, maxRows: 20 }} | |||||
| allowClear | |||||
| /> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default DatasetAnnotation; | |||||
| @@ -0,0 +1,69 @@ | |||||
| .subjectView-container { | |||||
| display: flex; | |||||
| height: 100%; | |||||
| background-color: #fff; | |||||
| &__workflow { | |||||
| flex: 1 1 0; | |||||
| min-width: 0; | |||||
| height: 100%; | |||||
| &__top { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: end; | |||||
| width: 100%; | |||||
| height: 52px; | |||||
| padding: 0 20px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__circle{ | |||||
| width:8px; | |||||
| height:8px; | |||||
| border-radius: 50%; | |||||
| margin-right: 10px; | |||||
| } | |||||
| } | |||||
| &__graph { | |||||
| width: 100%; | |||||
| height: calc(100% - 52px); | |||||
| background-color: @background-color; | |||||
| background-image: url(@/assets/img/pipeline-canvas-bg.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| } | |||||
| } | |||||
| :global { | |||||
| .pipeline-context-menu { | |||||
| width: 78px; | |||||
| padding: 10px 0; | |||||
| background: #ffffff; | |||||
| border-radius: 6px; | |||||
| box-shadow: 0px 0px 6px rgba(40, 84, 168, 0.21); | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 34px; | |||||
| padding-left: 12px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 15px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| color: #0d5ef8; | |||||
| font-weight: 500; | |||||
| background-color: .addAlpha(#8895a8, 0.11) []; | |||||
| } | |||||
| &__icon { | |||||
| width: 1em; | |||||
| height: 1em; | |||||
| margin-right: 9px; | |||||
| fill: currentColor; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,293 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteSubject, getKnowledgeSubject } from '@/services/subject'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { App, Button, Input, Table, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import AddSubjectConfigModal, { OperationType } from '../components/AddSubjectConfigModal'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; | |||||
| code_repo_name: string; | |||||
| code_repo_vis: number; | |||||
| git_url: string; | |||||
| git_branch: string; | |||||
| git_user_name: string; | |||||
| git_password: string; | |||||
| ssh_key: string; | |||||
| verify_mode: number; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| }; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| updateTime: string; | |||||
| conceptsCount: string; | |||||
| update_by: string; | |||||
| relationsCount: string; | |||||
| description: string; | |||||
| }; | |||||
| function GraphView() { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const navigate = useNavigate(); | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const { message } = App.useApp(); | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const [total, setTotal] = useState(0); | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| }, []); | |||||
| const items = [ | |||||
| { | |||||
| key: 1, | |||||
| label: `图谱版本`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: 2, | |||||
| label: `更新日志`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: null, | |||||
| }, | |||||
| ]; | |||||
| const createCodeConfig = () => { | |||||
| const { close } = openAntdModal(AddSubjectConfigModal, { | |||||
| opType: OperationType.Create, | |||||
| onOk: () => { | |||||
| getList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const getList = () => { | |||||
| const params: Record<string, any> = { | |||||
| pageNum: pagination.current, | |||||
| pageSize: pagination.pageSize, | |||||
| name: inputText, | |||||
| }; | |||||
| getKnowledgeSubject(params).then((ret) => { | |||||
| console.log(ret.rows); | |||||
| setTableData(ret.rows); | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const goSubjectEdit = (row) => { | |||||
| console.log(row); | |||||
| navigate(`/knowledge/subjectView/${row.name}/${row.id}`); | |||||
| }; | |||||
| const handleSubjectDelete = (row) => { | |||||
| modalConfirm({ | |||||
| title: '确定要删除该主体吗?', | |||||
| content: '删除后主体将不可恢复,是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteSubject(row.id).then((res) => { | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getList(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const columnsNow: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '主体名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '20%', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'updateTime', | |||||
| key: 'updateTime', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '概念个数', | |||||
| dataIndex: 'conceptsCount', | |||||
| key: 'conceptsCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '关系个数', | |||||
| dataIndex: 'relationsCount', | |||||
| key: 'relationsCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '主体描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '30%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| onClick={goSubjectEdit} | |||||
| icon={<KFIcon type="icon-chakanxiangqing" />} | |||||
| > | |||||
| 查看 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => goSubjectEdit(record)} | |||||
| > | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button type="link" size="small" key="run" icon={<KFIcon type="icon-bendishangchuan" />}> | |||||
| 导出 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleSubjectDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['mySubject-page']}> | |||||
| <div className={styles['mySubject-page__container']}> | |||||
| 主体管理 | |||||
| <div className={styles['mySubject-page__container__box']}> | |||||
| <img | |||||
| className={styles['mySubject-page__container__box__img']} | |||||
| src={require('@/assets/img/icon-graph.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span> | |||||
| 您还可以创建<span style={{ fontSize: '16px', color: '#1664ff' }}>47</span>个框架{' '} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['mySubject-page__topCenter']}> | |||||
| <div className={styles['mySubject-page__bottomBox']}> | |||||
| <div className={styles['mySubject-page__bottomBox__queryparams']}> | |||||
| <div className={styles['mySubject-page__bottomBox__queryparams__leftbox']}> | |||||
| <Input.Search | |||||
| placeholder="按名称筛选" | |||||
| allowClear | |||||
| onSearch={getList} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| value={inputText} | |||||
| /> | |||||
| {/* <Button | |||||
| type="primary" | |||||
| key="primary" | |||||
| style={{ margin: '0 20px' }} | |||||
| onClick={() => getList()} | |||||
| > | |||||
| 查询 | |||||
| </Button> */} | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| key="info" | |||||
| onClick={() => handleSubjectDelete()} | |||||
| > | |||||
| 批量删除 | |||||
| </Button> | |||||
| </div> | |||||
| <div> | |||||
| <Button | |||||
| onClick={() => createCodeConfig()} | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 创建主体 | |||||
| </Button> | |||||
| <Button key="info" style={{ margin: '0 20px' }}> | |||||
| obs导入 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columnsNow} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default GraphView; | |||||
| @@ -0,0 +1,94 @@ | |||||
| import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { Graph, INode } from '@antv/g6'; | |||||
| import { type MenuProps } from 'antd'; | |||||
| // 找到节点所以的上游节点 | |||||
| export const findAllParentNodes = (graph: Graph, node: INode) => { | |||||
| const parentNodes: INode[] = []; | |||||
| let index = -1; | |||||
| let targetNode = node; | |||||
| while (targetNode) { | |||||
| const neighbors: INode[] = graph.getNeighbors(targetNode, 'source'); | |||||
| for (const sourceNode of neighbors) { | |||||
| // 避免重复,也避免循环 | |||||
| const idx = parentNodes.findIndex((item) => sourceNode.getID() === item.getID()); | |||||
| if (idx === -1 && sourceNode.getID() !== node.getID()) { | |||||
| parentNodes.push(sourceNode); | |||||
| } | |||||
| } | |||||
| targetNode = parentNodes[++index]; | |||||
| } | |||||
| return parentNodes; | |||||
| }; | |||||
| // 判断并找到全局参数第一个重复项,有重复项时,全局参数不允许保存 | |||||
| export function findFirstDuplicate(params: PipelineGlobalParam[]): string | null { | |||||
| const seen = new Set(); | |||||
| for (const item of params) { | |||||
| if (seen.has(item.param_name)) { | |||||
| return item.param_name; | |||||
| } | |||||
| seen.add(item.param_name); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 创建参数下拉菜单 | |||||
| export function createMenuItems( | |||||
| params: PipelineGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| ): MenuProps['items'] { | |||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | |||||
| const model = item.getModel(); | |||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | |||||
| return { | |||||
| key: model.id as string, | |||||
| label: model.label as string, | |||||
| children: outParametersList.map((key: string) => ({ | |||||
| key: key as string, | |||||
| label: out_parametersObj[key].label, | |||||
| })), | |||||
| }; | |||||
| }); | |||||
| if (params.length > 0) { | |||||
| return [ | |||||
| { | |||||
| key: 'global', | |||||
| label: '全局参数', | |||||
| children: params.map((item) => ({ | |||||
| key: item.param_name, | |||||
| label: item.param_name, | |||||
| })), | |||||
| }, | |||||
| ...nodes, | |||||
| ]; | |||||
| } else { | |||||
| return [...nodes]; | |||||
| } | |||||
| } | |||||
| export function getInParameterComponent( | |||||
| parameter: PipelineNodeModelParameter, | |||||
| ): React.ReactNode | null { | |||||
| if (parameter.value) { | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 判断是否允许输入 | |||||
| export function canInput(parameter: PipelineNodeModelParameter) { | |||||
| const { type, item_type } = parameter; | |||||
| return !( | |||||
| type === 'ref' && | |||||
| (item_type === 'dataset' || | |||||
| item_type === 'model' || | |||||
| item_type === 'image' || | |||||
| item_type === 'code') | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| .collapse { | |||||
| flex: none; | |||||
| width: 250px; | |||||
| height: 100%; | |||||
| :global { | |||||
| .ant-collapse { | |||||
| height: calc(100% - 60px); | |||||
| overflow-y: auto; | |||||
| background-color: #fff; | |||||
| border-color: transparent !important; | |||||
| } | |||||
| .ant-collapse > .ant-collapse-item > .ant-collapse-header { | |||||
| margin-bottom: 5px; | |||||
| padding: 20px 16px 15px 16px; | |||||
| background-color: #fff; | |||||
| border-color: transparent; | |||||
| } | |||||
| .ant-collapse > .ant-collapse-item { | |||||
| margin: 0 10px; | |||||
| border-bottom: 0.5px dashed rgba(20, 49, 179, 0.12); | |||||
| border-radius: 0px; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content { | |||||
| padding-bottom: 15px; | |||||
| border-top: 1px solid transparent; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content > .ant-collapse-content-box { | |||||
| padding: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .collapseItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 40px; | |||||
| padding: 0 16px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| color: @primary-color; | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| } | |||||
| } | |||||
| .modelMenusTitle { | |||||
| margin-bottom: 10px; | |||||
| padding: 12px 25px; | |||||
| color: #111111; | |||||
| font-size: 16px; | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { PipelineNodeModel } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Collapse, type CollapseProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Styles from './index.less'; | |||||
| type ModelMenuData = { | |||||
| key: string; | |||||
| name: string; | |||||
| value: PipelineNodeModel[]; | |||||
| }; | |||||
| type ModelMenuProps = { | |||||
| onComponentDragEnd: ( | |||||
| data: PipelineNodeModel & { x: number; y: number; label: string; img: string }, | |||||
| ) => void; | |||||
| }; | |||||
| const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | |||||
| const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]); | |||||
| const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]); | |||||
| useEffect(() => { | |||||
| getAllComponents(); | |||||
| }, []); | |||||
| // 获取所有组件 | |||||
| const getAllComponents = async () => { | |||||
| const [res] = await to(getComponentAll()); | |||||
| if (res && res.data) { | |||||
| const menus = res.data as ModelMenuData[]; | |||||
| setModelMenusList(menus); | |||||
| const items = menus.map((item) => { | |||||
| return { | |||||
| key: item.key, | |||||
| label: item.name, | |||||
| children: item.value.map((ele) => { | |||||
| return ( | |||||
| <div | |||||
| key={ele.id} | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | |||||
| </div> | |||||
| ); | |||||
| }), | |||||
| }; | |||||
| }); | |||||
| setCollapseItems(items); | |||||
| } | |||||
| }; | |||||
| const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => { | |||||
| onComponentDragEnd({ | |||||
| ...data, | |||||
| x: e.clientX, | |||||
| y: e.clientY, | |||||
| label: data.component_label, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | |||||
| }; | |||||
| const defaultActiveKey = modelMenusList.map((item) => item.key + ''); | |||||
| return ( | |||||
| <div className={Styles.collapse}> | |||||
| <div className={Styles.modelMenusTitle}>组件库</div> | |||||
| {/* 这样 defaultActiveKey 才能生效 */} | |||||
| {modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| expandIconPosition="end" | |||||
| defaultActiveKey={defaultActiveKey} | |||||
| items={collapseItems} | |||||
| ></Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenu; | |||||
| @@ -0,0 +1,115 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { Button, Typography } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; //主键 | |||||
| name: string; //名称 | |||||
| spec: string; //图谱规格 | |||||
| createTime: string; //创建时间 | |||||
| relationCount: number; //关系数 | |||||
| entityCount: number; //实体数 | |||||
| }; | |||||
| type CodeConfigItemProps = { | |||||
| item: CodeConfigData; | |||||
| onClick?: (item: CodeConfigData) => void; | |||||
| onEdit?: (item: CodeConfigData) => void; | |||||
| onRemove?: (item: CodeConfigData) => void; | |||||
| }; | |||||
| function graphItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps) { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>([]); | |||||
| const navigate = useNavigate(); | |||||
| useEffect(() => {}, []); | |||||
| return ( | |||||
| <div | |||||
| className={styles['myGraph-page__centercontent__box']} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onClick?.(item); | |||||
| }} | |||||
| > | |||||
| <div className={styles['myGraph-page__centercontent__box__top']}> | |||||
| <div className={styles['myGraph-page__centercontent__box__top__left']}> | |||||
| <img | |||||
| className={styles['myGraph-page__centercontent__box__top__left__img']} | |||||
| src={require('@/assets/img/graph-star.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <Typography.Paragraph | |||||
| className={styles['myGraph-page__centercontent__box__top__left__name']} | |||||
| ellipsis={{ tooltip: item.name }} | |||||
| > | |||||
| {item.name} | |||||
| </Typography.Paragraph> | |||||
| </div> | |||||
| <div className={styles['myGraph-page__centercontent__box__top__right']}> | |||||
| <Button | |||||
| type="text" | |||||
| shape="circle" | |||||
| style={{ marginLeft: 'auto', marginRight: '4px' }} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onEdit?.(item); | |||||
| }} | |||||
| > | |||||
| <KFIcon type="icon-bianji" font={17} /> | |||||
| </Button> | |||||
| <Button | |||||
| type="text" | |||||
| shape="circle" | |||||
| style={{ marginRight: '-4px' }} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onRemove?.(item); | |||||
| }} | |||||
| > | |||||
| <KFIcon type="icon-shanchu" font={17} /> | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['myGraph-page__centercontent__box__center']}> | |||||
| <span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}>图谱规格</span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}> | |||||
| {item.spec} | |||||
| </span> | |||||
| </span> | |||||
| <span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}> | |||||
| 实体/关系 | |||||
| </span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}> | |||||
| {item.entityCount / item.relationCount} | |||||
| </span> | |||||
| </span> | |||||
| <span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}>创建时间</span> | |||||
| <span className={styles['myGraph-page__centercontent__box__center__word']}> | |||||
| {formatDate(item.createTime)} | |||||
| </span> | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles['myGraph-page__centercontent__box__bottom']}> | |||||
| <div className={styles['myGraph-page__centercontent__box__bottom__left']}> | |||||
| <img | |||||
| className={styles['myGraph-page__centercontent__box__bottom__left__img']} | |||||
| src={require('@/assets/img/graph-correct.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span>可用</span> | |||||
| </div> | |||||
| <span> | |||||
| 试用期剩余<span style={{ color: '#c73131' }}>27</span>天 | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default graphItem; | |||||
| @@ -0,0 +1,181 @@ | |||||
| .myGraph-page { | |||||
| height: 100%; | |||||
| &__container { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 50px; | |||||
| padding-left: 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__box{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| margin-left: 5px; | |||||
| &__img{ | |||||
| width: 24px; | |||||
| height: 24px; | |||||
| margin: 0 5px 0 9px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__centercontent{ | |||||
| margin-top: 10px; | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| align-items: center; | |||||
| &__box{ | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: calc(25% - 15px); | |||||
| height:229px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border:2px solid; | |||||
| border-color:#ffffff; | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| margin: 20px 0 0 20px; | |||||
| padding: 20px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| &__top{ | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 22px; | |||||
| color:#1664ff; | |||||
| font-size:16px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | |||||
| } | |||||
| &__name{ | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| margin-bottom: 0 !important; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 20px; | |||||
| height: 20px; | |||||
| margin-left: 15px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__center{ | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width:100%; | |||||
| height:114px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border-radius:4px; | |||||
| padding: 15px; | |||||
| &__word{ | |||||
| color:#565658; | |||||
| font-size:14px; | |||||
| margin-right: 15px; | |||||
| } | |||||
| } | |||||
| &__bottom{ | |||||
| width: 100%; | |||||
| height: 18px; | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| color:#808080; | |||||
| font-size:13px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 6px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__addBox{ | |||||
| width:350px; | |||||
| height:189px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border:1px dashed; | |||||
| border-color:rgba(22, 100, 255, 0.5); | |||||
| border-radius:4px; | |||||
| margin: 20px 0 0 40px; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| cursor: pointer; | |||||
| &__button{ | |||||
| position: relative; | |||||
| width:53px; | |||||
| height:53px; | |||||
| background:rgba(22, 100, 255, 0.05); | |||||
| border:1px dotted; | |||||
| border-color:#1664ff; | |||||
| border-radius: 50%; | |||||
| &__horizontal{ | |||||
| width: 13px; | |||||
| height: 0; | |||||
| border-top: 2px solid #1664ff; | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 50%; | |||||
| transform: translate(-6.5px, -1px) | |||||
| } | |||||
| &__vertical{ | |||||
| width: 0; | |||||
| height: 13px; | |||||
| border-left: 2px solid #1664ff; | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 50%; | |||||
| transform: translate(-1px,-6.5px) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,181 @@ | |||||
| .myGraph-page { | |||||
| height: 100%; | |||||
| &__container { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 50px; | |||||
| padding-left: 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__box{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| margin-left: 5px; | |||||
| &__img{ | |||||
| width: 24px; | |||||
| height: 24px; | |||||
| margin: 0 5px 0 9px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__centercontent{ | |||||
| margin-top: 10px; | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| align-items: center; | |||||
| &__box{ | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: calc(25% - 15px); | |||||
| height:229px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border:2px solid; | |||||
| border-color:#ffffff; | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| margin: 20px 0 0 20px; | |||||
| padding: 20px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| &__top{ | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 22px; | |||||
| color:#1664ff; | |||||
| font-size:16px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | |||||
| } | |||||
| &__name{ | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| margin-bottom: 0 !important; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 20px; | |||||
| height: 20px; | |||||
| margin-left: 15px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__center{ | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| width:100%; | |||||
| height:114px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border-radius:4px; | |||||
| padding: 15px; | |||||
| &__word{ | |||||
| color:#565658; | |||||
| font-size:14px; | |||||
| margin-right: 15px; | |||||
| } | |||||
| } | |||||
| &__bottom{ | |||||
| width: 100%; | |||||
| height: 18px; | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| color:#808080; | |||||
| font-size:13px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 6px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__addBox{ | |||||
| width:350px; | |||||
| height:189px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border:1px dashed; | |||||
| border-color:rgba(22, 100, 255, 0.5); | |||||
| border-radius:4px; | |||||
| margin: 20px 0 0 40px; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| cursor: pointer; | |||||
| &__button{ | |||||
| position: relative; | |||||
| width:53px; | |||||
| height:53px; | |||||
| background:rgba(22, 100, 255, 0.05); | |||||
| border:1px dotted; | |||||
| border-color:#1664ff; | |||||
| border-radius: 50%; | |||||
| &__horizontal{ | |||||
| width: 13px; | |||||
| height: 0; | |||||
| border-top: 2px solid #1664ff; | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 50%; | |||||
| transform: translate(-6.5px, -1px) | |||||
| } | |||||
| &__vertical{ | |||||
| width: 0; | |||||
| height: 13px; | |||||
| border-left: 2px solid #1664ff; | |||||
| position: absolute; | |||||
| top: 50%; | |||||
| left: 50%; | |||||
| transform: translate(-1px,-6.5px) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,124 @@ | |||||
| import { deleteKnowledgeGraph, getKnowledgeGraph } from '@/services/graph'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | |||||
| import GraphItem from './components/graphItem'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; //主键 | |||||
| name: string; //名称 | |||||
| status: number; //状态,0代表不可用,1代表可用 | |||||
| spec: string; //图谱规格 | |||||
| createTime: string; //创建时间 | |||||
| relationCount: number; //关系数 | |||||
| entityCount: number; //实体数 | |||||
| }; | |||||
| // type CodeConfigItemProps = { | |||||
| // item: CodeConfigData; | |||||
| // onClick?: (item: CodeConfigData) => void; | |||||
| // onEdit?: (item: CodeConfigData) => void; | |||||
| // onRemove?: (item: CodeConfigData) => void; | |||||
| // }; | |||||
| function DatasetAnnotation() { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>([]); | |||||
| const navigate = useNavigate(); | |||||
| const createCodeConfig = () => { | |||||
| const { close } = openAntdModal(AddCodeConfigModal, { | |||||
| opType: OperationType.Create, | |||||
| onOk: () => { | |||||
| getList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 删除 | |||||
| const handleRemove = (record: CodeConfigData) => { | |||||
| console.log(record); | |||||
| modalConfirm({ | |||||
| title: '确定删除这个图谱?', | |||||
| onOk: () => { | |||||
| deleteKnowledgeGraph(record.id); | |||||
| getList(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 修改 | |||||
| const handleEdit = (record: CodeConfigData) => { | |||||
| const { close } = openAntdModal(AddCodeConfigModal, { | |||||
| opType: OperationType.Update, | |||||
| codeConfigData: record, | |||||
| onOk: () => { | |||||
| getList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 查看 | |||||
| // const handleClick = (record: CodeConfigData) => { | |||||
| // const { git_url, git_branch } = record; | |||||
| // const url = getGitUrl(git_url, git_branch); | |||||
| // window.open(url, '_blank'); | |||||
| // }; | |||||
| const getList = () => { | |||||
| getKnowledgeGraph().then((ret) => { | |||||
| console.log(ret.rows); | |||||
| setDataList(ret.rows); | |||||
| }); | |||||
| }; | |||||
| const clickGraph = (record: any) => { | |||||
| navigate(`/knowledge/onlineGraph?id=${record.id}&version=${record.version}`); | |||||
| // navigate({ pathname: `/knowledge/onlineGraph` }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| }, []); | |||||
| return ( | |||||
| <div className={styles['myGraph-page']}> | |||||
| <div className={styles['myGraph-page__container']}> | |||||
| 图谱管理 | |||||
| <div className={styles['myGraph-page__container__box']}> | |||||
| <img | |||||
| className={styles['myGraph-page__container__box__img']} | |||||
| src={require('@/assets/img/icon-graph.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span> | |||||
| 您还可以创建<span style={{ fontSize: '16px', color: '#1664ff' }}>4</span>个图谱{' '} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['myGraph-page__centercontent']}> | |||||
| {dataList && dataList.length != 0 && ( | |||||
| <> | |||||
| {dataList.map((item) => ( | |||||
| <GraphItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| onRemove={handleRemove} | |||||
| onEdit={handleEdit} | |||||
| onClick={clickGraph} | |||||
| ></GraphItem> | |||||
| ))} | |||||
| </> | |||||
| )} | |||||
| <div className={styles['myGraph-page__centercontent__addBox']} onClick={createCodeConfig}> | |||||
| <div className={styles['myGraph-page__centercontent__addBox__button']}> | |||||
| <div | |||||
| className={styles['myGraph-page__centercontent__addBox__button__horizontal']} | |||||
| ></div> | |||||
| <div className={styles['myGraph-page__centercontent__addBox__button__vertical']}></div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default DatasetAnnotation; | |||||
| @@ -0,0 +1,192 @@ | |||||
| .mySubject-page { | |||||
| height: 100%; | |||||
| padding: 0 9px; | |||||
| &__container { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 50px; | |||||
| padding:0 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__box{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| margin-left: 5px; | |||||
| &__img{ | |||||
| width: 24px; | |||||
| height: 24px; | |||||
| margin: 0 5px 0 9px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__topCenter{ | |||||
| margin: 10px 0 15px 0; | |||||
| width: 100%; | |||||
| background:#ffffff; | |||||
| border-radius:10px; | |||||
| box-shadow:0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| &__title{ | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| margin: 30px 0 15px 25px; | |||||
| font-weight: 600; | |||||
| } | |||||
| &__listBox{ | |||||
| padding: 0 25px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__itemBox{ | |||||
| position: relative; | |||||
| width: calc(20% - 15px); | |||||
| height:100px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.12); | |||||
| margin-right: 15px; | |||||
| &__img{ | |||||
| position: absolute; | |||||
| left: 30px; | |||||
| top: 26px; | |||||
| width: 48px; | |||||
| height: 48px; | |||||
| } | |||||
| &__wordTitle{ | |||||
| position: absolute; | |||||
| top: 26px; | |||||
| left: 102px; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:18px; | |||||
| } | |||||
| &__wordContent{ | |||||
| position: absolute; | |||||
| top: 54px; | |||||
| left: 102px; | |||||
| color:#565658; | |||||
| font-size:15px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__centerListBox{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 20px 45px; | |||||
| &__itemBox{ | |||||
| position: relative; | |||||
| width: calc(20% - 20px); | |||||
| height: 229px; | |||||
| margin-right: 20px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| position: absolute; | |||||
| top: 24.5px; | |||||
| left: 20px; | |||||
| } | |||||
| &__title{ | |||||
| position: absolute; | |||||
| top: 20.1px; | |||||
| left: 46px; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| font-weight: 600; | |||||
| } | |||||
| &__content{ | |||||
| position: absolute; | |||||
| width: calc(100% - 40px); | |||||
| height: 114px; | |||||
| top: 61px; | |||||
| left: 20px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border-radius:4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__bottomBox{ | |||||
| padding: 23px 35px 23px 25px; | |||||
| &__titleBox{ | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 39px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__word{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| margin-left: 14px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| padding: 2px 20px 2px 16px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| font-weight: normal; | |||||
| &__circle{ | |||||
| position: relative; | |||||
| width:15px; | |||||
| height:15px; | |||||
| border-radius: 50%; | |||||
| background:#ffffff; | |||||
| box-shadow:0px 1px 3px rgba(0, 82, 217, 0.09); | |||||
| margin-right: 13px; | |||||
| &__inner{ | |||||
| position: absolute; | |||||
| left: 50%; | |||||
| top: 50%; | |||||
| transform: translate(-50%,-50%); | |||||
| width:9px; | |||||
| height:9px; | |||||
| border-radius: 50%; | |||||
| background:#1664ff; | |||||
| box-shadow:0px 1px 3px rgba(22, 100, 255, 0.27); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| // width:647px; | |||||
| height:39px; | |||||
| background:rgba(138, 138, 138, 0.05); | |||||
| border-radius:4px; | |||||
| padding-left: 30px; | |||||
| } | |||||
| } | |||||
| &__queryparams{ | |||||
| margin: 17px 0; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| &__leftbox{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,293 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteSubject, getKnowledgeSubject } from '@/services/subject'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { App, Button, Input, Table, type TablePaginationConfig, type TableProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import AddSubjectConfigModal, { OperationType } from '../components/AddSubjectConfigModal'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; | |||||
| code_repo_name: string; | |||||
| code_repo_vis: number; | |||||
| git_url: string; | |||||
| git_branch: string; | |||||
| git_user_name: string; | |||||
| git_password: string; | |||||
| ssh_key: string; | |||||
| verify_mode: number; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| }; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| updateTime: string; | |||||
| conceptsCount: string; | |||||
| update_by: string; | |||||
| relationsCount: string; | |||||
| description: string; | |||||
| }; | |||||
| function DatasetAnnotation() { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [tableData, setTableData] = useState<EditorData[]>([]); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const navigate = useNavigate(); | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const { message } = App.useApp(); | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const [total, setTotal] = useState(0); | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| }, []); | |||||
| const items = [ | |||||
| { | |||||
| key: 1, | |||||
| label: `图谱版本`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: 2, | |||||
| label: `更新日志`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: null, | |||||
| }, | |||||
| ]; | |||||
| const createCodeConfig = () => { | |||||
| const { close } = openAntdModal(AddSubjectConfigModal, { | |||||
| opType: OperationType.Create, | |||||
| onOk: () => { | |||||
| getList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const getList = () => { | |||||
| const params: Record<string, any> = { | |||||
| pageNum: pagination.current, | |||||
| pageSize: pagination.pageSize, | |||||
| name: inputText, | |||||
| }; | |||||
| getKnowledgeSubject(params).then((ret) => { | |||||
| console.log(ret.rows); | |||||
| setTableData(ret.rows); | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const goSubjectEdit = (row) => { | |||||
| console.log(row); | |||||
| navigate(`/knowledge/subjectView/${row.name}/${row.id}`); | |||||
| }; | |||||
| const handleSubjectDelete = (row) => { | |||||
| modalConfirm({ | |||||
| title: '确定要删除该主体吗?', | |||||
| content: '删除后主体将不可恢复,是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteSubject(row.id).then((res) => { | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getList(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const columnsNow: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Index, { | |||||
| page: pagination.current! - 1, | |||||
| pageSize: pagination.pageSize!, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| title: '主体名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '20%', | |||||
| }, | |||||
| { | |||||
| title: '更新时间', | |||||
| dataIndex: 'updateTime', | |||||
| key: 'updateTime', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '概念个数', | |||||
| dataIndex: 'conceptsCount', | |||||
| key: 'conceptsCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '关系个数', | |||||
| dataIndex: 'relationsCount', | |||||
| key: 'relationsCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '主体描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| width: '30%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 300, | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| onClick={goSubjectEdit} | |||||
| icon={<KFIcon type="icon-chakanxiangqing" />} | |||||
| > | |||||
| 查看 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => goSubjectEdit(record)} | |||||
| > | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button type="link" size="small" key="run" icon={<KFIcon type="icon-bendishangchuan" />}> | |||||
| 导出 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleSubjectDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['mySubject-page']}> | |||||
| <div className={styles['mySubject-page__container']}> | |||||
| 主体管理 | |||||
| <div className={styles['mySubject-page__container__box']}> | |||||
| <img | |||||
| className={styles['mySubject-page__container__box__img']} | |||||
| src={require('@/assets/img/icon-graph.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span> | |||||
| 您还可以创建<span style={{ fontSize: '16px', color: '#1664ff' }}>47</span>个框架{' '} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['mySubject-page__topCenter']}> | |||||
| <div className={styles['mySubject-page__bottomBox']}> | |||||
| <div className={styles['mySubject-page__bottomBox__queryparams']}> | |||||
| <div className={styles['mySubject-page__bottomBox__queryparams__leftbox']}> | |||||
| <Input.Search | |||||
| placeholder="按名称筛选" | |||||
| allowClear | |||||
| onSearch={getList} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| value={inputText} | |||||
| /> | |||||
| {/* <Button | |||||
| type="primary" | |||||
| key="primary" | |||||
| style={{ margin: '0 20px' }} | |||||
| onClick={() => getList()} | |||||
| > | |||||
| 查询 | |||||
| </Button> */} | |||||
| <Button | |||||
| style={{ marginLeft: '20px' }} | |||||
| key="info" | |||||
| onClick={() => handleSubjectDelete()} | |||||
| > | |||||
| 批量删除 | |||||
| </Button> | |||||
| </div> | |||||
| <div> | |||||
| <Button | |||||
| onClick={() => createCodeConfig()} | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 创建主体 | |||||
| </Button> | |||||
| <Button key="info" style={{ margin: '0 20px' }}> | |||||
| obs导入 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columnsNow} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default DatasetAnnotation; | |||||
| @@ -0,0 +1,206 @@ | |||||
| .onlineGraph-page { | |||||
| height: 100%; | |||||
| padding: 0 9px; | |||||
| &__container { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 50px; | |||||
| padding:0 30px; | |||||
| background-image: url(@/assets/img/page-title-bg.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__box{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| &__update{ | |||||
| padding: 7px 10px; | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border:1px solid rgba(22, 100, 255, 0.11); | |||||
| border-radius:4px; | |||||
| margin-right: 20px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__topCenter{ | |||||
| margin: 10px 0 15px 0; | |||||
| width: 100%; | |||||
| background:#ffffff; | |||||
| border-radius:10px; | |||||
| box-shadow:0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| } | |||||
| &__title{ | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| margin: 30px 0 15px 25px; | |||||
| font-weight: 600; | |||||
| } | |||||
| &__listBox{ | |||||
| padding: 0 25px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__itemBox{ | |||||
| position: relative; | |||||
| width: calc(20% - 15px); | |||||
| height:100px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.12); | |||||
| margin-right: 15px; | |||||
| &__img{ | |||||
| position: absolute; | |||||
| left: 30px; | |||||
| top: 26px; | |||||
| width: 48px; | |||||
| height: 48px; | |||||
| } | |||||
| &__wordTitle{ | |||||
| position: absolute; | |||||
| top: 26px; | |||||
| left: 102px; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:18px; | |||||
| } | |||||
| &__wordContent{ | |||||
| position: absolute; | |||||
| top: 54px; | |||||
| left: 102px; | |||||
| color:#565658; | |||||
| font-size:15px; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__centerListBox{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding: 20px 45px; | |||||
| &__itemBox{ | |||||
| position: relative; | |||||
| width: calc(20% - 20px); | |||||
| height: 229px; | |||||
| margin-right: 20px; | |||||
| background:linear-gradient(180deg,#f7faff 0%,#ffffff 100%); | |||||
| border-radius:4px; | |||||
| box-shadow:0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| &__img{ | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| position: absolute; | |||||
| top: 24.5px; | |||||
| left: 20px; | |||||
| } | |||||
| &__title{ | |||||
| position: absolute; | |||||
| top: 20.1px; | |||||
| left: 46px; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| font-weight: 600; | |||||
| } | |||||
| &__content{ | |||||
| position: absolute; | |||||
| width: calc(100% - 40px); | |||||
| height: 114px; | |||||
| top: 61px; | |||||
| left: 20px; | |||||
| background:rgba(22, 100, 255, 0.04); | |||||
| border-radius:4px; | |||||
| padding: 15px; | |||||
| &__center{ | |||||
| position: absolute; | |||||
| left: 50%; | |||||
| top: 50%; | |||||
| color:#1664ff; | |||||
| font-size:14px; | |||||
| transform: translate(-50%,-50%); | |||||
| cursor: pointer; | |||||
| } | |||||
| &__status{ | |||||
| color:#565658; | |||||
| font-size:14px; | |||||
| margin-right: 15px; | |||||
| // width: 38px; | |||||
| } | |||||
| } | |||||
| &__button{ | |||||
| position: absolute; | |||||
| color:#1664ff; | |||||
| font-size:13px; | |||||
| bottom: 20px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__bottomBox{ | |||||
| padding: 23px 35px 23px 25px; | |||||
| &__titleBox{ | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 39px; | |||||
| &__left{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| font-weight:600; | |||||
| color:#1d1d20; | |||||
| font-size:16px; | |||||
| &__word{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width:206px; | |||||
| height:27px; | |||||
| margin-left: 14px; | |||||
| background:rgba(22, 100, 255, 0.09); | |||||
| border-radius:4px; | |||||
| padding: 2px 20px 2px 16px; | |||||
| color:#4c5360; | |||||
| font-size:14px; | |||||
| font-weight: normal; | |||||
| &__circle{ | |||||
| position: relative; | |||||
| width:15px; | |||||
| height:15px; | |||||
| border-radius: 50%; | |||||
| background:#ffffff; | |||||
| box-shadow:0px 1px 3px rgba(0, 82, 217, 0.09); | |||||
| margin-right: 13px; | |||||
| &__inner{ | |||||
| position: absolute; | |||||
| left: 50%; | |||||
| top: 50%; | |||||
| transform: translate(-50%,-50%); | |||||
| width:9px; | |||||
| height:9px; | |||||
| border-radius: 50%; | |||||
| background:#1664ff; | |||||
| box-shadow:0px 1px 3px rgba(22, 100, 255, 0.27); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| // width:647px; | |||||
| height:39px; | |||||
| background:rgba(138, 138, 138, 0.05); | |||||
| border-radius:4px; | |||||
| padding-left: 30px; | |||||
| } | |||||
| } | |||||
| &__queryparams{ | |||||
| margin: 17px 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,632 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { | |||||
| deleteKnowledgeGraphVersion, | |||||
| fullUpdate, | |||||
| getKnowledgeGraphById, | |||||
| getKnowledgeGraphVersionList, | |||||
| incrementalUpdate, | |||||
| rollback, | |||||
| } from '@/services/graph'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useSearchParams } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| Input, | |||||
| Select, | |||||
| Table, | |||||
| type TablePaginationConfig, | |||||
| type TableProps, | |||||
| Tabs, | |||||
| } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { useNavigate } from 'react-router-dom'; | |||||
| import AddCodeConfigModal, { OperationType } from '../components/AddUpdateModal'; | |||||
| // import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | |||||
| import styles from './index.less'; | |||||
| export type CodeConfigData = { | |||||
| id: number; | |||||
| code_repo_name: string; | |||||
| code_repo_vis: number; | |||||
| git_url: string; | |||||
| git_branch: string; | |||||
| git_user_name: string; | |||||
| git_password: string; | |||||
| ssh_key: string; | |||||
| verify_mode: number; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| }; | |||||
| export type topData = { | |||||
| id: number; //主键 | |||||
| name: string; //名称 | |||||
| status: number; //状态,0代表不可用,1代表可用 | |||||
| spec: string; //图谱规格 | |||||
| createTime: string; //创建时间 | |||||
| updateTime: string; //最后更新时间 | |||||
| relationCount: number; //关系数 | |||||
| entityCount: number; //实体数 | |||||
| }; | |||||
| export type EditorData = { | |||||
| id: number; | |||||
| name: string; | |||||
| status: string; | |||||
| computing_resource: string; | |||||
| update_by: string; | |||||
| create_time: string; | |||||
| url: string; | |||||
| }; | |||||
| function DatasetAnnotation() { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [tableData, setTableData] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [topData, setTopData] = useState<topData>([]); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const navigate = useNavigate(); | |||||
| const { message } = App.useApp(); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| const updateMethodList = ['待更新', '增量更新', '全量更新']; | |||||
| const stateList = ['离线', '在线']; | |||||
| const graphId = searchParams.get('id') || ''; | |||||
| const graphVersion = searchParams.get('version') || ''; | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const clickRouter = (val: string) => { | |||||
| navigate(`/knowledge/graphCounter?tabActive=${val}`); | |||||
| }; | |||||
| const getList = () => { | |||||
| const params = { | |||||
| id: graphId, | |||||
| version: graphVersion, | |||||
| }; | |||||
| getKnowledgeGraphById(params).then((ret) => { | |||||
| console.log(ret); | |||||
| setTopData(ret.data); | |||||
| }); | |||||
| }; | |||||
| const getTableList = () => { | |||||
| const params = { | |||||
| id: graphId, | |||||
| }; | |||||
| getKnowledgeGraphVersionList(params).then((ret) => { | |||||
| console.log(ret); | |||||
| setTableData(ret.rows); | |||||
| setTotal(ret.total); | |||||
| }); | |||||
| }; | |||||
| const incrementalUpdates = (row) => { | |||||
| incrementalUpdate({ | |||||
| id: row.id, | |||||
| }).then((ret) => { | |||||
| if (ret.code == 200) { | |||||
| getTableList(); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const fullUpdates = (row) => { | |||||
| fullUpdate({ | |||||
| id: row.id, | |||||
| }).then((ret) => { | |||||
| if (ret.code == 200) { | |||||
| getTableList(); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const singleRollback = (row) => { | |||||
| rollback({ | |||||
| id: row.id, | |||||
| }).then((ret) => { | |||||
| if (ret.code == 200) { | |||||
| getTableList(); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| const [total, setTotal] = useState(0); | |||||
| useEffect(() => { | |||||
| getList(); | |||||
| getTableList(); | |||||
| }, []); | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: `图谱版本`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: null, | |||||
| }, | |||||
| // { | |||||
| // key: 2, | |||||
| // label: `更新日志`, | |||||
| // icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| // children: null, | |||||
| // }, | |||||
| ]; | |||||
| const addCodeConfig = () => { | |||||
| const { close } = openAntdModal(AddCodeConfigModal, { | |||||
| opType: OperationType.Create, | |||||
| codeConfigData: { | |||||
| id: searchParams.get('id') || '', | |||||
| }, | |||||
| onOk: () => { | |||||
| getTableList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const handleVersionDelete = (row) => { | |||||
| modalConfirm({ | |||||
| title: '确定要删除该版本吗?', | |||||
| content: '删除后版本将不可恢复,是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteKnowledgeGraphVersion(row.id).then((res) => { | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| getTableList(); | |||||
| } else { | |||||
| getTableList(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const columnsNow: TableProps<EditorData>['columns'] = [ | |||||
| { | |||||
| title: '版本名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '30%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '在线状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| width: '10%', | |||||
| render: (_: any, record: EditorData) => <div>{stateList[record.status]}</div>, | |||||
| }, | |||||
| { | |||||
| title: '更新方式', | |||||
| dataIndex: 'updateMethod', | |||||
| key: 'updateMethod', | |||||
| width: '10%', | |||||
| render: (_: any, record: EditorData) => <div>{updateMethodList[record.updateMethod]}</div>, | |||||
| }, | |||||
| { | |||||
| title: '实体个数', | |||||
| dataIndex: 'entityCount', | |||||
| key: 'entityCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '关系个数', | |||||
| dataIndex: 'relationCount', | |||||
| key: 'relationCount', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '创建时间', | |||||
| dataIndex: 'createTime', | |||||
| key: 'createTime', | |||||
| width: '20%', | |||||
| render: tableCellRender(false, TableCellValueType.Date), | |||||
| }, | |||||
| { | |||||
| title: '描述', | |||||
| dataIndex: 'remark', | |||||
| key: 'remark', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: '30%', | |||||
| key: 'operation', | |||||
| render: (_: any, record: EditorData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="jingxiang" | |||||
| disabled={record.updateMethod != 0} | |||||
| icon={<KFIcon type="icon-chakanxiangqing" />} | |||||
| onClick={() => incrementalUpdates(record)} | |||||
| > | |||||
| 增量更新 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="jingxiang" | |||||
| disabled={record.updateMethod != 0} | |||||
| icon={<KFIcon type="icon-chakanxiangqing" />} | |||||
| onClick={() => fullUpdates(record)} | |||||
| > | |||||
| 全量更新 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="jingxiang" | |||||
| disabled={!(record.status == 0 && record.updateMethod != 0)} | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => singleRollback(record)} | |||||
| > | |||||
| 回退 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| disabled={record.status !== 0} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleVersionDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| {/* <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleEditorDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> */} | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['onlineGraph-page']}> | |||||
| <div className={styles['onlineGraph-page__container']}> | |||||
| <span>{topData.name}</span> | |||||
| <div className={styles['onlineGraph-page__container__box']}> | |||||
| <div | |||||
| onClick={addCodeConfig} | |||||
| className={styles['onlineGraph-page__container__box__update']} | |||||
| > | |||||
| 导入 | |||||
| </div> | |||||
| {/* <div | |||||
| onClick={allCodeConfig} | |||||
| className={styles['onlineGraph-page__container__box__update']} | |||||
| > | |||||
| 全量更新 | |||||
| </div> */} | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__topCenter']}> | |||||
| <div className={styles['onlineGraph-page__title']}>在线版本图谱</div> | |||||
| <div className={styles['onlineGraph-page__listBox']}> | |||||
| {/* {dataList && dataList.length !== 0 && ( */} | |||||
| <> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__listBox__itemBox__img']} | |||||
| src={require('@/assets/img/runningstatus.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordTitle']}> | |||||
| {topData.status == '1' ? '可用' : '不可用'} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordContent']}> | |||||
| 运行状态 | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__listBox__itemBox__img']} | |||||
| src={require('@/assets/img/runningstatus.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordTitle']}> | |||||
| {topData.entityCount} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordContent']}> | |||||
| 实体个数(个) | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__listBox__itemBox__img']} | |||||
| src={require('@/assets/img/runningstatus.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordTitle']}> | |||||
| {topData.relationCount} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordContent']}> | |||||
| 关系个数(个) | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__listBox__itemBox__img']} | |||||
| src={require('@/assets/img/runningstatus.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordTitle']}> | |||||
| {formatDate(topData.updateTime, 'YYYY-MM-DD')} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordContent']}> | |||||
| 最后更新时间 | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__listBox__itemBox__img']} | |||||
| src={require('@/assets/img/runningstatus.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordTitle']}> | |||||
| {formatDate(topData.createTime, 'YYYY-MM-DD')} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__listBox__itemBox__wordContent']}> | |||||
| 创建时间 | |||||
| </div> | |||||
| </div> | |||||
| </> | |||||
| {/* )} */} | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__title']}>在线版本图谱</div> | |||||
| <div className={styles['onlineGraph-page__centerListBox']}> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__img']} | |||||
| src={require('@/assets/img/graph-star.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__title']}> | |||||
| 知识图谱预览查询 | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__content']}> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__content__center']}> | |||||
| 预览图谱 | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__img']} | |||||
| src={require('@/assets/img/graph-star.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__title']}> | |||||
| 知识图谱问答KBQA | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__content']}> | |||||
| <div style={{ marginBottom: '12px' }}> | |||||
| <span | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__content__status']} | |||||
| > | |||||
| 状态 | |||||
| </span> | |||||
| <span style={{ color: '#6ac21d', fontSize: '14px' }}>已开通</span> | |||||
| </div> | |||||
| <div style={{ display: 'flex', alignItems: 'start' }}> | |||||
| <div | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__content__status']} | |||||
| > | |||||
| 状态 | |||||
| </div> | |||||
| <div style={{ color: '#1d1d20', fontSize: '14px', flex: 1 }}> | |||||
| 利用NLP能力理解用户输入的不同类型问题、找到并返回知识图谱中相关的答案。 | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div | |||||
| onClick={() => { | |||||
| clickRouter('3'); | |||||
| }} | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__button']} | |||||
| style={{ right: '20px' }} | |||||
| > | |||||
| 接口信息 | |||||
| </div> | |||||
| <div | |||||
| onClick={() => { | |||||
| clickRouter('2'); | |||||
| }} | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__button']} | |||||
| style={{ right: '101px' }} | |||||
| > | |||||
| 问答体验 | |||||
| </div> | |||||
| <div | |||||
| onClick={() => { | |||||
| clickRouter('1'); | |||||
| }} | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__button']} | |||||
| style={{ right: '182px' }} | |||||
| > | |||||
| 问答配置 | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox']}> | |||||
| <img | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__img']} | |||||
| src={require('@/assets/img/graph-star.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__title']}> | |||||
| 实体链接 | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__centerListBox__itemBox__content']}> | |||||
| <div style={{ marginBottom: '12px' }}> | |||||
| <span | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__content__status']} | |||||
| > | |||||
| 状态 | |||||
| </span> | |||||
| <span style={{ color: '#6ac21d', fontSize: '14px' }}>已开通</span> | |||||
| </div> | |||||
| <div style={{ display: 'flex', alignItems: 'start' }}> | |||||
| <div | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__content__status']} | |||||
| > | |||||
| 状态 | |||||
| </div> | |||||
| <span style={{ color: '#1d1d20', fontSize: '14px', flex: 1 }}> | |||||
| 识别句子中出现的知识图谱中的实体,并返回实体相关信息。 | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| <div | |||||
| className={styles['onlineGraph-page__centerListBox__itemBox__button']} | |||||
| style={{ right: '20px' }} | |||||
| > | |||||
| 接口信息 | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__bottomBox']}> | |||||
| <div className={styles['onlineGraph-page__bottomBox__titleBox']}> | |||||
| <div className={styles['onlineGraph-page__bottomBox__titleBox__left']}> | |||||
| <span>版本统计</span> | |||||
| <div className={styles['onlineGraph-page__bottomBox__titleBox__left__word']}> | |||||
| <div | |||||
| className={styles['onlineGraph-page__bottomBox__titleBox__left__word__circle']} | |||||
| > | |||||
| <div | |||||
| className={ | |||||
| styles['onlineGraph-page__bottomBox__titleBox__left__word__circle__inner'] | |||||
| } | |||||
| ></div> | |||||
| </div> | |||||
| 您还可以创建<span style={{ color: '#1664ff', fontSize: '16px' }}>1</span>个版本{' '} | |||||
| </div> | |||||
| </div> | |||||
| <div className={styles['onlineGraph-page__bottomBox__titleBox__right']}> | |||||
| <div> | |||||
| <span style={{ color: '#565658', fontSize: '14px', marginRight: '10px' }}> | |||||
| 已上线 | |||||
| </span> | |||||
| <span style={{ color: '#1d1d20', fontSize: '15px', marginRight: '61px' }}>1</span> | |||||
| </div> | |||||
| <div> | |||||
| <span style={{ color: '#565658', fontSize: '14px', marginRight: '10px' }}> | |||||
| 未上线 | |||||
| </span> | |||||
| <span style={{ color: '#1d1d20', fontSize: '15px', marginRight: '61px' }}>1</span> | |||||
| </div> | |||||
| <div> | |||||
| <span style={{ color: '#565658', fontSize: '14px', marginRight: '10px' }}> | |||||
| 增量更新 | |||||
| </span> | |||||
| <span style={{ color: '#1d1d20', fontSize: '15px', marginRight: '61px' }}>1</span> | |||||
| </div> | |||||
| <div> | |||||
| <span style={{ color: '#565658', fontSize: '14px', marginRight: '10px' }}> | |||||
| 全量更新 | |||||
| </span> | |||||
| <span style={{ color: '#1d1d20', fontSize: '15px', marginRight: '61px' }}>1</span> | |||||
| </div> | |||||
| <div> | |||||
| <span style={{ color: '#565658', fontSize: '14px', marginRight: '10px' }}> | |||||
| 发布次数 | |||||
| </span> | |||||
| <span style={{ color: '#1d1d20', fontSize: '15px', marginRight: '20px' }}>1</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| <div className={styles['onlineGraph-page__bottomBox__queryparams']}> | |||||
| <Input.Search | |||||
| placeholder="按镜像名称筛选" | |||||
| allowClear | |||||
| // onSearch={onSearch} | |||||
| // onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| // value={inputText} | |||||
| /> | |||||
| <Select | |||||
| allowClear | |||||
| style={{ width: '106px', margin: '0px 30px 0 20px' }} | |||||
| placeholder="请选择数据集分类" | |||||
| options={[]} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| <Button | |||||
| key="primary" | |||||
| type="primary" | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 查询 | |||||
| </Button> | |||||
| </div> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columnsNow} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default DatasetAnnotation; | |||||
| @@ -0,0 +1,976 @@ | |||||
| import colorB from '@/assets/img/tupu/b-circle.png'; | |||||
| import colorC from '@/assets/img/tupu/c-circle.png'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { editKnowledgeSubject, getKnowledgeSubjectById } from '@/services/subject'; | |||||
| import { fittingString, parseJsonText, s8 } from '@/utils'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import G6 from '@antv/g6'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { App, Button } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | |||||
| import AddSubjectConfigModal, { OperationType } from '../components/subjecDetailEdit'; | |||||
| import PropsEdge from '../components/SubjectEdgeDrawer'; | |||||
| import Props from '../components/SubjectNodeDrawer'; | |||||
| import styles from './index.less'; | |||||
| import { findAllParentNodes } from './utils'; | |||||
| let graph = null; | |||||
| const EditPipeline = () => { | |||||
| const navigate = useNavigate(); | |||||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||||
| console.log(locationParams); | |||||
| const graphRef = useRef(); | |||||
| const paramsDrawerRef = useRef(); | |||||
| const edgeDrawerRef = useRef(); | |||||
| const propsRef = useRef(); | |||||
| const propsEdgeRef = useRef(); | |||||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | |||||
| const [edgeDrawerOpen, edgeParamsDrawer, closeEdgeDrawer] = useVisible(false); | |||||
| const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); | |||||
| const { message } = App.useApp(); | |||||
| const [contextFlag, setContextFlag] = useState(false); | |||||
| const [subjectName, setSubjectName] = useState(''); | |||||
| const colorList = [colorB, colorC]; | |||||
| const [subjectDesrc, setSubjectDesrc] = useState(''); | |||||
| let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx; | |||||
| let dragSourceNode; | |||||
| useEffect(() => { | |||||
| initGraph(); | |||||
| getFirstWorkflow(locationParams.id); | |||||
| console.log(locationParams); | |||||
| const changeSize = () => { | |||||
| if (!graph || graph.get('destroyed')) return; | |||||
| if (!graphRef.current) return; | |||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | |||||
| graph.fitView(); | |||||
| }; | |||||
| window.addEventListener('resize', changeSize); | |||||
| return () => { | |||||
| window.removeEventListener('resize', changeSize); | |||||
| }; | |||||
| }, []); | |||||
| // 拖拽结束,添加新节点 | |||||
| const onDragEnd = (val) => { | |||||
| const { x, y } = val; | |||||
| const point = graph.getPointByClient(x, y); | |||||
| // 元模型 | |||||
| const model = { | |||||
| ...val, | |||||
| x: point.x, | |||||
| y: point.y, | |||||
| id: val.component_name + '-' + s8(), | |||||
| isCluster: false, | |||||
| formError: true, | |||||
| }; | |||||
| // console.log('model', model); | |||||
| graph.addItem('node', model, false); | |||||
| }; | |||||
| const updateCodeConfig = () => { | |||||
| getKnowledgeSubjectById(locationParams.id).then((ret) => { | |||||
| console.log(ret); | |||||
| if (ret.code == 200) { | |||||
| const { close } = openAntdModal(AddSubjectConfigModal, { | |||||
| opType: OperationType.Update, | |||||
| codeConfigData: { | |||||
| name: ret.data.name, | |||||
| description: ret.data.description, | |||||
| id: ret.data.id, | |||||
| }, | |||||
| onOk: () => { | |||||
| // getList(); | |||||
| getKnowledgeSubjectById(locationParams.id).then((res) => { | |||||
| getFirstWorkflow(locationParams.id); | |||||
| close(); | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| // 节点数据发生变化 | |||||
| const handleFormEdgeChange = (val) => { | |||||
| console.log(val); | |||||
| if (graph) { | |||||
| const data = graph.save(); | |||||
| const index = data.edges.findIndex((item) => { | |||||
| return item.id === val.id; | |||||
| }); | |||||
| data.edges[index].label = val.label; | |||||
| const zoom = graph.getZoom(); | |||||
| // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 | |||||
| const lastPoint = graph.getCanvasByPoint(0, 0); | |||||
| graph.changeData(data); | |||||
| graph.render(); | |||||
| graph.zoomTo(zoom); | |||||
| // 获取重新渲染之后点(0, 0)在画布的位置 | |||||
| const newPoint = graph.getCanvasByPoint(0, 0); | |||||
| // 移动画布相对位移; | |||||
| graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | |||||
| } | |||||
| }; | |||||
| const handleFormChange = (val) => { | |||||
| console.log(val); | |||||
| if (graph) { | |||||
| const data = graph.save(); | |||||
| const index = data.nodes.findIndex((item) => { | |||||
| return item.id === val.id; | |||||
| }); | |||||
| data.nodes[index] = val; | |||||
| const zoom = graph.getZoom(); | |||||
| // 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置 | |||||
| const lastPoint = graph.getCanvasByPoint(0, 0); | |||||
| graph.changeData(data); | |||||
| graph.render(); | |||||
| graph.zoomTo(zoom); | |||||
| // 获取重新渲染之后点(0, 0)在画布的位置 | |||||
| const newPoint = graph.getCanvasByPoint(0, 0); | |||||
| // 移动画布相对位移; | |||||
| graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y); | |||||
| } | |||||
| }; | |||||
| // 保存 | |||||
| const savePipeline = async (val) => { | |||||
| // const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | |||||
| // if (globalParamError) { | |||||
| // message.error('全局参数配置有误'); | |||||
| // openParamsDrawer(); | |||||
| // return; | |||||
| // } | |||||
| // closeParamsDrawer(); | |||||
| // const [propsRes, propsError] = await to(propsRef.current.validateFields()); | |||||
| // if (propsError) { | |||||
| // message.error('节点必填项必须配置'); | |||||
| // return; | |||||
| // } | |||||
| // propsRef.current.close(); | |||||
| // setTimeout(() => { | |||||
| // const data = graph.save(); | |||||
| // // console.log(data); | |||||
| // const errorNode = data.nodes.find((item) => item.formError === true); | |||||
| // if (errorNode) { | |||||
| // message.error(`【${errorNode.label}】节点必填项必须配置`); | |||||
| // const graphNode = graph.findById(errorNode.id); | |||||
| // if (graphNode) { | |||||
| // openNodeDrawer(graphNode, true); | |||||
| // } | |||||
| // return; | |||||
| // } | |||||
| const data = graph.save(); | |||||
| console.log(data); | |||||
| const params = { | |||||
| id: locationParams.id / 1, | |||||
| name: subjectName, | |||||
| description: subjectDesrc, | |||||
| dag: JSON.stringify(data), | |||||
| // global_param: JSON.stringify(globalParamRes.global_param), | |||||
| }; | |||||
| editKnowledgeSubject(params).then((ret) => { | |||||
| message.success('保存成功'); | |||||
| // setTimeout(() => { | |||||
| // if (val) { | |||||
| // navigate({ pathname: `/pipeline/template` }); | |||||
| // } | |||||
| // }, 500); | |||||
| }); | |||||
| // }, 500); | |||||
| }; | |||||
| // 渲染数据 | |||||
| const getGraphData = (data) => { | |||||
| if (graph && data) { | |||||
| graph.data(data); | |||||
| graph.render(); | |||||
| } else { | |||||
| setTimeout(() => { | |||||
| getGraphData(data); | |||||
| }, 500); | |||||
| } | |||||
| }; | |||||
| // 处理并行边,暂时没有用 | |||||
| const processParallelEdgesOnAnchorPoint = ( | |||||
| edges, | |||||
| offsetDiff = 15, | |||||
| multiEdgeType = 'cubic-vertical', | |||||
| singleEdgeType = undefined, | |||||
| loopEdgeType = undefined, | |||||
| ) => { | |||||
| const len = edges.length; | |||||
| const cod = offsetDiff * 2; | |||||
| const loopPosition = [ | |||||
| 'top', | |||||
| 'top-right', | |||||
| 'right', | |||||
| 'bottom-right', | |||||
| 'bottom', | |||||
| 'bottom-left', | |||||
| 'left', | |||||
| 'top-left', | |||||
| ]; | |||||
| const edgeMap = {}; | |||||
| const tags = []; | |||||
| const reverses = {}; | |||||
| for (let i = 0; i < len; i++) { | |||||
| const edge = edges[i]; | |||||
| const { source, target, sourceAnchor, targetAnchor } = edge; | |||||
| const sourceTarget = `${source}|${sourceAnchor}-${target}|${targetAnchor}`; | |||||
| if (tags[i]) continue; | |||||
| if (!edgeMap[sourceTarget]) { | |||||
| edgeMap[sourceTarget] = []; | |||||
| } | |||||
| tags[i] = true; | |||||
| edgeMap[sourceTarget].push(edge); | |||||
| for (let j = 0; j < len; j++) { | |||||
| if (i === j) continue; | |||||
| const sedge = edges[j]; | |||||
| const { | |||||
| source: src, | |||||
| target: dst, | |||||
| sourceAnchor: srcAnchor, | |||||
| targetAnchor: dstAnchor, | |||||
| } = sedge; | |||||
| // 两个节点之间共同的边 | |||||
| // 第一条的source = 第二条的target | |||||
| // 第一条的target = 第二条的source | |||||
| if (!tags[j]) { | |||||
| if ( | |||||
| source === dst && | |||||
| sourceAnchor === dstAnchor && | |||||
| target === src && | |||||
| targetAnchor === srcAnchor | |||||
| ) { | |||||
| edgeMap[sourceTarget].push(sedge); | |||||
| tags[j] = true; | |||||
| reverses[ | |||||
| `${src}|${srcAnchor}|${dst}|${dstAnchor}|${edgeMap[sourceTarget].length - 1}` | |||||
| ] = true; | |||||
| } else if ( | |||||
| source === src && | |||||
| sourceAnchor === srcAnchor && | |||||
| target === dst && | |||||
| targetAnchor === dstAnchor | |||||
| ) { | |||||
| edgeMap[sourceTarget].push(sedge); | |||||
| tags[j] = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // eslint-disable-next-line | |||||
| for (const key in edgeMap) { | |||||
| const arcEdges = edgeMap[key]; | |||||
| const { length } = arcEdges; | |||||
| for (let k = 0; k < length; k++) { | |||||
| const current = arcEdges[k]; | |||||
| if (current.source === current.target) { | |||||
| if (loopEdgeType) current.type = loopEdgeType; | |||||
| // 超过8条自环边,则需要重新处理 | |||||
| current.loopCfg = { | |||||
| position: loopPosition[k % 8], | |||||
| dist: Math.floor(k / 8) * 20 + 50, | |||||
| }; | |||||
| continue; | |||||
| } | |||||
| if ( | |||||
| length === 1 && | |||||
| singleEdgeType && | |||||
| (current.source !== current.target || current.sourceAnchor !== current.targetAnchor) | |||||
| ) { | |||||
| current.type = singleEdgeType; | |||||
| continue; | |||||
| } | |||||
| current.type = multiEdgeType; | |||||
| const sign = | |||||
| (k % 2 === 0 ? 1 : -1) * | |||||
| (reverses[ | |||||
| `${current.source}|${current.sourceAnchor}|${current.target}|${current.targetAnchor}|${k}` | |||||
| ] | |||||
| ? -1 | |||||
| : 1); | |||||
| if (length % 2 === 1) { | |||||
| current.curveOffset = sign * Math.ceil(k / 2) * cod; | |||||
| } else { | |||||
| current.curveOffset = sign * (Math.floor(k / 2) * cod + offsetDiff); | |||||
| } | |||||
| } | |||||
| } | |||||
| return edges; | |||||
| }; | |||||
| // 判断两个节点之间是否有边 | |||||
| const hasEdge = (source, target) => { | |||||
| const neighbors = source.getNeighbors(); | |||||
| for (const node of neighbors) { | |||||
| // 新建边的时候,获取的 neighbors 的数据有问题,不全是 INode 类型,可能没有 getID 方法 | |||||
| if (node.getID?.() === target.getID?.()) return true; | |||||
| } | |||||
| return false; | |||||
| }; | |||||
| // 复制节点 | |||||
| const cloneElement = (item) => { | |||||
| let data = graph.save(); | |||||
| const nodeId = s8(); | |||||
| data.nodes.push({ | |||||
| ...item.getModel(), | |||||
| label: item.getModel().label + '-copy', | |||||
| x: item.getModel().x + 150, | |||||
| y: item.getModel().y, | |||||
| id: item.getModel().component_name + '-' + nodeId, | |||||
| }); | |||||
| graph.changeData(data); | |||||
| }; | |||||
| const addElement = () => { | |||||
| let data = graph.save(); | |||||
| const nodeId = s8(); | |||||
| data.nodes.push({ | |||||
| ...item.getModel(), | |||||
| label: item.getModel().label, | |||||
| x: item.getModel().x + 150, | |||||
| y: item.getModel().y, | |||||
| id: item.getModel().component_name + '-' + nodeId, | |||||
| }); | |||||
| graph.changeData(data); | |||||
| }; | |||||
| // 获取流水线详情 | |||||
| const getFirstWorkflow = async (val) => { | |||||
| getKnowledgeSubjectById(val).then((res) => { | |||||
| if (res && res.data) { | |||||
| setSubjectName(res.data.name); | |||||
| setSubjectDesrc(res.data.description); | |||||
| const { dag } = res.data; | |||||
| if (dag) { | |||||
| getGraphData(parseJsonText(dag)); | |||||
| } | |||||
| } | |||||
| }); | |||||
| // if (res && res.data) { | |||||
| // const { global_param, dag } = res.data; | |||||
| // setGlobalParam(global_param || []); | |||||
| // if (dag) { | |||||
| // getGraphData(parseJsonText(dag)); | |||||
| // } | |||||
| // } | |||||
| }; | |||||
| // 打开节点抽屉 | |||||
| const openNodeDrawer = (node, validate = false) => { | |||||
| console.log(node); | |||||
| // 获取所有的上游节点 | |||||
| const parentNodes = findAllParentNodes(graph, node); | |||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| // 打开节点编辑抽屉 | |||||
| propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); | |||||
| }; | |||||
| // 打开边抽屉 | |||||
| const openEdgeDrawer = (edge, validate = false) => { | |||||
| // // 获取所有的上游节点 | |||||
| // const parentNodes = findAllParentNodes(graph, node); | |||||
| // // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| // const globalParams = | |||||
| // edgeDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| // 打开节点编辑抽屉 | |||||
| console.log(edge); | |||||
| propsEdgeRef.current.showDrawer(edge.getModel(), validate); | |||||
| }; | |||||
| // 初始化图 | |||||
| const initGraph = () => { | |||||
| // const contextMenu = initMenu(); | |||||
| G6.registerNode( | |||||
| 'rect-node', | |||||
| { | |||||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||||
| getAnchorPoints(cfg) { | |||||
| return ( | |||||
| cfg.anchorPoints || [ | |||||
| // 四个,上下左右 | |||||
| [0.5, 0.4], | |||||
| // [0.5, 1], | |||||
| // [0, 0.5], | |||||
| // [1, 0.5], | |||||
| ] | |||||
| ); | |||||
| }, | |||||
| afterDraw(cfg, group) { | |||||
| group.addShape('image', { | |||||
| name: 'custom-image', | |||||
| attrs: { | |||||
| x: -20, | |||||
| y: -20, | |||||
| width: 40, | |||||
| height: 40, | |||||
| img: cfg.color ? colorList[cfg.color - 1] : colorC, | |||||
| cursor: 'pointer', | |||||
| stroke: '#fff', | |||||
| fill: 'transparent', | |||||
| }, | |||||
| draggable: true, | |||||
| }); | |||||
| if (cfg.label) { | |||||
| group.addShape('text', { | |||||
| attrs: { | |||||
| text: fittingString(cfg.label, 70, 10), | |||||
| x: 0, | |||||
| y: 30, | |||||
| fontSize: 10, | |||||
| textAlign: 'center', | |||||
| textBaseline: 'middle', | |||||
| fill: '#000', | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| name: 'text-shape', | |||||
| draggable: true, | |||||
| }); | |||||
| } | |||||
| // if (cfg.formError) { | |||||
| // group.addShape('image', { | |||||
| // attrs: { | |||||
| // x: 43, | |||||
| // y: -24, | |||||
| // width: 18, | |||||
| // height: 18, | |||||
| // img: require('@/assets/img/pipeline-warning.png'), | |||||
| // cursor: 'pointer', | |||||
| // }, | |||||
| // draggable: false, | |||||
| // capture: false, | |||||
| // }); | |||||
| // } | |||||
| const bbox = group.getBBox(); | |||||
| // if (cfg.formError) { | |||||
| // bbox.y += 6; | |||||
| // bbox.width -= 6; | |||||
| // bbox.height -= 6; | |||||
| // } | |||||
| const anchorPoints = this.getAnchorPoints(cfg); | |||||
| anchorPoints.forEach((anchorPos, i) => { | |||||
| group.addShape('circle', { | |||||
| attrs: { | |||||
| r: 3, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | |||||
| y: bbox.y + bbox.height * anchorPos[1], | |||||
| fill: '#fff', | |||||
| stroke: '#a4a4a5', | |||||
| cursor: 'crosshair', | |||||
| lineWidth: 1, | |||||
| }, | |||||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||||
| links: 0, // cache the number of edges connected to this shape | |||||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||||
| draggable: true, | |||||
| }); | |||||
| }); | |||||
| }, | |||||
| // response the state changes and show/hide the link-point circles | |||||
| setState(name, value, item) { | |||||
| const group = item.getContainer(); | |||||
| const shape = group.get('children')[0]; | |||||
| shape.attr('stroke', 'transparent'); | |||||
| const anchorPoints = group.findAll((item) => item.get('name') === 'anchor-point'); | |||||
| if (name === 'hover') { | |||||
| if (value) { | |||||
| // shape.attr('stroke', themes['primaryColor']); | |||||
| anchorPoints.forEach((point) => { | |||||
| point.show(); | |||||
| }); | |||||
| } else { | |||||
| shape.attr('stroke', 'transparent'); | |||||
| anchorPoints.forEach((point) => { | |||||
| point.hide(); | |||||
| }); | |||||
| } | |||||
| } else if (name === 'drag') { | |||||
| if (sourceAnchorIdx !== null && sourceAnchorIdx !== undefined) { | |||||
| const anchorPoint = anchorPoints[sourceAnchorIdx]; | |||||
| // anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); | |||||
| // anchorPoint.attr('lineWidth', value ? 2 : 1); | |||||
| } | |||||
| } else if (name === 'drop') { | |||||
| if (dropAnchorIdx !== null && dropAnchorIdx !== undefined) { | |||||
| const anchorPoint = anchorPoints[dropAnchorIdx]; | |||||
| // anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); | |||||
| // anchorPoint.attr('lineWidth', value ? 2 : 1); | |||||
| } | |||||
| } | |||||
| }, | |||||
| }, | |||||
| 'rect', | |||||
| ); | |||||
| graph = new G6.Graph({ | |||||
| container: graphRef.current, | |||||
| width: graphRef.current.clientWidth || 500, | |||||
| height: graphRef.current.clientHeight || '100%', | |||||
| animate: false, | |||||
| groupByTypes: true, | |||||
| fitView: true, | |||||
| // plugins: [contextMenu], | |||||
| enabledStack: false, | |||||
| fitView: true, | |||||
| minZoom: 0.5, | |||||
| maxZoom: 5, | |||||
| fitViewPadding: 200, | |||||
| // behaviors: [ | |||||
| // { | |||||
| // type: 'create-edge', | |||||
| // trigger: 'click', | |||||
| // style: { | |||||
| // fill: 'red', | |||||
| // lineWidth: 2, | |||||
| // }, | |||||
| // }, | |||||
| // ], | |||||
| modes: { | |||||
| default: [ | |||||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||||
| { | |||||
| type: 'drag-node', | |||||
| shouldBegin: (e) => { | |||||
| if (e.target.get('name') === 'anchor-point') return false; | |||||
| return true; | |||||
| }, | |||||
| }, | |||||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||||
| { | |||||
| type: 'create-edge', | |||||
| trigger: 'drag', | |||||
| shouldBegin: (e) => { | |||||
| // avoid beginning at other shapes on the node | |||||
| if (e.target && e.target.get('name') !== 'anchor-point') return false; | |||||
| sourceAnchorIdx = e.target.get('anchorPointIdx'); | |||||
| e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle | |||||
| dragSourceNode = e.item; | |||||
| return true; | |||||
| }, | |||||
| shouldEnd: (e) => { | |||||
| // avoid ending at other shapes on the node | |||||
| // if (e.target && e.target.get('name') !== 'anchor-point') return false; | |||||
| // if (!dragSourceNode || !e.item) return false; | |||||
| // 不允许连接自己 | |||||
| // if (dragSourceNode.getID() === e.item.getID()) return false; | |||||
| // 两个节点不允许多条边 | |||||
| if (hasEdge(dragSourceNode, e.item)) return false; | |||||
| // if (e.target) { | |||||
| // targetAnchorIdx = e.target.get('anchorPointIdx'); | |||||
| // e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle | |||||
| // return true; | |||||
| // } | |||||
| // targetAnchorIdx = undefined; | |||||
| return true; | |||||
| }, | |||||
| }, | |||||
| 'drag-canvas', | |||||
| 'zoom-canvas', | |||||
| ], | |||||
| }, | |||||
| defaultNode: { | |||||
| type: 'rect-node', | |||||
| size: [40, 40], | |||||
| labelCfg: { | |||||
| style: { | |||||
| fill: 'transparent', | |||||
| fontSize: 0, | |||||
| }, | |||||
| }, | |||||
| style: { | |||||
| fill: 'transparent', | |||||
| lineWidth: 0, | |||||
| }, | |||||
| }, | |||||
| defaultEdge: { | |||||
| // type: 'polyline', | |||||
| type: 'cubic', | |||||
| style: { | |||||
| endArrow: { | |||||
| // 设置终点箭头 | |||||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||||
| d: 4.5, | |||||
| fill: '#CDD0DC', | |||||
| }, | |||||
| cursor: 'pointer', | |||||
| lineWidth: 1, | |||||
| lineAppendWidth: 4, | |||||
| opacity: 1, | |||||
| stroke: '#CDD0DC', | |||||
| radius: 1, | |||||
| }, | |||||
| labelCfg: { | |||||
| autoRotate: true, | |||||
| style: { | |||||
| fontSize: 10, | |||||
| fill: 'rgba(22, 100, 255, 0.5)', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| linkCenter: true, | |||||
| }); | |||||
| // 修改历史数据样式问题 | |||||
| graph.node((node) => { | |||||
| return { | |||||
| style: { | |||||
| stroke: 'transparent', | |||||
| radius: 8, | |||||
| }, | |||||
| }; | |||||
| }); | |||||
| // 绑定事件 | |||||
| bindEvents(); | |||||
| }; | |||||
| // 绑定事件 | |||||
| const bindEvents = () => { | |||||
| graph.on('node:click', (e) => { | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| openNodeDrawer(e.item); | |||||
| } | |||||
| }); | |||||
| // 监听边的点击事件 | |||||
| graph.on('edge:click', (e) => { | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| openEdgeDrawer(e.item); | |||||
| } | |||||
| }); | |||||
| graph.on('aftercreateedge', (e) => { | |||||
| // update the sourceAnchor and targetAnchor for the newly added edge | |||||
| graph.updateItem(e.edge, { | |||||
| sourceAnchor: sourceAnchorIdx, | |||||
| targetAnchor: targetAnchorIdx, | |||||
| }); | |||||
| }); | |||||
| // 删除边时,修改 anchor-point 的 links 值 | |||||
| // graph.on('afterremoveitem', (e) => { | |||||
| // if (e.item && e.item.source && e.item.target) { | |||||
| // const { source, target, sourceAnchor, targetAnchor } = e.item; | |||||
| // const sourceNode = graph.findById(source); | |||||
| // const targetNode = graph.findById(target); | |||||
| // if (sourceNode && !isNaN(sourceAnchor)) { | |||||
| // const sourceAnchorShape = sourceNode | |||||
| // .getContainer() | |||||
| // .find( | |||||
| // (ele) => | |||||
| // ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor, | |||||
| // ); | |||||
| // sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1); | |||||
| // } | |||||
| // if (targetNode && !isNaN(targetAnchor)) { | |||||
| // const targetAnchorShape = targetNode | |||||
| // .getContainer() | |||||
| // .find( | |||||
| // (ele) => | |||||
| // ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor, | |||||
| // ); | |||||
| // // targetAnchorShape.set('links', targetAnchorShape.get('links') - 1); | |||||
| // } | |||||
| // } | |||||
| // }); | |||||
| // after drag on the first node, the edge is created, update the sourceAnchor | |||||
| graph.on('afteradditem', (e) => { | |||||
| const sourceAnchor = e.item.getModel().sourceAnchor; | |||||
| if (e.item && e.item.getType() === 'edge' && !sourceAnchor) { | |||||
| graph.updateItem(e.item, { | |||||
| sourceAnchor: sourceAnchorIdx, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| graph.on('node:mouseenter', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| }); | |||||
| graph.on('node:mouseleave', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| }); | |||||
| graph.on('node:dragstart', (e) => { | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| graph.setItemState(e.item, 'drag', true); | |||||
| }); | |||||
| graph.on('node:dragend', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| graph.setItemState(e.item, 'drag', false); | |||||
| }); | |||||
| graph.on('node:dragenter', (e) => { | |||||
| if (e.item?.getID() === dragSourceNode?.getID()) return; | |||||
| graph.setItemState(e.item, 'hover', true); | |||||
| if (e.target.get('name') === 'anchor-point') { | |||||
| dropAnchorIdx = e.target.get('anchorPointIdx'); | |||||
| graph.setItemState(e.item, 'drop', true); | |||||
| } else { | |||||
| graph.setItemState(e.item, 'drop', false); | |||||
| } | |||||
| }); | |||||
| graph.on('node:dragleave', (e) => { | |||||
| if (e.item?.getID() === dragSourceNode?.getID()) return; | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| graph.setItemState(e.item, 'drop', false); | |||||
| dropAnchorIdx = undefined; | |||||
| }); | |||||
| graph.on('node:drop', (e) => { | |||||
| graph.setItemState(e.item, 'hover', false); | |||||
| graph.setItemState(e.item, 'drop', false); | |||||
| dropAnchorIdx = undefined; | |||||
| }); | |||||
| graph.on('contextmenu', (e) => { | |||||
| e.preventDefault(); | |||||
| console.log(e); | |||||
| // 获取点击位置 | |||||
| const point = graph.getPointByClient(e.clientX, e.clientY); | |||||
| const contextMenu = document.getElementById('contextMenu'); | |||||
| // 显示右键菜单 | |||||
| contextMenu.style.display = 'block'; | |||||
| contextMenu.style.left = `${e.clientX - 250}px`; | |||||
| contextMenu.style.top = `${e.clientY}px`; | |||||
| if (e.item) { | |||||
| setContextFlag(true); | |||||
| } else { | |||||
| setContextFlag(false); | |||||
| } | |||||
| // 新增节点 | |||||
| document.getElementById('addNode').onclick = () => { | |||||
| const newNode = { | |||||
| id: `node-${Date.now()}`, | |||||
| label: '材料', | |||||
| x: point.x, | |||||
| y: point.y, | |||||
| properties: [ | |||||
| { | |||||
| name: '', | |||||
| isMultivalued: '', | |||||
| type: '', | |||||
| flag: false, | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| graph.addItem('node', newNode); | |||||
| graph.refresh(); | |||||
| contextMenu.style.display = 'none'; | |||||
| }; | |||||
| document.getElementById('cloneNode').onclick = () => { | |||||
| let data = graph.save(); | |||||
| const nodeId = s8(); | |||||
| data.nodes.push({ | |||||
| ...e.item.getModel(), | |||||
| label: e.item.getModel().label + '-copy', | |||||
| x: e.item.getModel().x + 150, | |||||
| y: e.item.getModel().y, | |||||
| id: e.item.getModel().component_name + '-' + nodeId, | |||||
| properties: e.item.getModel().properties || [ | |||||
| { | |||||
| name: '', | |||||
| isMultivalued: '', | |||||
| type: '', | |||||
| flag: false, | |||||
| }, | |||||
| ], | |||||
| }); | |||||
| graph.changeData(data); | |||||
| contextMenu.style.display = 'none'; | |||||
| }; | |||||
| document.getElementById('deleteNode').onclick = () => { | |||||
| graph.removeItem(e.item); | |||||
| contextMenu.style.display = 'none'; | |||||
| }; | |||||
| // 清空画布 | |||||
| // document.getElementById('clearCanvas').onclick = () => { | |||||
| // graph.clear(); | |||||
| // contextMenu.style.display = 'none'; | |||||
| // }; | |||||
| // 点击其他地方隐藏菜单 | |||||
| document.addEventListener('click', () => { | |||||
| contextMenu.style.display = 'none'; | |||||
| }); | |||||
| }); | |||||
| // 初始数据 | |||||
| const data = { | |||||
| nodes: [ | |||||
| { id: 'node1', x: 100, y: 100, label: '节点1' }, | |||||
| { id: 'node2', x: 300, y: 100, label: '节点2' }, | |||||
| ], | |||||
| edges: [{ source: 'node1', target: 'node2' }], | |||||
| }; | |||||
| }; | |||||
| // 上下文菜单 | |||||
| // const initMenu = () => { | |||||
| // const contextMenu = new G6.Menu({ | |||||
| // className: 'pipeline-context-menu', | |||||
| // getContent(evt) { | |||||
| // const type = evt.item.getType(); | |||||
| // const cloneDisplay = type === 'node' ? 'flex' : 'none'; | |||||
| // return ` | |||||
| // <div> | |||||
| // <div class="pipeline-context-menu__item" id="add"> | |||||
| // <svg class="pipeline-context-menu__item__icon" id="clone-svg"> | |||||
| // <use xlink:href="#icon-fuzhi1" /> | |||||
| // </svg> | |||||
| // 新建 | |||||
| // </div> | |||||
| // <div class="pipeline-context-menu__item" style="display: ${cloneDisplay}" id="clone"> | |||||
| // <svg class="pipeline-context-menu__item__icon" id="clone-svg"> | |||||
| // <use xlink:href="#icon-fuzhi1" /> | |||||
| // </svg> | |||||
| // 复制 | |||||
| // </div> | |||||
| // <div class="pipeline-context-menu__item" id="delete"> | |||||
| // <svg class="pipeline-context-menu__item__icon" id="delete-svg"> | |||||
| // <use xlink:href="#icon-shanchu1" /> | |||||
| // </svg> | |||||
| // 删除 | |||||
| // </div> | |||||
| // </div>`; | |||||
| // }, | |||||
| // handleMenuClick: (target, item) => { | |||||
| // const id = target.id; | |||||
| // if (id.startsWith('add')) { | |||||
| // addElement(item); | |||||
| // } | |||||
| // if (id.startsWith('clone')) { | |||||
| // cloneElement(item); | |||||
| // } else if (id.startsWith('delete')) { | |||||
| // graph.removeItem(item); | |||||
| // } | |||||
| // }, | |||||
| // // offsetX and offsetY include the padding of the parent container | |||||
| // // 需要加上父级容器的 padding-left 16 与自身偏移量 10 | |||||
| // offsetX: 16 + 10, | |||||
| // // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10 | |||||
| // offsetY: 0, | |||||
| // // the types of items that allow the menu show up | |||||
| // // 在哪些类型的元素上响应 | |||||
| // itemTypes: ['node', 'edge', 'canvas'], | |||||
| // }); | |||||
| // return contextMenu; | |||||
| // }; | |||||
| return ( | |||||
| <div className={styles['subjectView-container']}> | |||||
| {/* <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu> */} | |||||
| <div>{subjectName}</div> | |||||
| <Button type="link" size="small" onClick={() => updateCodeConfig()}> | |||||
| 修改主体 | |||||
| </Button> | |||||
| <div className={styles['subjectView-container__workflow']}> | |||||
| <div className={styles['subjectView-container__workflow__top']}> | |||||
| <Button type="link" size="small" onClick={() => savePipeline()}> | |||||
| 保存 | |||||
| </Button> | |||||
| <span>图例:</span> | |||||
| <div | |||||
| className={styles['subjectView-container__workflow__top__circle']} | |||||
| style={{ background: '#1664ff' }} | |||||
| ></div> | |||||
| <span style={{ color: '#1664ff', fontSize: '15px', marginRight: '20px' }}>XXXX</span> | |||||
| <div | |||||
| className={styles['subjectView-container__workflow__top__circle']} | |||||
| style={{ background: '#63a728' }} | |||||
| ></div> | |||||
| <span style={{ color: '#63a728', fontSize: '15px' }}>XXXX</span> | |||||
| {/* <Button | |||||
| type="default" | |||||
| icon={<KFIcon type="icon-quanjucanshu" />} | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={openParamsDrawer} | |||||
| > | |||||
| 全局参数 | |||||
| </Button> | |||||
| <Button | |||||
| type="primary" | |||||
| icon={<KFIcon type="icon-baocun" />} | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={() => { | |||||
| savePipeline(false); | |||||
| }} | |||||
| > | |||||
| 保存 | |||||
| </Button> | |||||
| <Button | |||||
| type="primary" | |||||
| style={{ | |||||
| border: '1px solid', | |||||
| borderColor: '#1664ff', | |||||
| background: '#fff', | |||||
| color: '#1664ff', | |||||
| }} | |||||
| icon={<KFIcon type="icon-baocunbingfanhui" />} | |||||
| onClick={() => { | |||||
| savePipeline(true); | |||||
| }} | |||||
| > | |||||
| 保存并返回 | |||||
| </Button> */} | |||||
| </div> | |||||
| <div className={styles['subjectView-container__workflow__graph']} ref={graphRef}></div> | |||||
| </div> | |||||
| <Props ref={propsRef} onFormChange={handleFormChange}></Props> | |||||
| <PropsEdge ref={propsEdgeRef} onFormChange={handleFormEdgeChange}></PropsEdge> | |||||
| <GlobalParamsDrawer | |||||
| ref={paramsDrawerRef} | |||||
| open={paramsDrawerOpen} | |||||
| globalParam={globalParam} | |||||
| onClose={closeParamsDrawer} | |||||
| ></GlobalParamsDrawer> | |||||
| {/* <EdgeParamsDrawer | |||||
| ref={edgeDrawerRef} | |||||
| open={edgeDrawerOpen} | |||||
| globalParam={globalParam} | |||||
| onClose={closeEdgeDrawer} | |||||
| ></EdgeParamsDrawer> */} | |||||
| <div className={styles['contextMenu']} id="contextMenu"> | |||||
| <ul> | |||||
| <li> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| disabled={contextFlag} | |||||
| id="addNode" | |||||
| // onClick={() => createMirror(record.id)} | |||||
| > | |||||
| 新增节点 | |||||
| </Button> | |||||
| </li> | |||||
| <li id="cloneNode"> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| disabled={!contextFlag} | |||||
| // onClick={() => createMirror(record.id)} | |||||
| > | |||||
| 复制节点 | |||||
| </Button> | |||||
| </li> | |||||
| <li id="deleteNode"> | |||||
| <Button | |||||
| type="link" | |||||
| disabled={!contextFlag} | |||||
| size="small" | |||||
| // onClick={() => createMirror(record.id)} | |||||
| > | |||||
| 删除节点 | |||||
| </Button> | |||||
| </li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default EditPipeline; | |||||
| @@ -0,0 +1,93 @@ | |||||
| .subjectView-container { | |||||
| display: flex; | |||||
| height: 100%; | |||||
| background-color: #fff; | |||||
| &__workflow { | |||||
| flex: 1 1 0; | |||||
| min-width: 0; | |||||
| height: 100%; | |||||
| &__top { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: end; | |||||
| width: 100%; | |||||
| height: 52px; | |||||
| padding: 0 20px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__circle{ | |||||
| width:8px; | |||||
| height:8px; | |||||
| border-radius: 50%; | |||||
| margin-right: 10px; | |||||
| } | |||||
| } | |||||
| &__graph { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| height: calc(100% - 52px); | |||||
| background-color: @background-color; | |||||
| background-image: url(@/assets/img/pipeline-canvas-bg.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| } | |||||
| .contextMenu { | |||||
| position: absolute; | |||||
| background: #fff; | |||||
| border: 1px solid #ccc; | |||||
| box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); | |||||
| padding: 10px 5px; | |||||
| display: none; | |||||
| border-radius: 10px; | |||||
| } | |||||
| .contextMenu ul { | |||||
| list-style: none; | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| } | |||||
| .contextMenu ul li { | |||||
| padding: 5px 5px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .contextMenu ul li:hover { | |||||
| background: #f0f0f0; | |||||
| } | |||||
| } | |||||
| :global { | |||||
| .pipeline-context-menu { | |||||
| width: 78px; | |||||
| padding: 10px 0; | |||||
| background: #ffffff; | |||||
| border-radius: 6px; | |||||
| box-shadow: 0px 0px 6px rgba(40, 84, 168, 0.21); | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 34px; | |||||
| padding-left: 12px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 15px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| color: #0d5ef8; | |||||
| font-weight: 500; | |||||
| background-color: .addAlpha(#8895a8, 0.11) []; | |||||
| } | |||||
| &__icon { | |||||
| width: 1em; | |||||
| height: 1em; | |||||
| margin-right: 9px; | |||||
| fill: currentColor; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,94 @@ | |||||
| import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { Graph, INode } from '@antv/g6'; | |||||
| import { type MenuProps } from 'antd'; | |||||
| // 找到节点所以的上游节点 | |||||
| export const findAllParentNodes = (graph: Graph, node: INode) => { | |||||
| const parentNodes: INode[] = []; | |||||
| let index = -1; | |||||
| let targetNode = node; | |||||
| while (targetNode) { | |||||
| const neighbors: INode[] = graph.getNeighbors(targetNode, 'source'); | |||||
| for (const sourceNode of neighbors) { | |||||
| // 避免重复,也避免循环 | |||||
| const idx = parentNodes.findIndex((item) => sourceNode.getID() === item.getID()); | |||||
| if (idx === -1 && sourceNode.getID() !== node.getID()) { | |||||
| parentNodes.push(sourceNode); | |||||
| } | |||||
| } | |||||
| targetNode = parentNodes[++index]; | |||||
| } | |||||
| return parentNodes; | |||||
| }; | |||||
| // 判断并找到全局参数第一个重复项,有重复项时,全局参数不允许保存 | |||||
| export function findFirstDuplicate(params: PipelineGlobalParam[]): string | null { | |||||
| const seen = new Set(); | |||||
| for (const item of params) { | |||||
| if (seen.has(item.param_name)) { | |||||
| return item.param_name; | |||||
| } | |||||
| seen.add(item.param_name); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 创建参数下拉菜单 | |||||
| export function createMenuItems( | |||||
| params: PipelineGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| ): MenuProps['items'] { | |||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | |||||
| const model = item.getModel(); | |||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | |||||
| return { | |||||
| key: model.id as string, | |||||
| label: model.label as string, | |||||
| children: outParametersList.map((key: string) => ({ | |||||
| key: key as string, | |||||
| label: out_parametersObj[key].label, | |||||
| })), | |||||
| }; | |||||
| }); | |||||
| if (params.length > 0) { | |||||
| return [ | |||||
| { | |||||
| key: 'global', | |||||
| label: '全局参数', | |||||
| children: params.map((item) => ({ | |||||
| key: item.param_name, | |||||
| label: item.param_name, | |||||
| })), | |||||
| }, | |||||
| ...nodes, | |||||
| ]; | |||||
| } else { | |||||
| return [...nodes]; | |||||
| } | |||||
| } | |||||
| export function getInParameterComponent( | |||||
| parameter: PipelineNodeModelParameter, | |||||
| ): React.ReactNode | null { | |||||
| if (parameter.value) { | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 判断是否允许输入 | |||||
| export function canInput(parameter: PipelineNodeModelParameter) { | |||||
| const { type, item_type } = parameter; | |||||
| return !( | |||||
| type === 'ref' && | |||||
| (item_type === 'dataset' || | |||||
| item_type === 'model' || | |||||
| item_type === 'image' || | |||||
| item_type === 'code') | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,228 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { addKnowledgeGraph, editKnowledgeGraph, getEntityGraph } from '@/services/graph'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, Select, message, type ModalProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| export enum VerifyMode { | |||||
| Password = 0, // 用户名密码 | |||||
| SSH = 1, // SSH Key | |||||
| } | |||||
| export enum OperationType { | |||||
| Create = 0, // 新建 | |||||
| Update = 1, // 更新 | |||||
| } | |||||
| type FormData = Partial<CodeConfigData>; | |||||
| interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| opType: OperationType; | |||||
| codeConfigData?: CodeConfigData; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) { | |||||
| const [form] = Form.useForm(); | |||||
| const [ontologyList, setOntologyList] = useState([]); | |||||
| const isPublic = Form.useWatch('code_repo_vis', form) === AvailableRange.Public; | |||||
| useEffect(() => { | |||||
| getEntityGraph().then((ret) => { | |||||
| setOntologyList(ret.data); | |||||
| }); | |||||
| }, []); | |||||
| // 创建 | |||||
| const createCodeConfig = async (formData: FormData) => { | |||||
| console.log(OperationType); | |||||
| const params: FormData & { id?: number } = { | |||||
| ...formData, | |||||
| }; | |||||
| // 清除多余的信息 | |||||
| // if (formData.code_repo_vis === AvailableRange.Public) { | |||||
| // omit(params, ['verify_mode', 'git_user_name', 'git_password', 'ssh_key']); | |||||
| // } | |||||
| // if (formData.verify_mode === VerifyMode.Password) { | |||||
| // omit(params, ['ssh_key']); | |||||
| // } else if (formData.verify_mode === VerifyMode.SSH) { | |||||
| // omit(params, ['git_user_name', 'git_password']); | |||||
| // } | |||||
| if (opType === OperationType.Update) { | |||||
| params.id = codeConfigData?.id; | |||||
| } | |||||
| const request = opType === OperationType.Create ? addKnowledgeGraph : editKnowledgeGraph; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success(opType === OperationType.Create ? '创建成功' : '修改成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: FormData) => { | |||||
| createCodeConfig(formData); | |||||
| }; | |||||
| // 设置初始值 | |||||
| const initialValues: FormData = codeConfigData ?? { | |||||
| code_repo_vis: AvailableRange.Public, | |||||
| verify_mode: VerifyMode.Password, | |||||
| }; | |||||
| if (initialValues.verify_mode === undefined || initialValues.verify_mode === null) { | |||||
| initialValues.verify_mode = VerifyMode.Password; | |||||
| } | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title={opType === OperationType.Create ? '创建图谱' : '修改图谱'} | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={initialValues} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="图谱名称" | |||||
| name="name" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入图谱名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入图谱名称" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="关联实体" | |||||
| name="ontologyId" | |||||
| rules={[ | |||||
| { | |||||
| required: false, | |||||
| message: '请选择关联实体', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| allowClear | |||||
| placeholder="请选择关联实体" | |||||
| options={ontologyList} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item | |||||
| noStyle | |||||
| shouldUpdate={(prevValues, currentValues) => | |||||
| prevValues?.code_repo_vis !== currentValues?.code_repo_vis | |||||
| } | |||||
| > | |||||
| {({ getFieldValue }) => { | |||||
| return getFieldValue('code_repo_vis') === AvailableRange.Private ? ( | |||||
| <> | |||||
| <Form.Item | |||||
| label="验证方式" | |||||
| name="verify_mode" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择验证方式', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={VerifyMode.Password}>用户名/密码</Radio> | |||||
| <Radio value={VerifyMode.SSH}>SSH Key</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| noStyle | |||||
| shouldUpdate={(prevValues, currentValues) => | |||||
| prevValues?.verify_mode !== currentValues?.verify_mode | |||||
| } | |||||
| > | |||||
| {({ getFieldValue }) => { | |||||
| return getFieldValue('verify_mode') === VerifyMode.Password ? ( | |||||
| <> | |||||
| <Form.Item | |||||
| label="Git 用户名" | |||||
| name="git_user_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入 Git 用户名', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入 Git 用户名" | |||||
| autoComplete="off" | |||||
| showCount | |||||
| allowClear | |||||
| maxLength={64} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="Git 密码" | |||||
| name="git_password" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入 Git 密码', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.Password | |||||
| autoComplete="new-password" | |||||
| placeholder="请输入 Git 密码" | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ) : ( | |||||
| <Form.Item | |||||
| label="SSH Key" | |||||
| name="ssh_key" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入 SSH Key', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入 SSH Key" | |||||
| showCount | |||||
| maxLength={4096} | |||||
| autoSize={{ minRows: 4, maxRows: 8 }} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </> | |||||
| ) : null; | |||||
| }} | |||||
| </Form.Item> */} | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddCodeConfigModal; | |||||
| @@ -0,0 +1,136 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { addKnowledgeSubject } from '@/services/subject'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message, type FormRule, type ModalProps } from 'antd'; | |||||
| import { useMemo, useState } from 'react'; | |||||
| export enum VerifyMode { | |||||
| Password = 0, // 用户名密码 | |||||
| SSH = 1, // SSH Key | |||||
| } | |||||
| export enum OperationType { | |||||
| Create = 0, // 新建 | |||||
| Update = 1, // 更新 | |||||
| } | |||||
| type FormData = Partial<CodeConfigData>; | |||||
| interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| opType: OperationType; | |||||
| codeConfigData?: CodeConfigData; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) { | |||||
| const [form] = Form.useForm(); | |||||
| const [typeList, setTypeList] = useState([]); | |||||
| const isPublic = Form.useWatch('code_repo_vis', form) === AvailableRange.Public; | |||||
| const urlExample = useMemo( | |||||
| () => | |||||
| isPublic | |||||
| ? 'https://gitlink.org.cn/ci4s/ci4sManagement-cloud.git' | |||||
| : 'git@code.gitlink.org.cn:ci4s/ci4sManagement-cloud.git', | |||||
| [isPublic], | |||||
| ); | |||||
| // /^(git@[\w.-]+:[\w./-]+\.git)$/ | |||||
| const urlRules: FormRule[] = useMemo( | |||||
| () => | |||||
| isPublic | |||||
| ? [ | |||||
| { | |||||
| type: 'url', | |||||
| message: '请输入正确的 Git 地址', | |||||
| }, | |||||
| ] | |||||
| : ([] as FormRule[]), | |||||
| [isPublic], | |||||
| ); | |||||
| // 创建 | |||||
| const createCodeConfig = async (formData: FormData) => { | |||||
| const params: FormData & { id?: number } = { | |||||
| ...formData, | |||||
| }; | |||||
| if (opType === OperationType.Update) { | |||||
| params.id = codeConfigData?.id; | |||||
| } | |||||
| const request = opType === OperationType.Create ? addKnowledgeSubject : ''; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success(opType === OperationType.Create ? '创建成功' : '修改成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: FormData) => { | |||||
| createCodeConfig(formData); | |||||
| }; | |||||
| // 设置初始值 | |||||
| const initialValues: FormData = codeConfigData ?? { | |||||
| code_repo_vis: AvailableRange.Public, | |||||
| verify_mode: VerifyMode.Password, | |||||
| }; | |||||
| if (initialValues.verify_mode === undefined || initialValues.verify_mode === null) { | |||||
| initialValues.verify_mode = VerifyMode.Password; | |||||
| } | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="创建主体" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={initialValues} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="主体名称" | |||||
| name="name" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入主体名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入主体名称" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="主体描述" | |||||
| name="description" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入主体描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入主体描述" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddCodeConfigModal; | |||||
| @@ -0,0 +1,161 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { getEntityGraph, postVersion } from '@/services/graph'; | |||||
| import { getFileListFromEvent } from '@/utils/ui'; | |||||
| import { Button, Form, message, Tabs, Upload, type ModalProps, type UploadProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import './index.less'; | |||||
| export enum VerifyMode { | |||||
| Password = 0, // 用户名密码 | |||||
| SSH = 1, // SSH Key | |||||
| } | |||||
| export enum OperationType { | |||||
| Create = 0, // 新建 | |||||
| Update = 1, // 更新 | |||||
| } | |||||
| type FormData = Partial<CodeConfigData>; | |||||
| interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| opType: OperationType; | |||||
| codeConfigData?: CodeConfigData; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) { | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: `CSV`, | |||||
| children: null, | |||||
| }, | |||||
| ]; | |||||
| const [form] = Form.useForm(); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const [ontologyList, setOntologyList] = useState([]); | |||||
| const isPublic = Form.useWatch('code_repo_vis', form) === AvailableRange.Public; | |||||
| useEffect(() => { | |||||
| getEntityGraph().then((ret) => { | |||||
| setOntologyList(ret.data); | |||||
| }); | |||||
| }, []); | |||||
| const [fileList, setFileList] = useState([]); | |||||
| const handleChange: UploadProps['onChange'] = (info) => { | |||||
| const { status } = info; | |||||
| if (status == 'done') { | |||||
| console.log(info); | |||||
| } | |||||
| }; | |||||
| // 上传前认证 | |||||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | |||||
| const fileList = form.getFieldValue('fileList'); | |||||
| if (Array.isArray(fileList) && fileList.length >= 1) { | |||||
| message.error('只允许上传一个文件'); | |||||
| return Upload.LIST_IGNORE; | |||||
| } | |||||
| return true; | |||||
| }; | |||||
| const uploadProps: UploadProps = { | |||||
| action: '/api//kg/version/upload', | |||||
| headers: { | |||||
| Authorization: getAccessToken() || '', | |||||
| }, | |||||
| maxCount: 1, | |||||
| defaultFileList: [], | |||||
| }; | |||||
| // 创建 | |||||
| // const createCodeConfig = async (formData: FormData) => { | |||||
| // console.log(OperationType); | |||||
| // const params: FormData & { id?: number } = { | |||||
| // ...formData, | |||||
| // }; | |||||
| // // 清除多余的信息 | |||||
| // // if (formData.code_repo_vis === AvailableRange.Public) { | |||||
| // // omit(params, ['verify_mode', 'git_user_name', 'git_password', 'ssh_key']); | |||||
| // // } | |||||
| // // if (formData.verify_mode === VerifyMode.Password) { | |||||
| // // omit(params, ['ssh_key']); | |||||
| // // } else if (formData.verify_mode === VerifyMode.SSH) { | |||||
| // // omit(params, ['git_user_name', 'git_password']); | |||||
| // // } | |||||
| // if (opType === OperationType.Update) { | |||||
| // params.id = codeConfigData?.id; | |||||
| // } | |||||
| // const request = opType === OperationType.Create ? addKnowledgeGraph : editKnowledgeGraph; | |||||
| // const [res] = await to(request(params)); | |||||
| // if (res) { | |||||
| // message.success(opType === OperationType.Create ? '增量更新成功' : '全量更新成功'); | |||||
| // onOk?.(); | |||||
| // } | |||||
| // }; | |||||
| // 提交 | |||||
| const onFinish = (formData: FormData) => { | |||||
| console.log(formData); | |||||
| postVersion({ | |||||
| path: formData.fileList[0].response.data, | |||||
| kgId: codeConfigData.id, | |||||
| }).then((ret) => { | |||||
| if (ret.code == 200) { | |||||
| message.success('导入成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }); | |||||
| }; | |||||
| // 设置初始值 | |||||
| const initialValues: FormData = codeConfigData ?? { | |||||
| code_repo_vis: AvailableRange.Public, | |||||
| verify_mode: VerifyMode.Password, | |||||
| }; | |||||
| if (initialValues.verify_mode === undefined || initialValues.verify_mode === null) { | |||||
| initialValues.verify_mode = VerifyMode.Password; | |||||
| } | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title={'导入'} | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| destroyOnClose | |||||
| > | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={initialValues} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| name="fileList" | |||||
| valuePropName="fileList" | |||||
| getValueFromEvent={getFileListFromEvent} | |||||
| required | |||||
| > | |||||
| <Upload {...uploadProps} beforeUpload={beforeUpload} fileList={fileList}> | |||||
| <Button type="link" style={{ paddingLeft: 0, paddingRight: 0 }}> | |||||
| 选择CSV文件 | |||||
| </Button> | |||||
| </Upload> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddCodeConfigModal; | |||||
| @@ -0,0 +1,108 @@ | |||||
| .code-config-item { | |||||
| position: relative; | |||||
| width: calc(25% - 15px); | |||||
| padding: 20px; | |||||
| background: linear-gradient(180deg, #f7faff 0%, #ffffff 100%); | |||||
| border: 2px solid white; | |||||
| border-radius: 4px; | |||||
| box-shadow: 0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| @media screen and (max-width: 1860px) { | |||||
| & { | |||||
| width: calc(33.33% - 13.33px); | |||||
| } | |||||
| } | |||||
| &__icon { | |||||
| flex: none; | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | |||||
| } | |||||
| &__name { | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| margin-bottom: 0 !important; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 16px; | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| &__tag { | |||||
| flex: none; | |||||
| padding: 1px 10px; | |||||
| font-size: 13px; | |||||
| border-radius: 2px; | |||||
| &--public { | |||||
| color: @primary-color; | |||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.5) []; | |||||
| } | |||||
| &--private { | |||||
| color: @warning-color; | |||||
| background-color: .addAlpha(@warning-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@warning-color, 0.5) []; | |||||
| } | |||||
| } | |||||
| :global { | |||||
| .ant-btn { | |||||
| flex: none; | |||||
| color: #808080; | |||||
| } | |||||
| } | |||||
| &__url-box { | |||||
| margin-bottom: 15px; | |||||
| padding: 14px; | |||||
| background-color: .addAlpha(@primary-color, 0.04) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| &__url { | |||||
| margin-bottom: 15px !important; | |||||
| color: @text-color; | |||||
| font-size: 14px; | |||||
| } | |||||
| &__branch { | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | |||||
| &__user, | |||||
| &__time { | |||||
| display: flex; | |||||
| flex: 0 1 content; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| color: #808080; | |||||
| font-size: 13px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,94 @@ | |||||
| import clock from '@/assets/img/clock.png'; | |||||
| import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { Button, Flex, Typography } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import styles from './index.less'; | |||||
| type CodeConfigItemProps = { | |||||
| item: CodeConfigData; | |||||
| onClick?: (item: CodeConfigData) => void; | |||||
| onEdit?: (item: CodeConfigData) => void; | |||||
| onRemove?: (item: CodeConfigData) => void; | |||||
| }; | |||||
| function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps) { | |||||
| return ( | |||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | |||||
| <img | |||||
| className={styles['code-config-item__icon']} | |||||
| src={require('@/assets/img/code-name-icon.png')} | |||||
| alt="" | |||||
| /> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| > | |||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| <div | |||||
| className={classNames( | |||||
| styles['code-config-item__tag'], | |||||
| item.code_repo_vis === AvailableRange.Public | |||||
| ? styles['code-config-item__tag--public'] | |||||
| : styles['code-config-item__tag--private'], | |||||
| )} | |||||
| > | |||||
| {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | |||||
| </div> | |||||
| <Button | |||||
| type="text" | |||||
| shape="circle" | |||||
| style={{ marginLeft: 'auto', marginRight: '4px' }} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onEdit?.(item); | |||||
| }} | |||||
| > | |||||
| <KFIcon type="icon-bianji" font={17} /> | |||||
| </Button> | |||||
| <Button | |||||
| type="text" | |||||
| shape="circle" | |||||
| style={{ marginRight: '-4px' }} | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| onRemove?.(item); | |||||
| }} | |||||
| > | |||||
| <KFIcon type="icon-shanchu" font={17} /> | |||||
| </Button> | |||||
| </Flex> | |||||
| <div className={styles['code-config-item__url-box']}> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| </div> | |||||
| <Flex justify="space-between"> | |||||
| <div className={styles['code-config-item__user']}> | |||||
| <img | |||||
| style={{ width: '16px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| <span>{item.create_by}</span> | |||||
| </div> | |||||
| <div className={styles['code-config-item__time']}> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" draggable={false} /> | |||||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | |||||
| </div> | |||||
| </Flex> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CodeConfigItem; | |||||
| @@ -0,0 +1,50 @@ | |||||
| .code-selector { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| &__search { | |||||
| width: 100%; | |||||
| } | |||||
| :global { | |||||
| .ant-input-affix-wrapper { | |||||
| border-radius: 23px !important; | |||||
| .ant-input-prefix { | |||||
| margin-inline-end: 12px; | |||||
| } | |||||
| .ant-input-suffix { | |||||
| margin-inline-end: 12px; | |||||
| } | |||||
| .ant-input-clear-icon { | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .ant-input-group-addon { | |||||
| display: none; | |||||
| } | |||||
| .ant-pagination { | |||||
| .ant-select-single { | |||||
| height: 32px !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__content { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| gap: 10px; | |||||
| width: 100%; | |||||
| max-height: 50vh; | |||||
| margin-top: 24px; | |||||
| margin-bottom: 30px; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| } | |||||
| &__empty { | |||||
| padding-top: 40px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,120 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-11 16:31:18 | |||||
| * @Description: 选择代码 | |||||
| */ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { getCodeConfigListReq } from '@/services/codeConfig'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Icon } from '@umijs/max'; | |||||
| import type { ModalProps, PaginationProps } from 'antd'; | |||||
| import { Empty, Input, Pagination } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import CodeConfigItem from '../CodeConfigItem'; | |||||
| import styles from './index.less'; | |||||
| export { type CodeConfigData }; | |||||
| export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| onOk?: (params: CodeConfigData | undefined) => void; | |||||
| } | |||||
| function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }); | |||||
| const [searchText, setSearchText] = useState<string | undefined>(undefined); | |||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | |||||
| useEffect(() => { | |||||
| getDataList(); | |||||
| }, [pagination, searchText]); | |||||
| // 获取数据请求 | |||||
| const getDataList = async () => { | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| code_repo_name: searchText !== '' ? searchText : undefined, | |||||
| }; | |||||
| const [res] = await to(getCodeConfigListReq(params)); | |||||
| if (res && res.data && res.data.content) { | |||||
| setDataList(res.data.content); | |||||
| setTotal(res.data.totalElements); | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const handleSearch = (value: string) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| const handleClick = (item: CodeConfigData) => { | |||||
| onOk?.(item); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { | |||||
| setPagination({ | |||||
| current: page, | |||||
| pageSize: pageSize, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="选择代码配置" | |||||
| image={require('@/assets/img/modal-code-config.png')} | |||||
| width={920} | |||||
| footer={null} | |||||
| destroyOnClose | |||||
| > | |||||
| <div className={styles['code-selector']}> | |||||
| <Input.Search | |||||
| className={styles['code-selector__search']} | |||||
| placeholder="按代码仓库名称筛选" | |||||
| allowClear | |||||
| onSearch={handleSearch} | |||||
| size="large" | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| suffix={null} | |||||
| value={inputText} | |||||
| prefix={ | |||||
| <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} /> | |||||
| } | |||||
| /> | |||||
| {dataList?.length !== 0 ? ( | |||||
| <> | |||||
| <div className={styles['code-selector__content']}> | |||||
| {dataList?.map((item) => ( | |||||
| <CodeConfigItem item={item} key={item.id} onClick={handleClick} /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| align="center" | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </> | |||||
| ) : ( | |||||
| <div className={styles['code-selector__empty']}> | |||||
| <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default CodeSelectorModal; | |||||
| @@ -0,0 +1,29 @@ | |||||
| .form-item { | |||||
| position: relative; | |||||
| padding-top: 40px; | |||||
| border-bottom: 1px dashed rgba(20, 49, 179, 0.12); | |||||
| &__delete-button { | |||||
| position: absolute; | |||||
| top: 5px; | |||||
| right: 24px; | |||||
| } | |||||
| :global { | |||||
| .anticon.anticon-question-circle { | |||||
| margin-top: -12px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .form-item-add { | |||||
| margin-top: 15px; | |||||
| &:first-child { | |||||
| margin-top: 0; | |||||
| } | |||||
| &__add-button { | |||||
| padding: 0; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,189 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | |||||
| import { type PipelineGlobalParam } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | |||||
| import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useImperativeHandle } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type GlobalParamsDrawerProps = { | |||||
| open: boolean; | |||||
| onClose: () => void; | |||||
| globalParam: PipelineGlobalParam[] | null; | |||||
| }; | |||||
| const GlobalParamsDrawer = forwardRef( | |||||
| ({ open = false, onClose, globalParam = [] }: GlobalParamsDrawerProps, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| useImperativeHandle(ref, () => ({ | |||||
| validateFields: async () => { | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| return values; | |||||
| } else { | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }, | |||||
| getFieldsValue: () => { | |||||
| return form.getFieldsValue(); | |||||
| }, | |||||
| })); | |||||
| // 处理参数类型变化 | |||||
| const handleTypeChange = (name: NamePath) => { | |||||
| form.setFieldValue(name, null); | |||||
| }; | |||||
| const removeParameter = (name: number, remove: (param: number) => void) => { | |||||
| modalConfirm({ | |||||
| title: '确认删除该参数吗?', | |||||
| onOk: () => { | |||||
| remove(name); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <Drawer | |||||
| rootStyle={{ marginTop: '45px' }} | |||||
| title="全局参数" | |||||
| placement="right" | |||||
| closeIcon={false} | |||||
| getContainer={false} | |||||
| onClose={onClose} | |||||
| open={open} | |||||
| width={520} | |||||
| > | |||||
| <Form | |||||
| name="global_params_form" | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| labelCol={{ span: 5 }} | |||||
| wrapperCol={{ span: 19 }} | |||||
| initialValues={{ global_param: globalParam }} | |||||
| labelAlign="left" | |||||
| > | |||||
| <Form.List name="global_param"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }) => ( | |||||
| <div key={key} className={styles['form-item']}> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_name']} | |||||
| label="参数名称" | |||||
| validateTrigger={[]} | |||||
| rules={[ | |||||
| { required: true, message: '请输入参数名称' }, | |||||
| { | |||||
| validator: (_, value) => { | |||||
| const list = form.getFieldValue('global_param') || []; | |||||
| const names = list.filter((item: any) => item?.param_name === value); | |||||
| if (value && names.length > 1) { | |||||
| return Promise.reject('参数名称不能重复'); | |||||
| } else { | |||||
| return Promise.resolve(); | |||||
| } | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入参数名称" | |||||
| allowClear | |||||
| onBlur={() => form.validateFields()} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'description']} | |||||
| label="参数描述" | |||||
| rules={[{ required: true, message: '请输入参数描述' }]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入参数描述" | |||||
| autoSize={{ minRows: 2, maxRows: 4 }} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_type']} | |||||
| label="类 型" | |||||
| rules={[{ required: true, message: '请选择类型' }]} | |||||
| > | |||||
| <Radio.Group | |||||
| onChange={() => handleTypeChange(['global_param', name, 'param_value'])} | |||||
| > | |||||
| <Radio value={1}>字符串</Radio> | |||||
| <Radio value={2}>整型</Radio> | |||||
| <Radio value={3}>布尔类型</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| noStyle | |||||
| shouldUpdate={(prev, cur) => | |||||
| prev.global_param?.[name]?.param_type !== | |||||
| cur.global_param?.[name]?.param_type | |||||
| } | |||||
| > | |||||
| {({ getFieldValue }) => ( | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'param_value']} | |||||
| label="值" | |||||
| rules={getParamRules( | |||||
| getFieldValue(['global_param', name, 'param_type']), | |||||
| true, | |||||
| )} | |||||
| > | |||||
| {getParamComponent(getFieldValue(['global_param', name, 'param_type']))} | |||||
| </Form.Item> | |||||
| )} | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'is_sensitive']} | |||||
| label="脱敏显示" | |||||
| rules={[{ required: true, message: '请选择' }]} | |||||
| tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示" | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={1}>是</Radio> | |||||
| <Radio value={0}>否</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Tooltip title="删除参数"> | |||||
| <Button | |||||
| className={styles['form-item__delete-button']} | |||||
| type="link" | |||||
| onClick={() => removeParameter(name, remove)} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| ></Button> | |||||
| </Tooltip> | |||||
| </div> | |||||
| ))} | |||||
| <Form.Item className={styles['form-item-add']}> | |||||
| <Button | |||||
| className={styles['form-item-add__add-button']} | |||||
| type="link" | |||||
| onClick={() => add()} | |||||
| icon={<PlusOutlined />} | |||||
| > | |||||
| 流水线参数 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form> | |||||
| </Drawer> | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| export default GlobalParamsDrawer; | |||||
| @@ -0,0 +1,61 @@ | |||||
| .pipeline-drawer { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| .ant-tabs-nav .ant-tabs-nav-list{ | |||||
| width: 100%; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .ant-tabs .ant-tabs-tab{ | |||||
| padding: 12px 20px 12px 24px; | |||||
| margin: 0; | |||||
| } | |||||
| .ant-tabs .ant-tabs-ink-bar{ | |||||
| background-color: #1664ff; | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| .graph-drawer-box{ | |||||
| padding: 12px 20px; | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| &__slice{ | |||||
| width: 100%; | |||||
| height:0px; | |||||
| border:1px solid; | |||||
| border-color:rgba(20, 49, 179, 0.12); | |||||
| margin: 5px 0 20px 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,382 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { requiredValidator } from '@/components/ParameterInput'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { createMenuItems } from '@/pages/Pipeline/Info/utils'; | |||||
| import { | |||||
| PipelineGlobalParam, | |||||
| PipelineNodeModel, | |||||
| PipelineNodeModelParameter, | |||||
| PipelineNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { INode } from '@antv/g6'; | |||||
| import { Drawer, Form, Input, MenuProps, Tabs } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import CodeSelectorModal from '../CodeSelectorModal'; | |||||
| import PropsLabel from '../PropsLabel'; | |||||
| import ResourceSelectorModal, { | |||||
| ResourceSelectorType, | |||||
| selectorTypeConfig, | |||||
| } from '../ResourceSelectorModal'; | |||||
| import styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| type PipelineNodeParameterProps = { | |||||
| onFormChange: (data: PipelineNodeModelSerialize) => void; | |||||
| }; | |||||
| const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParameterProps, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | |||||
| {} as PipelineNodeModelSerialize, | |||||
| ); | |||||
| const [open, setOpen] = useState(false); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | |||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: `属性`, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: `条件过滤`, | |||||
| children: null, | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: `多条过滤`, | |||||
| children: null, | |||||
| }, | |||||
| ]; | |||||
| const afterOpenChange = async () => { | |||||
| if (!open) { | |||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| const [_values, error] = await to(form.validateFields()); | |||||
| const fields = form.getFieldsValue(); | |||||
| const control_strategy = JSON.stringify(fields.control_strategy); | |||||
| const in_parameters = JSON.stringify(fields.in_parameters); | |||||
| const out_parameters = JSON.stringify(fields.out_parameters); | |||||
| // console.log('getFieldsValue', fields); | |||||
| const res = { | |||||
| ...stagingItem, | |||||
| ...fields, | |||||
| control_strategy: control_strategy, | |||||
| in_parameters: in_parameters, | |||||
| out_parameters: out_parameters, | |||||
| formError: !!error, | |||||
| }; | |||||
| console.log('res', res); | |||||
| onFormChange(res); | |||||
| } | |||||
| }; | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer( | |||||
| model: PipelineNodeModel, | |||||
| params: PipelineGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| validate: boolean = false, | |||||
| ) { | |||||
| try { | |||||
| const nodeData: PipelineNodeModelSerialize = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| // console.log('model', nodeData); | |||||
| setStagingItem({ | |||||
| ...nodeData, | |||||
| }); | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | |||||
| if (validate) { | |||||
| form.validateFields(); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('JSON.parse error: ', error); | |||||
| } | |||||
| setOpen(true); | |||||
| // 参数下拉菜单 | |||||
| setMenuItems(createMenuItems(params, parentNodes)); | |||||
| }, | |||||
| close: () => { | |||||
| onClose(); | |||||
| }, | |||||
| validateFields: async () => { | |||||
| if (!open) { | |||||
| return; | |||||
| } | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| return values; | |||||
| } else { | |||||
| form.scrollToField((error as any)?.errorFields?.[0]?.name, { block: 'center' }); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }, | |||||
| })); | |||||
| // ref 类型选择 | |||||
| const selectRefData = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| if (item.item_type === 'code') { | |||||
| selectCodeConfig(formItemName, item); | |||||
| } else { | |||||
| selectResource(formItemName, item); | |||||
| } | |||||
| }; | |||||
| // 选择代码配置 | |||||
| const selectCodeConfig = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| const value = JSON.stringify({ | |||||
| id: res.id, | |||||
| name: res.code_repo_name, | |||||
| code_path: res.git_url, | |||||
| branch: res.git_branch, | |||||
| username: res.git_user_name, | |||||
| password: res.git_password, | |||||
| ssh_private_key: res.ssh_key, | |||||
| }); | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value, | |||||
| showValue: res.code_repo_name, | |||||
| fromSelect: true, | |||||
| }); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 选择数据集、模型、镜像 | |||||
| const selectResource = ( | |||||
| formItemName: NamePath, | |||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||||
| ) => { | |||||
| let type: ResourceSelectorType; | |||||
| switch (item.item_type) { | |||||
| case 'dataset': | |||||
| type = ResourceSelectorType.Dataset; | |||||
| break; | |||||
| case 'model': | |||||
| type = ResourceSelectorType.Model; | |||||
| break; | |||||
| default: | |||||
| type = ResourceSelectorType.Mirror; | |||||
| break; | |||||
| } | |||||
| const fieldValue = form.getFieldValue(formItemName); | |||||
| const activeTab = fieldValue?.activeTab as CommonTabKeys | undefined; | |||||
| const expandedKeys = Array.isArray(fieldValue?.expandedKeys) ? fieldValue?.expandedKeys : []; | |||||
| const checkedKeys = Array.isArray(fieldValue?.checkedKeys) ? fieldValue?.checkedKeys : []; | |||||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||||
| type, | |||||
| defaultExpandedKeys: expandedKeys, | |||||
| defaultCheckedKeys: checkedKeys, | |||||
| defaultActiveTab: activeTab, | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| const { activeTab, id, version, path } = res; | |||||
| if (formItemName === 'image') { | |||||
| // 单独的选择镜像 | |||||
| form.setFieldValue(formItemName, path); | |||||
| } else { | |||||
| // 输入参数选择镜像 | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: path, | |||||
| showValue: path, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | |||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| const value = JSON.stringify({ | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| }); | |||||
| const showValue = `${name}:${version}`; | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value, | |||||
| showValue, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | |||||
| if (type === ResourceSelectorType.Mirror && formItemName === 'image') { | |||||
| form.setFieldValue(formItemName, undefined); | |||||
| } else { | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: [], | |||||
| checkedKeys: [], | |||||
| }); | |||||
| } | |||||
| } | |||||
| form.validateFields([formItemName]); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||||
| const type = item.item_type; | |||||
| if (type === 'code') { | |||||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||||
| } | |||||
| let selectorType: ResourceSelectorType; | |||||
| if (type === 'dataset') { | |||||
| selectorType = ResourceSelectorType.Dataset; | |||||
| } else if (type === 'model') { | |||||
| selectorType = ResourceSelectorType.Model; | |||||
| } else { | |||||
| selectorType = ResourceSelectorType.Mirror; | |||||
| } | |||||
| return <KFIcon type={selectorTypeConfig[selectorType].buttonIcon} />; | |||||
| }; | |||||
| // 参数回填 | |||||
| const handleParameterClick = (name: NamePath, value: any) => { | |||||
| form.setFieldValue(name, value); | |||||
| }; | |||||
| // form item label | |||||
| const getLabel = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| namePrefix: string, | |||||
| ) => { | |||||
| return item.value.type === 'select' ? ( | |||||
| item.value.label + '(' + item.key + ')' | |||||
| ) : ( | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick([namePrefix, item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| // 必填项校验规则 | |||||
| const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => { | |||||
| return item.value.require | |||||
| ? [ | |||||
| { | |||||
| validator: requiredValidator, | |||||
| message: '必填项', | |||||
| }, | |||||
| ] | |||||
| : []; | |||||
| }; | |||||
| // 控制策略 | |||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| // 输入参数 | |||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| // 输出参数 | |||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| return ( | |||||
| <Drawer | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '52px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={358} | |||||
| className={styles['pipeline-drawer']} | |||||
| > | |||||
| <Tabs | |||||
| style={{ width: '100%' }} | |||||
| activeKey={activeTab} | |||||
| items={items} | |||||
| onChange={(key) => setActiveTab(key)} | |||||
| ></Tabs> | |||||
| <div className={styles['graph-drawer-box']}> | |||||
| <div style={{ marginBottom: '15px' }}> | |||||
| <span>ID:</span> | |||||
| <span style={{ marginLeft: '10px' }}>9855312121212121212</span> | |||||
| </div> | |||||
| <div style={{ marginBottom: '15px' }}> | |||||
| <span>Label:</span> | |||||
| <span style={{ marginLeft: '10px' }}>9855312121212121212</span> | |||||
| </div> | |||||
| <div className={styles['graph-drawer-box__slice']}></div> | |||||
| <div style={{ marginBottom: '15px' }}> | |||||
| <span>Name:</span> | |||||
| <span style={{ marginLeft: '10px' }}>小时一西</span> | |||||
| </div> | |||||
| <div style={{ marginBottom: '15px' }}> | |||||
| <span>XXX时间:</span> | |||||
| <span style={{ marginLeft: '10px' }}>15: 10: 205分26秒</span> | |||||
| </div> | |||||
| <div style={{ marginBottom: '15px' }}> | |||||
| <span>XXX:</span> | |||||
| <span style={{ marginLeft: '10px' }}>15: 10: 205分26秒</span> | |||||
| </div> | |||||
| </div> | |||||
| </Drawer> | |||||
| ); | |||||
| }); | |||||
| export default PipelineNodeParameter; | |||||
| @@ -0,0 +1,54 @@ | |||||
| .collapse { | |||||
| flex: none; | |||||
| // width: 250px; | |||||
| height: 100%; | |||||
| padding: 20px 0 0 15px; | |||||
| :global { | |||||
| .ant-collapse { | |||||
| height: calc(100% - 60px); | |||||
| overflow-y: auto; | |||||
| background-color: #fff; | |||||
| border-color: transparent !important; | |||||
| } | |||||
| .ant-collapse > .ant-collapse-item > .ant-collapse-header { | |||||
| margin-bottom: 5px; | |||||
| padding: 20px 16px 15px 16px; | |||||
| background-color: #fff; | |||||
| border-color: transparent; | |||||
| } | |||||
| .ant-collapse > .ant-collapse-item { | |||||
| margin: 0 10px; | |||||
| border-bottom: 0.5px dashed rgba(20, 49, 179, 0.12); | |||||
| border-radius: 0px; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content { | |||||
| padding-bottom: 15px; | |||||
| border-top: 1px solid transparent; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content > .ant-collapse-content-box { | |||||
| padding: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .collapseItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 40px; | |||||
| padding: 0 16px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| &:hover { | |||||
| color: @primary-color; | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| } | |||||
| } | |||||
| .modelMenusTitle { | |||||
| margin-bottom: 10px; | |||||
| padding: 12px 25px; | |||||
| color: #111111; | |||||
| font-size: 16px; | |||||
| } | |||||
| @@ -0,0 +1,108 @@ | |||||
| import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { PipelineNodeModel } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Button, Collapse, type CollapseProps, Input } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import Styles from './index.less'; | |||||
| type ModelMenuData = { | |||||
| key: string; | |||||
| name: string; | |||||
| value: PipelineNodeModel[]; | |||||
| }; | |||||
| type ModelMenuProps = { | |||||
| onComponentDragEnd: ( | |||||
| data: PipelineNodeModel & { x: number; y: number; label: string; img: string }, | |||||
| ) => void; | |||||
| }; | |||||
| const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => { | |||||
| const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]); | |||||
| const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]); | |||||
| useEffect(() => { | |||||
| getAllComponents(); | |||||
| }, []); | |||||
| // 获取所有组件 | |||||
| const getAllComponents = async () => { | |||||
| const [res] = await to(getComponentAll()); | |||||
| if (res && res.data) { | |||||
| const menus = res.data as ModelMenuData[]; | |||||
| setModelMenusList(menus); | |||||
| const items = menus.map((item) => { | |||||
| return { | |||||
| key: item.key, | |||||
| label: item.name, | |||||
| children: item.value.map((ele) => { | |||||
| return ( | |||||
| <div | |||||
| key={ele.id} | |||||
| draggable="true" | |||||
| onDragEnd={(e) => { | |||||
| dragEnd(e, ele); | |||||
| }} | |||||
| className={Styles.collapseItem} | |||||
| > | |||||
| {ele.icon_path && ( | |||||
| <img | |||||
| style={{ height: '16px', marginRight: '15px' }} | |||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| )} | |||||
| {ele.component_label} | |||||
| </div> | |||||
| ); | |||||
| }), | |||||
| }; | |||||
| }); | |||||
| setCollapseItems(items); | |||||
| } | |||||
| }; | |||||
| const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => { | |||||
| onComponentDragEnd({ | |||||
| ...data, | |||||
| x: e.clientX, | |||||
| y: e.clientY, | |||||
| label: data.component_label, | |||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| }); | |||||
| }; | |||||
| const defaultActiveKey = modelMenusList.map((item) => item.key + ''); | |||||
| return ( | |||||
| <div className={Styles.collapse}> | |||||
| <Input.Search | |||||
| placeholder="按XXXX筛选" | |||||
| allowClear | |||||
| // onSearch={onSearch} | |||||
| // onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| // value={inputText} | |||||
| /> | |||||
| <Button | |||||
| key="info" | |||||
| type="primary" | |||||
| style={{ marginLeft: '20px' }} | |||||
| // onClick={() => toDetail(record)} | |||||
| > | |||||
| 查询 | |||||
| </Button> | |||||
| {/* <div className={Styles.modelMenusTitle}>组件库</div> */} | |||||
| {/* 这样 defaultActiveKey 才能生效 */} | |||||
| {modelMenusList.length > 0 ? ( | |||||
| <Collapse | |||||
| collapsible="header" | |||||
| expandIconPosition="end" | |||||
| defaultActiveKey={defaultActiveKey} | |||||
| items={collapseItems} | |||||
| ></Collapse> | |||||
| ) : null} | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ModelMenu; | |||||
| @@ -0,0 +1,6 @@ | |||||
| .props-label { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| import { Dropdown, type MenuProps } from 'antd'; | |||||
| import { useEffect } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type PropsLabelProps = { | |||||
| title: string; | |||||
| menuItems: MenuProps['items']; | |||||
| onClick?: (key: string) => void; | |||||
| }; | |||||
| function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) { | |||||
| useEffect(() => {}, []); | |||||
| const handleItemClick: MenuProps['onClick'] = (e) => { | |||||
| const keyPath = e.keyPath.reverse(); | |||||
| if (keyPath[0] === 'global') { | |||||
| onClick?.(`\${${e.key}}`); | |||||
| } else { | |||||
| onClick?.(`{{${keyPath.join('.')}}}`); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['props-label']}> | |||||
| <div>{title}</div> | |||||
| {menuItems && menuItems.length > 0 && ( | |||||
| <Dropdown | |||||
| menu={{ | |||||
| items: menuItems, | |||||
| onClick: handleItemClick, | |||||
| triggerSubMenuAction: 'hover', | |||||
| }} | |||||
| trigger={['click']} | |||||
| placement="topRight" | |||||
| arrow | |||||
| > | |||||
| <a onClick={(e) => e.preventDefault()}>参数</a> | |||||
| </Dropdown> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default PropsLabel; | |||||
| @@ -0,0 +1,251 @@ | |||||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | |||||
| import modelImg from '@/assets/img/modal-select-model.png'; | |||||
| import { AvailableRange, CommonTabKeys } from '@/enums'; | |||||
| import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { MirrorVersionData } from '@/pages/Mirror/Info'; | |||||
| import { MirrorData } from '@/pages/Mirror/List'; | |||||
| import { | |||||
| getDatasetInfo, | |||||
| getDatasetList, | |||||
| getDatasetVersionList, | |||||
| getModelInfo, | |||||
| getModelList, | |||||
| getModelVersionList, | |||||
| } from '@/services/dataset/index.js'; | |||||
| import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | |||||
| import type { TabsProps, TreeDataNode } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| export enum ResourceSelectorType { | |||||
| Model = 'Model', // 模型 | |||||
| Dataset = 'Dataset', // 数据集 | |||||
| Mirror = 'Mirror', //镜像 | |||||
| } | |||||
| // 数据集、模型列表转为树形结构 | |||||
| const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | |||||
| ...v, | |||||
| key: `${v.id}`, | |||||
| title: v.name, | |||||
| isLeaf: false, | |||||
| checkable: false, | |||||
| })); | |||||
| }; | |||||
| // 镜像列表转为树形结构 | |||||
| const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | |||||
| key: `${v.id}`, | |||||
| title: v.name, | |||||
| isLeaf: false, | |||||
| checkable: false, | |||||
| })); | |||||
| }; | |||||
| // 数据集版本列表转为树形结构 | |||||
| const convertDatasetVersionToTreeData = ( | |||||
| parentId: string, | |||||
| info: ResourceData, | |||||
| list: ResourceVersionData[], | |||||
| ): TreeDataNode[] => { | |||||
| return list.map((item: ResourceVersionData) => ({ | |||||
| ...pick(info, ['id', 'name', 'owner', 'identifier']), | |||||
| version: item.name, | |||||
| title: item.name, | |||||
| key: `${parentId}-${item.name}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| }; | |||||
| // 镜像版本列表转为树形结构 | |||||
| const convertMirrorVersionToTreeData = ( | |||||
| parentId: string, | |||||
| list: MirrorVersionData[], | |||||
| ): TreeDataNode[] => { | |||||
| return list.map((item: MirrorVersionData) => ({ | |||||
| url: item.url, | |||||
| title: item.tag_name, | |||||
| key: `${parentId}-${item.id}`, | |||||
| isLeaf: true, | |||||
| checkable: true, | |||||
| })); | |||||
| }; | |||||
| interface SelectorTypeInfo { | |||||
| getList: (isPublic: boolean) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源版本列表 | |||||
| getFiles: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源文件列表 | |||||
| readonly modalIcon: string; // modal icon | |||||
| readonly buttonIcon: string; // button icon | |||||
| readonly name: string; // 名称 | |||||
| readonly tabItems: TabsProps['items']; // tab 列表 | |||||
| readonly buttontTitle: string; // 按钮 title | |||||
| } | |||||
| export class DatasetSelector implements SelectorTypeInfo { | |||||
| readonly name = '数据集'; | |||||
| readonly modalIcon = datasetImg; | |||||
| readonly buttonIcon = 'icon-xuanzeshujuji'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的数据集', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开数据集', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择数据集'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertDatasetToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(parentKey: string, parentNode: ResourceData) { | |||||
| const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier'])); | |||||
| if (res && res.data) { | |||||
| const list = res.data; | |||||
| return convertDatasetVersionToTreeData(parentKey, parentNode, list); | |||||
| } else { | |||||
| return Promise.reject('获取数据集版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||||
| const res = await getDatasetInfo(params); | |||||
| if (res && res.data) { | |||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.dataset_version_vos || []; | |||||
| return { | |||||
| path, | |||||
| content: list, | |||||
| }; | |||||
| } else { | |||||
| return Promise.reject('获取数据集文件列表失败'); | |||||
| } | |||||
| } | |||||
| } | |||||
| export class ModelSelector implements SelectorTypeInfo { | |||||
| readonly name = '模型'; | |||||
| readonly modalIcon = modelImg; | |||||
| readonly buttonIcon = 'icon-xuanzemoxing'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的模型', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开模型', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择模型'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertDatasetToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取模型列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(key: string, parentNode: ResourceData) { | |||||
| const res = await getModelVersionList(pick(parentNode, ['owner', 'identifier'])); | |||||
| if (res && res.data) { | |||||
| const list = res.data; | |||||
| return convertDatasetVersionToTreeData(key, parentNode, list); | |||||
| } else { | |||||
| return Promise.reject('获取模型版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); | |||||
| const res = await getModelInfo(params); | |||||
| if (res && res.data) { | |||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.model_version_vos || []; | |||||
| return { | |||||
| path, | |||||
| content: list, | |||||
| }; | |||||
| } else { | |||||
| return Promise.reject('获取模型文件列表失败'); | |||||
| } | |||||
| } | |||||
| } | |||||
| export class MirrorSelector implements SelectorTypeInfo { | |||||
| readonly name = '镜像'; | |||||
| readonly modalIcon = mirrorImg; | |||||
| readonly buttonIcon = 'icon-xuanzejingxiang'; | |||||
| readonly tabItems = [ | |||||
| { | |||||
| key: CommonTabKeys.Private, | |||||
| label: '我的镜像', | |||||
| }, | |||||
| { | |||||
| key: CommonTabKeys.Public, | |||||
| label: '公开镜像', | |||||
| }, | |||||
| ]; | |||||
| readonly buttontTitle = '选择镜像'; | |||||
| async getList(isPublic: boolean) { | |||||
| const res = await getMirrorListReq({ | |||||
| image_type: isPublic ? AvailableRange.Public : AvailableRange.Private, | |||||
| page: 0, | |||||
| size: 2000, | |||||
| }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertMirrorToTreeData(list); | |||||
| } else { | |||||
| return Promise.reject('获取镜像列表失败'); | |||||
| } | |||||
| } | |||||
| async getVersions(parentKey: string) { | |||||
| const res = await getMirrorVersionListReq({ | |||||
| image_id: parentKey, | |||||
| page: 0, | |||||
| size: 2000, | |||||
| }); | |||||
| if (res && res.data) { | |||||
| const list = res.data.content || []; | |||||
| return convertMirrorVersionToTreeData(parentKey, list); | |||||
| } else { | |||||
| return Promise.reject('获取镜像版本列表失败'); | |||||
| } | |||||
| } | |||||
| async getFiles(_parentKey: string, parentNode: MirrorVersionData) { | |||||
| const { url } = parentNode; | |||||
| return { | |||||
| path: url, | |||||
| content: [ | |||||
| { | |||||
| url: url, | |||||
| file_name: `${url}`, | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| } | |||||
| } | |||||
| export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = { | |||||
| [ResourceSelectorType.Model]: new ModelSelector(), | |||||
| [ResourceSelectorType.Dataset]: new DatasetSelector(), | |||||
| [ResourceSelectorType.Mirror]: new MirrorSelector(), | |||||
| }; | |||||
| @@ -0,0 +1,76 @@ | |||||
| .model-tabs { | |||||
| margin-left: 8px; | |||||
| :global { | |||||
| .ant-tabs-tab { | |||||
| padding-top: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .model-selector { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| :global { | |||||
| .ant-input-affix-wrapper .ant-input-prefix { | |||||
| margin-inline-end: 12px; | |||||
| } | |||||
| } | |||||
| &__left { | |||||
| width: 488px; | |||||
| height: 398px; | |||||
| margin-right: 15px; | |||||
| padding: 15px; | |||||
| background-color: @background-color-primary; | |||||
| border: 1px solid @border-color; | |||||
| border-radius: 8px; | |||||
| &__search { | |||||
| margin-bottom: 14px; | |||||
| padding-left: 0; | |||||
| background-color: transparent; | |||||
| border-width: 0; | |||||
| border-bottom: 1px solid @border-color-secondary; | |||||
| border-radius: 0; | |||||
| } | |||||
| &__tree-title { | |||||
| display: inline-block; | |||||
| .singleLine(); | |||||
| } | |||||
| } | |||||
| &__right { | |||||
| width: calc(100% - 488px - 15px); | |||||
| height: 398px; | |||||
| padding: 15px; | |||||
| background-color: @background-color-primary; | |||||
| border: 1px solid @border-color; | |||||
| border-radius: 8px; | |||||
| &__title { | |||||
| height: 46px; | |||||
| margin-bottom: 15px; | |||||
| padding: 3px 0 6px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 46px; | |||||
| border-bottom: 1px solid @border-color-secondary; | |||||
| } | |||||
| &__files { | |||||
| height: calc(100% - 75px); | |||||
| overflow-y: auto; | |||||
| &__file { | |||||
| margin-bottom: 10px; | |||||
| padding: 3px 10px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | |||||
| word-break: break-all; | |||||
| background: @background-color-gray; | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,314 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-11 16:31:18 | |||||
| * @Description: 选择数据集、模型、镜像 | |||||
| */ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { ResourceFileData } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Icon } from '@umijs/max'; | |||||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | |||||
| import { Input, Tabs, Tree } from 'antd'; | |||||
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | |||||
| import { ResourceSelectorType, selectorTypeConfig } from './config'; | |||||
| import styles from './index.less'; | |||||
| export { ResourceSelectorType, selectorTypeConfig }; | |||||
| // 选择数据集\模型\镜像的返回类型 | |||||
| export type ResourceSelectorResponse = { | |||||
| id: string; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| identifier: string; // 数据集\模型 identifier | |||||
| owner: string; // 数据集\模型 owner | |||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | |||||
| }; | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 数据集\模型\镜像 | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | undefined) => void; | |||||
| } | |||||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | |||||
| // 更新树形结构的 children | |||||
| const updateChildren = (parentId: string, children: TreeDataNode[]) => { | |||||
| return (node: TreeDataNode) => { | |||||
| if (node.key === parentId) { | |||||
| return { | |||||
| ...node, | |||||
| children, | |||||
| }; | |||||
| } | |||||
| return node; | |||||
| }; | |||||
| }; | |||||
| // 得到数据集\模型\镜像 id 和下属版本号 | |||||
| const getIdAndVersion = (versionKey: string) => { | |||||
| const index = versionKey.indexOf('-'); | |||||
| const id = versionKey.slice(0, index); | |||||
| const version = versionKey.slice(index + 1); | |||||
| return { | |||||
| id, | |||||
| version, | |||||
| }; | |||||
| }; | |||||
| function ResourceSelectorModal({ | |||||
| type, | |||||
| defaultExpandedKeys = [], | |||||
| defaultCheckedKeys = [], | |||||
| defaultActiveTab = CommonTabKeys.Private, | |||||
| onOk, | |||||
| ...rest | |||||
| }: ResourceSelectorModalProps) { | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultActiveTab); | |||||
| const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); | |||||
| const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]); | |||||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | |||||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | |||||
| const [files, setFiles] = useState<ResourceFileData[]>([]); | |||||
| const [versionPath, setVersionPath] = useState(''); | |||||
| const [searchText, setSearchText] = useState(''); | |||||
| const [firstLoadList, setFirstLoadList] = useState(false); | |||||
| const [firstLoadVersions, setFirstLoadVersions] = useState(false); | |||||
| const treeRef = useRef<TreeRef>(null); | |||||
| const config = selectorTypeConfig[type]; | |||||
| useEffect(() => { | |||||
| setExpandedKeys([]); | |||||
| setCheckedKeys([]); | |||||
| setLoadedKeys([]); | |||||
| setFiles([]); | |||||
| setVersionPath(''); | |||||
| setSearchText(''); | |||||
| getTreeData(); | |||||
| }, [activeTab, type]); | |||||
| const treeData = useMemo( | |||||
| () => | |||||
| originTreeData.filter((v) => | |||||
| (v.title as string).toLowerCase()?.includes(searchText.toLowerCase()), | |||||
| ), | |||||
| [originTreeData, searchText], | |||||
| ); | |||||
| // 获取数据集\模型\镜像列表 | |||||
| const getTreeData = async () => { | |||||
| const isPublic = activeTab === CommonTabKeys.Private ? false : true; | |||||
| const [res] = await to(config.getList(isPublic)); | |||||
| if (res) { | |||||
| setOriginTreeData(res); | |||||
| // 恢复上一次的 Expand 操作 | |||||
| restoreLastExpand(); | |||||
| } else { | |||||
| setOriginTreeData([]); | |||||
| } | |||||
| }; | |||||
| // 获取数据集\模型\镜像版本列表 | |||||
| const getVersions = async (parentId: string, parentNode: any) => { | |||||
| const [res, error] = await to(config.getVersions(parentId, parentNode)); | |||||
| if (res) { | |||||
| // 更新 treeData children | |||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, res))); | |||||
| // 缓存 loadedKeys | |||||
| const index = loadedKeys.find((v) => v === parentId); | |||||
| if (!index) { | |||||
| setLoadedKeys((prev) => prev.concat(parentId)); | |||||
| } | |||||
| // 恢复上一次的 Check 操作 | |||||
| setTimeout(() => { | |||||
| restoreLastCheck(parentId, res); | |||||
| }, 300); | |||||
| } else { | |||||
| setExpandedKeys([]); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }; | |||||
| // 获取版本下的文件 | |||||
| const getFiles = async (parentId: string, parentNode: any) => { | |||||
| const [res] = await to(config.getFiles(parentId, parentNode)); | |||||
| if (res) { | |||||
| setVersionPath(res.path); | |||||
| setFiles(res.content); | |||||
| } else { | |||||
| setVersionPath(''); | |||||
| setFiles([]); | |||||
| } | |||||
| }; | |||||
| // 动态加载 tree children | |||||
| const onLoadData = ({ key, children, ...rest }: TreeDataNode) => { | |||||
| if (children) { | |||||
| return Promise.resolve(); | |||||
| } else { | |||||
| return getVersions(key as string, rest); | |||||
| } | |||||
| }; | |||||
| // 扩展 | |||||
| const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { | |||||
| const lastKeys = expandedKeysValue.slice(-1); | |||||
| setExpandedKeys(lastKeys); | |||||
| }; | |||||
| // 选中 | |||||
| const onCheck: TreeProps['onCheck'] = (checkedKeysValue, { checkedNodes }) => { | |||||
| const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); | |||||
| setCheckedKeys(lastKeys); | |||||
| if (lastKeys.length && checkedNodes.length) { | |||||
| const last = lastKeys[0] as string; | |||||
| const lastNode = checkedNodes[checkedNodes.length - 1]; | |||||
| getFiles(last, lastNode); | |||||
| } else { | |||||
| setVersionPath(''); | |||||
| setFiles([]); | |||||
| } | |||||
| }; | |||||
| // 恢复上一次的 Expand 操作 | |||||
| // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys | |||||
| // fisrtLoadList 标志位 | |||||
| const restoreLastExpand = () => { | |||||
| if (!firstLoadList && defaultExpandedKeys.length > 0) { | |||||
| setTimeout(() => { | |||||
| setExpandedKeys(defaultExpandedKeys); | |||||
| setFirstLoadList(true); | |||||
| setTimeout(() => { | |||||
| treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); | |||||
| }, 100); | |||||
| }, 0); | |||||
| } | |||||
| }; | |||||
| // 恢复上一次的 Check 操作 | |||||
| // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 | |||||
| // fisrtLoadVersions 标志位 | |||||
| const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => { | |||||
| if (!firstLoadVersions && defaultCheckedKeys.length > 0) { | |||||
| const last = defaultCheckedKeys[0] as string; | |||||
| const { id } = getIdAndVersion(last); | |||||
| // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 | |||||
| if (id === parentId) { | |||||
| setTimeout(() => { | |||||
| setCheckedKeys(defaultCheckedKeys); | |||||
| const parentNode = versions.find((v) => v.key === last); | |||||
| getFiles(last, parentNode); | |||||
| setFirstLoadVersions(true); | |||||
| setTimeout(() => { | |||||
| treeRef?.current?.scrollTo({ | |||||
| key: defaultCheckedKeys[0], | |||||
| align: 'bottom', | |||||
| }); | |||||
| }, 100); | |||||
| }, 0); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleOk = () => { | |||||
| if (checkedKeys.length > 0) { | |||||
| const last = checkedKeys[0] as string; | |||||
| const { id, version } = getIdAndVersion(last); | |||||
| const treeNode = treeData.find((v) => v.key === id) as any; | |||||
| const name = (treeNode?.title ?? '') as string; | |||||
| const identifier = (treeNode?.identifier ?? '') as string; | |||||
| const owner = (treeNode?.owner ?? '') as string; | |||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| activeTab: activeTab as CommonTabKeys, | |||||
| }; | |||||
| onOk?.(res); | |||||
| } else { | |||||
| onOk?.(undefined); | |||||
| } | |||||
| }; | |||||
| const title = `选择${config.name}`; | |||||
| const palceholder = `请输入${config.name}名称`; | |||||
| const fileTitle = | |||||
| type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`; | |||||
| const tabItems = config.tabItems; | |||||
| const titleImg = config.modalIcon; | |||||
| return ( | |||||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | |||||
| <div> | |||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| items={tabItems} | |||||
| onChange={setActiveTab} | |||||
| className={styles['model-tabs']} | |||||
| /> | |||||
| <div className={styles['model-selector']}> | |||||
| <div className={styles['model-selector__left']}> | |||||
| <Input | |||||
| className={styles['model-selector__left__search']} | |||||
| placeholder={palceholder} | |||||
| allowClear | |||||
| variant="borderless" | |||||
| value={searchText} | |||||
| onChange={(e) => setSearchText(e.target.value)} | |||||
| prefix={<Icon icon="local:magnifying-glass" style={{ height: '15px' }} />} | |||||
| /> | |||||
| <Tree | |||||
| ref={treeRef} | |||||
| rootStyle={{ backgroundColor: 'transparent' }} | |||||
| loadData={onLoadData} | |||||
| treeData={treeData} | |||||
| onCheck={onCheck} | |||||
| checkedKeys={checkedKeys} | |||||
| multiple={false} | |||||
| selectable={false} | |||||
| height={324} | |||||
| loadedKeys={loadedKeys} | |||||
| expandedKeys={expandedKeys} | |||||
| onExpand={onExpand} | |||||
| checkable | |||||
| titleRender={(nodeData) => { | |||||
| return ( | |||||
| <span | |||||
| className={styles['model-selector__left__tree-title']} | |||||
| style={{ width: nodeData.isLeaf ? '370px' : '420px' }} | |||||
| > | |||||
| {nodeData.title as string} | |||||
| </span> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['model-selector__right']}> | |||||
| <div className={styles['model-selector__right__title']}>{fileTitle}</div> | |||||
| <div className={styles['model-selector__right__files']}> | |||||
| {files.map((v) => ( | |||||
| <div key={v.url} className={styles['model-selector__right__files__file']}> | |||||
| {v.file_name} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default ResourceSelectorModal; | |||||
| @@ -0,0 +1,91 @@ | |||||
| .subject-drawer { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| .subject-drawer{ | |||||
| height:58px; | |||||
| background:#ffffff; | |||||
| border:1px solid; | |||||
| border-color:#e6e6e6; | |||||
| border-radius:6px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__left{ | |||||
| width: 40px; | |||||
| height: 100%; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| &__circle{ | |||||
| width:17px; | |||||
| height:17px; | |||||
| border-radius: 50%; | |||||
| background:#f98e1b; | |||||
| } | |||||
| } | |||||
| &__slice{ | |||||
| width:0px; | |||||
| height:57px; | |||||
| border:1px solid; | |||||
| border-color:#e6e6e6; | |||||
| } | |||||
| &__right{ | |||||
| padding: 0 10px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__circle{ | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| width:18px; | |||||
| height:18px; | |||||
| background:#fff; | |||||
| margin-right: 8px; | |||||
| border-radius: 50%; | |||||
| cursor: pointer; | |||||
| border:1px solid; | |||||
| &__center{ | |||||
| width: 14px; | |||||
| height: 14px; | |||||
| border-radius: 50%; | |||||
| background-color: #164fff; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,234 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { | |||||
| subjectEdgeModelSerialize, | |||||
| subjectGlobalParam, | |||||
| subjectNodeModel, | |||||
| subjectNodeModelParameter, | |||||
| } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { INode } from '@antv/g6'; | |||||
| import { Drawer, Form, Input, MenuProps } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | |||||
| import PropsLabel from '../PropsLabel'; | |||||
| import { ResourceSelectorType, selectorTypeConfig } from '../ResourceSelectorModal'; | |||||
| import styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| export type ServiceData = { | |||||
| name: string; | |||||
| isMultivalued: string; | |||||
| type: string; | |||||
| flag: boolean; | |||||
| }; | |||||
| type subjectNodeParameterProps = { | |||||
| onFormChange: (data: subjectEdgeModelSerialize) => void; | |||||
| }; | |||||
| const subjectNodeParameter = forwardRef(({ onFormChange }: subjectNodeParameterProps, ref) => { | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem, setStagingItem] = useState<subjectEdgeModelSerialize>( | |||||
| {} as subjectEdgeModelSerialize, | |||||
| ); | |||||
| const [open, setOpen] = useState(false); | |||||
| const [typeList, setTypeList] = useState([]); | |||||
| const [singleTypeList, setSingleTypeList] = useState([]); | |||||
| const [doubleTypeList, setDoubleTypeList] = useState([]); | |||||
| const [tableData, setTableData] = useState([]); | |||||
| const [formId, setFormId] = useState(''); | |||||
| const [circleColor, setCircleColor] = useState(0); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | |||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||||
| const afterOpenChange = async () => { | |||||
| if (!open) { | |||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| const [_values, error] = await to(form.validateFields()); | |||||
| console.log(_values); | |||||
| const fields = form.getFieldsValue(); | |||||
| console.log(fields); | |||||
| // const out_parameters = JSON.stringify(fields.out_parameters); | |||||
| // console.log('getFieldsValue', fields); | |||||
| const res = { | |||||
| ...stagingItem, | |||||
| ...fields, | |||||
| id: formId, | |||||
| }; | |||||
| console.log('res', res); | |||||
| onFormChange(res); | |||||
| } | |||||
| }; | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer( | |||||
| model: subjectNodeModel, | |||||
| params: subjectGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| validate: boolean = false, | |||||
| ) { | |||||
| try { | |||||
| const edgeData: subjectEdgeModelSerialize = { | |||||
| ...model, | |||||
| label: model.label, | |||||
| id: model.id, | |||||
| source: model.source, | |||||
| labelCfg: model.labelCfg, | |||||
| curveOffset: model.curveOffset, | |||||
| curvePosition: model.curvePosition, | |||||
| endPoint: model.endPoint, | |||||
| sourceAnchor: model.sourceAnchor, | |||||
| startPoint: model.startPoint, | |||||
| style: model.style, | |||||
| target: model.target, | |||||
| type: model.type, | |||||
| // color: JSON.parse(model.color), | |||||
| // control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| // console.log('model', edgeData); | |||||
| // setStagingItem({ | |||||
| // ...edgeData, | |||||
| // }); | |||||
| console.log(edgeData); | |||||
| setCircleColor(model.color || 0); | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...edgeData, | |||||
| }); | |||||
| console.log(form.getFieldsValue()); | |||||
| setTableData(model.properties); | |||||
| setFormId(model.id); | |||||
| console.log(form.getFieldsValue()); | |||||
| if (validate) { | |||||
| form.validateFields(); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('JSON.parse error: ', error); | |||||
| } | |||||
| setOpen(true); | |||||
| // 参数下拉菜单 | |||||
| // setMenuItems(createMenuItems(params, parentNodes)); | |||||
| }, | |||||
| close: () => { | |||||
| onClose(); | |||||
| }, | |||||
| validateFields: async () => { | |||||
| if (!open) { | |||||
| return; | |||||
| } | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| return values; | |||||
| } else { | |||||
| form.scrollToField((error as any)?.errorFields?.[0]?.name, { block: 'center' }); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }, | |||||
| })); | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||||
| const type = item.item_type; | |||||
| if (type === 'code') { | |||||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||||
| } | |||||
| let selectorType: ResourceSelectorType; | |||||
| if (type === 'dataset') { | |||||
| selectorType = ResourceSelectorType.Dataset; | |||||
| } else if (type === 'model') { | |||||
| selectorType = ResourceSelectorType.Model; | |||||
| } else { | |||||
| selectorType = ResourceSelectorType.Mirror; | |||||
| } | |||||
| return <KFIcon type={selectorTypeConfig[selectorType].buttonIcon} />; | |||||
| }; | |||||
| // 参数回填 | |||||
| const handleParameterClick = (name: NamePath, value: any) => { | |||||
| form.setFieldValue(name, value); | |||||
| }; | |||||
| // form item label | |||||
| const getLabel = ( | |||||
| item: { key: string; value: subjectNodeModelParameter }, | |||||
| namePrefix: string, | |||||
| ) => { | |||||
| return item.value.type === 'select' ? ( | |||||
| item.value.label + '(' + item.key + ')' | |||||
| ) : ( | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick([namePrefix, item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <Drawer | |||||
| title="关系" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '52px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={500} | |||||
| className={styles['subject-drawer']} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| height: '100%', | |||||
| }} | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| > | |||||
| {/* <div className={styles['subject-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/static-message.png')} | |||||
| title="基本信息" | |||||
| ></SubAreaTitle> | |||||
| </div> */} | |||||
| <Form.Item | |||||
| label="关系名称" | |||||
| name="label" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| layout="horizontal" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入概念名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入概念名称" allowClear /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Drawer> | |||||
| ); | |||||
| }); | |||||
| export default subjectNodeParameter; | |||||
| @@ -0,0 +1,91 @@ | |||||
| .subject-drawer { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| .subject-drawer{ | |||||
| height:58px; | |||||
| background:#ffffff; | |||||
| border:1px solid; | |||||
| border-color:#e6e6e6; | |||||
| border-radius:6px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__left{ | |||||
| width: 40px; | |||||
| height: 100%; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| &__circle{ | |||||
| width:17px; | |||||
| height:17px; | |||||
| border-radius: 50%; | |||||
| background:#f98e1b; | |||||
| } | |||||
| } | |||||
| &__slice{ | |||||
| width:0px; | |||||
| height:57px; | |||||
| border:1px solid; | |||||
| border-color:#e6e6e6; | |||||
| } | |||||
| &__right{ | |||||
| padding: 0 10px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__circle{ | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| width:18px; | |||||
| height:18px; | |||||
| background:#fff; | |||||
| margin-right: 8px; | |||||
| border-radius: 50%; | |||||
| cursor: pointer; | |||||
| border:1px solid; | |||||
| &__center{ | |||||
| width: 14px; | |||||
| height: 14px; | |||||
| border-radius: 50%; | |||||
| background-color: #164fff; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,635 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { getSubjectType } from '@/services/subject'; | |||||
| import { | |||||
| subjectGlobalParam, | |||||
| subjectNodeModel, | |||||
| subjectNodeModelParameter, | |||||
| subjectNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { INode } from '@antv/g6'; | |||||
| import { Button, Drawer, Form, Input, MenuProps, Select, Table, type TableProps } from 'antd'; | |||||
| import { NamePath } from 'antd/es/form/interface'; | |||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||||
| import PropsLabel from '../PropsLabel'; | |||||
| import { ResourceSelectorType, selectorTypeConfig } from '../ResourceSelectorModal'; | |||||
| import styles from './index.less'; | |||||
| const { TextArea } = Input; | |||||
| export type ServiceData = { | |||||
| name: string; | |||||
| isMultivalued: string; | |||||
| type: string; | |||||
| flag: boolean; | |||||
| }; | |||||
| type subjectNodeParameterProps = { | |||||
| onFormChange: (data: subjectNodeModelSerialize) => void; | |||||
| }; | |||||
| const subjectNodeParameter = forwardRef(({ onFormChange }: subjectNodeParameterProps, ref) => { | |||||
| const isMultivaluedList = [ | |||||
| { label: '单值', value: 'kg_entity_property_single_value' }, | |||||
| { label: '双值', value: 'kg_entity_property_multi_value' }, | |||||
| ]; | |||||
| const [form] = Form.useForm(); | |||||
| const [stagingItem, setStagingItem] = useState<subjectNodeModelSerialize>( | |||||
| {} as subjectNodeModelSerialize, | |||||
| ); | |||||
| const [open, setOpen] = useState(false); | |||||
| const [typeList, setTypeList] = useState([]); | |||||
| const [singleTypeList, setSingleTypeList] = useState([]); | |||||
| const [doubleTypeList, setDoubleTypeList] = useState([]); | |||||
| const [tableData, setTableData] = useState([]); | |||||
| const [formId, setFormId] = useState(''); | |||||
| const [circleColor, setCircleColor] = useState(0); | |||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模 | |||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||||
| useEffect(() => { | |||||
| getSubjectType('kg_entity_property_single_value').then((ret) => { | |||||
| setSingleTypeList(ret.data); | |||||
| }); | |||||
| getSubjectType('kg_entity_property_multi_value').then((ret) => { | |||||
| setDoubleTypeList(ret.data); | |||||
| }); | |||||
| }, []); | |||||
| const columns: TableProps<ServiceData>['columns'] = [ | |||||
| { | |||||
| title: '名称', | |||||
| dataIndex: 'name', | |||||
| key: 'name', | |||||
| width: '20%', | |||||
| render: (text, record, index) => { | |||||
| if (record.flag) { | |||||
| return ( | |||||
| <Input | |||||
| onChange={(e) => { | |||||
| console.log(e, index, record); | |||||
| const newData = [...tableData]; | |||||
| newData[index].name = e.target.value; | |||||
| setTableData(newData); | |||||
| }} | |||||
| defaultValue={record.name} | |||||
| autoFocus | |||||
| /> | |||||
| ); | |||||
| } else { | |||||
| return record.name; | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '单值/多值', | |||||
| dataIndex: 'isMultivalued', | |||||
| key: 'isMultivalued', | |||||
| width: '25%', | |||||
| render: (text, record, index) => { | |||||
| if (record.flag) { | |||||
| return ( | |||||
| <Select | |||||
| allowClear | |||||
| onChange={(e) => { | |||||
| console.log(e, index, record); | |||||
| const newData = [...tableData]; | |||||
| newData[index].isMultivalued = e; | |||||
| setTableData(newData); | |||||
| if (e == 'kg_entity_property_single_value') { | |||||
| setTypeList(singleTypeList); | |||||
| } else { | |||||
| setTypeList(doubleTypeList); | |||||
| } | |||||
| // getSubjectType(e).then((ret) => { | |||||
| // setTypeList(ret.data); | |||||
| // }); | |||||
| }} | |||||
| options={isMultivaluedList} | |||||
| fieldNames={{ label: 'label', value: 'value' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| ); | |||||
| } else { | |||||
| console.log(isMultivaluedList.filter((item) => item.value == record.isMultivalued)[0]); | |||||
| return isMultivaluedList.filter((item) => item.value == record.isMultivalued)[0]?.label; | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '类型', | |||||
| dataIndex: 'type', | |||||
| key: 'type', | |||||
| width: '30%', | |||||
| render: (text, record, index) => { | |||||
| if (record.flag) { | |||||
| return ( | |||||
| <Select | |||||
| allowClear | |||||
| onChange={(e) => { | |||||
| console.log(e, index, record); | |||||
| const newData = [...tableData]; | |||||
| newData[index].type = e; | |||||
| setTableData(newData); | |||||
| }} | |||||
| options={typeList} | |||||
| fieldNames={{ label: 'dictLabel', value: 'dictValue' }} | |||||
| optionFilterProp="name" | |||||
| showSearch | |||||
| /> | |||||
| ); | |||||
| } else { | |||||
| return record.isMultivalued && record.isMultivalued == 'kg_entity_property_single_value' | |||||
| ? singleTypeList.filter((item) => item.dictValue == record.type)[0]?.dictLabel | |||||
| : doubleTypeList.filter((item) => item.dictValue == record.type)[0]?.dictLabel; | |||||
| } | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: '30%', | |||||
| key: 'operation', | |||||
| render: (_: any, record: any, index: any) => | |||||
| record.flag ? ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| onClick={() => { | |||||
| editTable(record, index); | |||||
| }} | |||||
| icon={<KFIcon type="icon-baocunbingfanhui" />} | |||||
| ></Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-fuzhi" />} | |||||
| onClick={() => { | |||||
| cloneTable(record, index); | |||||
| }} | |||||
| ></Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| onClick={() => { | |||||
| deleteTable(record, index); | |||||
| }} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| ></Button> | |||||
| </div> | |||||
| ) : ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| onClick={() => { | |||||
| editTable(record, index); | |||||
| }} | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| ></Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-fuzhi" />} | |||||
| onClick={() => { | |||||
| cloneTable(record, index); | |||||
| }} | |||||
| ></Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => { | |||||
| deleteTable(record, index); | |||||
| }} | |||||
| ></Button> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| const editTable = (val: any, index: any) => { | |||||
| tableData[index] = { | |||||
| ...tableData[index], | |||||
| flag: !val.flag, | |||||
| }; | |||||
| const newTableData = JSON.parse(JSON.stringify(tableData)); | |||||
| setTableData(newTableData); | |||||
| console.log(tableData); | |||||
| }; | |||||
| const addTable = () => { | |||||
| setTableData([ | |||||
| ...tableData, | |||||
| { | |||||
| name: '', | |||||
| isMultivalued: '', | |||||
| type: '', | |||||
| flag: false, | |||||
| }, | |||||
| ]); | |||||
| }; | |||||
| const cloneTable = (val, index) => { | |||||
| setTableData([...tableData, val]); | |||||
| }; | |||||
| const deleteTable = (val, index) => { | |||||
| tableData.splice(index, 1); | |||||
| setTableData(JSON.parse(JSON.stringify(tableData))); | |||||
| }; | |||||
| const afterOpenChange = async () => { | |||||
| if (!open) { | |||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |||||
| const [_values, error] = await to(form.validateFields()); | |||||
| console.log(_values); | |||||
| const fields = form.getFieldsValue(); | |||||
| const label = fields.label; | |||||
| const color = circleColor; | |||||
| // const out_parameters = JSON.stringify(fields.out_parameters); | |||||
| // console.log('getFieldsValue', fields); | |||||
| const res = { | |||||
| ...stagingItem, | |||||
| ...fields, | |||||
| properties: tableData, | |||||
| label, | |||||
| color, | |||||
| id: formId, | |||||
| }; | |||||
| console.log('res', res); | |||||
| onFormChange(res); | |||||
| } | |||||
| }; | |||||
| const onClose = () => { | |||||
| setOpen(false); | |||||
| }; | |||||
| const changeColor = (val: any) => { | |||||
| console.log(val); | |||||
| setCircleColor(val); | |||||
| form.setFieldsValue({ | |||||
| color: circleColor, | |||||
| }); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | |||||
| showDrawer( | |||||
| model: subjectNodeModel, | |||||
| params: subjectGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| validate: boolean = false, | |||||
| ) { | |||||
| try { | |||||
| const nodeData: subjectNodeModelSerialize = { | |||||
| ...model, | |||||
| label: model.label, | |||||
| id: model.id, | |||||
| // color: JSON.parse(model.color), | |||||
| // control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| // console.log('model', nodeData); | |||||
| // setStagingItem({ | |||||
| // ...nodeData, | |||||
| // }); | |||||
| console.log(model); | |||||
| setCircleColor(model.color || 0); | |||||
| form.resetFields(); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | |||||
| setTableData(model.properties); | |||||
| setFormId(model.id); | |||||
| console.log(form.getFieldsValue()); | |||||
| if (validate) { | |||||
| form.validateFields(); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error('JSON.parse error: ', error); | |||||
| } | |||||
| setOpen(true); | |||||
| // 参数下拉菜单 | |||||
| // setMenuItems(createMenuItems(params, parentNodes)); | |||||
| }, | |||||
| close: () => { | |||||
| onClose(); | |||||
| }, | |||||
| validateFields: async () => { | |||||
| if (!open) { | |||||
| return; | |||||
| } | |||||
| const [values, error] = await to(form.validateFields()); | |||||
| if (!error && values) { | |||||
| return values; | |||||
| } else { | |||||
| form.scrollToField((error as any)?.errorFields?.[0]?.name, { block: 'center' }); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| }, | |||||
| })); | |||||
| // ref 类型选择 | |||||
| // const selectRefData = ( | |||||
| // formItemName: NamePath, | |||||
| // item: subjectNodeModelParameter | Pick<subjectNodeModelParameter, 'item_type'>, | |||||
| // ) => { | |||||
| // if (item.item_type === 'code') { | |||||
| // selectCodeConfig(formItemName, item); | |||||
| // } else { | |||||
| // selectResource(formItemName, item); | |||||
| // } | |||||
| // }; | |||||
| // // 选择代码配置 | |||||
| // const selectCodeConfig = ( | |||||
| // formItemName: NamePath, | |||||
| // item: subjectNodeModelParameter | Pick<subjectNodeModelParameter, 'item_type'>, | |||||
| // ) => { | |||||
| // const { close } = openAntdModal(CodeSelectorModal, { | |||||
| // onOk: (res) => { | |||||
| // if (res) { | |||||
| // const value = JSON.stringify({ | |||||
| // id: res.id, | |||||
| // name: res.code_repo_name, | |||||
| // code_path: res.git_url, | |||||
| // branch: res.git_branch, | |||||
| // username: res.git_user_name, | |||||
| // password: res.git_password, | |||||
| // ssh_private_key: res.ssh_key, | |||||
| // }); | |||||
| // form.setFieldValue(formItemName, { | |||||
| // ...item, | |||||
| // value, | |||||
| // showValue: res.code_repo_name, | |||||
| // fromSelect: true, | |||||
| // }); | |||||
| // } | |||||
| // close(); | |||||
| // }, | |||||
| // }); | |||||
| // }; | |||||
| // // 选择数据集、模型、镜像 | |||||
| // const selectResource = ( | |||||
| // formItemName: NamePath, | |||||
| // item: subjectNodeModelParameter | Pick<subjectNodeModelParameter, 'item_type'>, | |||||
| // ) => { | |||||
| // let type: ResourceSelectorType; | |||||
| // switch (item.item_type) { | |||||
| // case 'dataset': | |||||
| // type = ResourceSelectorType.Dataset; | |||||
| // break; | |||||
| // case 'model': | |||||
| // type = ResourceSelectorType.Model; | |||||
| // break; | |||||
| // default: | |||||
| // type = ResourceSelectorType.Mirror; | |||||
| // break; | |||||
| // } | |||||
| // const fieldValue = form.getFieldValue(formItemName); | |||||
| // const activeTab = fieldValue?.activeTab as CommonTabKeys | undefined; | |||||
| // const expandedKeys = Array.isArray(fieldValue?.expandedKeys) ? fieldValue?.expandedKeys : []; | |||||
| // const checkedKeys = Array.isArray(fieldValue?.checkedKeys) ? fieldValue?.checkedKeys : []; | |||||
| // const { close } = openAntdModal(ResourceSelectorModal, { | |||||
| // type, | |||||
| // defaultExpandedKeys: expandedKeys, | |||||
| // defaultCheckedKeys: checkedKeys, | |||||
| // defaultActiveTab: activeTab, | |||||
| // onOk: (res) => { | |||||
| // // if (res) { | |||||
| // // if (type === ResourceSelectorType.Mirror) { | |||||
| // // const { activeTab, id, version, path } = res; | |||||
| // // if (formItemName === 'image') { | |||||
| // // // 单独的选择镜像 | |||||
| // // form.setFieldValue(formItemName, path); | |||||
| // // } else { | |||||
| // // // 输入参数选择镜像 | |||||
| // // form.setFieldValue(formItemName, { | |||||
| // // ...item, | |||||
| // // value: path, | |||||
| // // showValue: path, | |||||
| // // fromSelect: true, | |||||
| // // activeTab, | |||||
| // // expandedKeys: [id], | |||||
| // // checkedKeys: [`${id}-${version}`], | |||||
| // // }); | |||||
| // // } | |||||
| // // } else { | |||||
| // // const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| // // const value = JSON.stringify({ | |||||
| // // id, | |||||
| // // name, | |||||
| // // version, | |||||
| // // path, | |||||
| // // identifier, | |||||
| // // owner, | |||||
| // // }); | |||||
| // // const showValue = `${name}:${version}`; | |||||
| // // form.setFieldValue(formItemName, { | |||||
| // // ...item, | |||||
| // // value, | |||||
| // // showValue, | |||||
| // // fromSelect: true, | |||||
| // // activeTab, | |||||
| // // expandedKeys: [id], | |||||
| // // checkedKeys: [`${id}-${version}`], | |||||
| // // }); | |||||
| // // } | |||||
| // // } else { | |||||
| // // if (type === ResourceSelectorType.Mirror && formItemName === 'image') { | |||||
| // // form.setFieldValue(formItemName, undefined); | |||||
| // // } else { | |||||
| // // form.setFieldValue(formItemName, { | |||||
| // // ...item, | |||||
| // // value: undefined, | |||||
| // // showValue: undefined, | |||||
| // // fromSelect: false, | |||||
| // // activeTab: undefined, | |||||
| // // expandedKeys: [], | |||||
| // // checkedKeys: [], | |||||
| // // }); | |||||
| // // } | |||||
| // // } | |||||
| // // form.validateFields([formItemName]); | |||||
| // // close(); | |||||
| // }, | |||||
| // }); | |||||
| // }; | |||||
| // 获取选择数据集、模型后面按钮 icon | |||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||||
| const type = item.item_type; | |||||
| if (type === 'code') { | |||||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||||
| } | |||||
| let selectorType: ResourceSelectorType; | |||||
| if (type === 'dataset') { | |||||
| selectorType = ResourceSelectorType.Dataset; | |||||
| } else if (type === 'model') { | |||||
| selectorType = ResourceSelectorType.Model; | |||||
| } else { | |||||
| selectorType = ResourceSelectorType.Mirror; | |||||
| } | |||||
| return <KFIcon type={selectorTypeConfig[selectorType].buttonIcon} />; | |||||
| }; | |||||
| // 参数回填 | |||||
| const handleParameterClick = (name: NamePath, value: any) => { | |||||
| form.setFieldValue(name, value); | |||||
| }; | |||||
| // form item label | |||||
| const getLabel = ( | |||||
| item: { key: string; value: subjectNodeModelParameter }, | |||||
| namePrefix: string, | |||||
| ) => { | |||||
| return item.value.type === 'select' ? ( | |||||
| item.value.label + '(' + item.key + ')' | |||||
| ) : ( | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick([namePrefix, item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <Drawer | |||||
| title="概念" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '52px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={500} | |||||
| className={styles['subject-drawer']} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| height: '100%', | |||||
| }} | |||||
| autoComplete="off" | |||||
| scrollToFirstError | |||||
| > | |||||
| {/* <div className={styles['subject-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/static-message.png')} | |||||
| title="基本信息" | |||||
| ></SubAreaTitle> | |||||
| </div> */} | |||||
| <Form.Item | |||||
| label="概念名称" | |||||
| name="label" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| layout="horizontal" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入概念名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入概念名称" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="ICON" | |||||
| name="color" | |||||
| labelCol={{ span: 6 }} | |||||
| wrapperCol={{ span: 18 }} | |||||
| layout="horizontal" | |||||
| > | |||||
| <div className={styles['subject-drawer']}> | |||||
| <div className={styles['subject-drawer__left']}> | |||||
| <div | |||||
| className={styles['subject-drawer__left__circle']} | |||||
| style={{ backgroundColor: circleColor == 1 ? '#1664ff' : '#f98e1b' }} | |||||
| ></div> | |||||
| </div> | |||||
| <div className={styles['subject-drawer__slice']}></div> | |||||
| <div className={styles['subject-drawer__right']}> | |||||
| <div | |||||
| onClick={() => { | |||||
| changeColor(1); | |||||
| }} | |||||
| style={{ borderColor: circleColor == 1 ? '#1664ff' : 'transparent' }} | |||||
| className={styles['subject-drawer__right__circle']} | |||||
| > | |||||
| <div className={styles['subject-drawer__right__circle__center']}></div> | |||||
| </div> | |||||
| <div | |||||
| onClick={() => { | |||||
| changeColor(2); | |||||
| }} | |||||
| style={{ | |||||
| borderColor: circleColor == 2 ? '#1664ff' : 'transparent', | |||||
| }} | |||||
| className={styles['subject-drawer__right__circle']} | |||||
| > | |||||
| <div | |||||
| className={styles['subject-drawer__right__circle__center']} | |||||
| style={{ background: '#f98e1b' }} | |||||
| ></div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="属性" | |||||
| // name="properties" | |||||
| layout="vertical" | |||||
| style={{ position: 'relative' }} | |||||
| labelCol={{ span: 24 }} | |||||
| wrapperCol={{ span: 24 }} | |||||
| > | |||||
| <div style={{ position: 'absolute', width: '100%' }}> | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| pagination={false} | |||||
| // onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| onClick={addTable} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 增加属性 | |||||
| </Button> | |||||
| </div> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </Drawer> | |||||
| ); | |||||
| }); | |||||
| export default subjectNodeParameter; | |||||
| @@ -0,0 +1,144 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | |||||
| import { editKnowledgeSubject } from '@/services/subject'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message, type FormRule, type ModalProps } from 'antd'; | |||||
| import { useMemo, useState } from 'react'; | |||||
| export enum VerifyMode { | |||||
| Password = 0, // 用户名密码 | |||||
| SSH = 1, // SSH Key | |||||
| } | |||||
| export enum OperationType { | |||||
| Create = 0, // 新建 | |||||
| Update = 1, // 更新 | |||||
| } | |||||
| type FormData = Partial<CodeConfigData>; | |||||
| interface AddCodeConfigModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| opType: OperationType; | |||||
| codeConfigData?: CodeConfigData; | |||||
| onOk: () => void; | |||||
| } | |||||
| function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeConfigModalProps) { | |||||
| const [form] = Form.useForm(); | |||||
| const [typeList, setTypeList] = useState([]); | |||||
| const isPublic = Form.useWatch('code_repo_vis', form) === AvailableRange.Public; | |||||
| const urlExample = useMemo( | |||||
| () => | |||||
| isPublic | |||||
| ? 'https://gitlink.org.cn/ci4s/ci4sManagement-cloud.git' | |||||
| : 'git@code.gitlink.org.cn:ci4s/ci4sManagement-cloud.git', | |||||
| [isPublic], | |||||
| ); | |||||
| // /^(git@[\w.-]+:[\w./-]+\.git)$/ | |||||
| const urlRules: FormRule[] = useMemo( | |||||
| () => | |||||
| isPublic | |||||
| ? [ | |||||
| { | |||||
| type: 'url', | |||||
| message: '请输入正确的 Git 地址', | |||||
| }, | |||||
| ] | |||||
| : ([] as FormRule[]), | |||||
| [isPublic], | |||||
| ); | |||||
| // 创建 | |||||
| const createCodeConfig = async (formData: FormData) => { | |||||
| console.log(OperationType); | |||||
| const params: FormData & { id?: number } = { | |||||
| ...formData, | |||||
| }; | |||||
| // 清除多余的信息 | |||||
| // if (formData.code_repo_vis === AvailableRange.Public) { | |||||
| // omit(params, ['verify_mode', 'git_user_name', 'git_password', 'ssh_key']); | |||||
| // } | |||||
| // if (formData.verify_mode === VerifyMode.Password) { | |||||
| // omit(params, ['ssh_key']); | |||||
| // } else if (formData.verify_mode === VerifyMode.SSH) { | |||||
| // omit(params, ['git_user_name', 'git_password']); | |||||
| // } | |||||
| params.id = codeConfigData?.id; | |||||
| const request = editKnowledgeSubject; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('修改成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: FormData) => { | |||||
| createCodeConfig(formData); | |||||
| }; | |||||
| // 设置初始值 | |||||
| const initialValues: FormData = codeConfigData ?? { | |||||
| code_repo_vis: AvailableRange.Public, | |||||
| verify_mode: VerifyMode.Password, | |||||
| }; | |||||
| if (initialValues.verify_mode === undefined || initialValues.verify_mode === null) { | |||||
| initialValues.verify_mode = VerifyMode.Password; | |||||
| } | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title={'修改主体'} | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| destroyOnClose | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={initialValues} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label="主体名称" | |||||
| name="name" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入主体名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入主体名称" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="主体描述" | |||||
| name="description" | |||||
| required | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入主体描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入主体描述" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default AddCodeConfigModal; | |||||
| @@ -0,0 +1,93 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| import mock from 'mock/knowledge' | |||||
| // // 添加图谱 | |||||
| export function addKnowledgeGraph(data:any) { | |||||
| return request(`/api/kg`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 修改图谱 | |||||
| export function editKnowledgeGraph(data:any) { | |||||
| return request(`/api/kg`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 获取图谱 | |||||
| export function getKnowledgeGraph() { | |||||
| return request(`/api/kg/list`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 删除图谱 | |||||
| export function deleteKnowledgeGraph(data:any) { | |||||
| return request(`/api/kg/${data}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 查看图谱详情 | |||||
| export function getKnowledgeGraphById(data:any) { | |||||
| return request(`/api/kg`, { | |||||
| method: 'GET', | |||||
| params:data | |||||
| }); | |||||
| } | |||||
| // 查看图谱版本列表 | |||||
| export function getKnowledgeGraphVersionList(data:any) { | |||||
| return request(`/api/kg/version/list`, { | |||||
| method: 'GET', | |||||
| params:data | |||||
| }); | |||||
| } | |||||
| // // 查看元素配置列表 | |||||
| export function getConfigurationList(data:any) { | |||||
| return request(`/api/kg/configuration/list`, { | |||||
| method: 'GET', | |||||
| params:data | |||||
| }); | |||||
| } | |||||
| // 获取关联实体 | |||||
| export function getEntityGraph() { | |||||
| return request(`/api/kg/ontology/getBasicInfo`, { | |||||
| method: 'GET', | |||||
| data:{} | |||||
| }); | |||||
| } | |||||
| // 图谱版本新增 | |||||
| export function postVersion(data:any) { | |||||
| return request(`/api/kg/version`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| // 删除图谱版本 | |||||
| export function deleteKnowledgeGraphVersion(data:any) { | |||||
| return request(`/api/mmp//kg/version/${data}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 增量更新 | |||||
| export function incrementalUpdate(data:any) { | |||||
| return request(`/api/kg/version/incrementalUpdate`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| // 全量更新 | |||||
| export function fullUpdate(data:any) { | |||||
| return request(`/api/kg/version/fullUpdate`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| // 全量更新 | |||||
| export function rollback(data:any) { | |||||
| return request(`/api/kg/version/rollback`, { | |||||
| method: 'POST', | |||||
| data | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| import { request } from '@umijs/max'; | |||||
| import mock from 'mock/knowledge' | |||||
| // // 添加主体 | |||||
| export function addKnowledgeSubject(data:any) { | |||||
| return request(`/api/mmp//kg/ontology`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 修改主体 | |||||
| export function editKnowledgeGraph(data:any) { | |||||
| return request(`/api/kg`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 获取主体 | |||||
| export function getKnowledgeSubject(data:any) { | |||||
| return request(`/api/kg/ontology/list`, { | |||||
| method: 'POST', | |||||
| data:data, | |||||
| }); | |||||
| } | |||||
| // 删除单个主体 | |||||
| export function deleteSubject(data:any) { | |||||
| return request(`/api/kg/ontology/${data}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 查看主体详情 | |||||
| export function getKnowledgeSubjectById(data:any) { | |||||
| return request(`/api/kg/ontology/${data}`, { | |||||
| method: 'GET', | |||||
| }); | |||||
| } | |||||
| // 查看主体版本列表 | |||||
| export function getKnowledgeGraphVersionList(data:any) { | |||||
| return request(`/api/kg/version/list`, { | |||||
| method: 'GET', | |||||
| params:data | |||||
| }); | |||||
| } | |||||
| // // 查看元素配置列表 | |||||
| export function getConfigurationList(data:any) { | |||||
| return request(`/api/kg/configuration/list`, { | |||||
| method: 'GET', | |||||
| params:data | |||||
| }); | |||||
| } | |||||
| // 修改基本主体 | |||||
| export function editKnowledgeSubject(data:any) { | |||||
| return request(`/api/kg/ontology/info`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 修改基本主体 | |||||
| export function getSubjectType(dictcode:any) { | |||||
| return request(`/api/system/dict/data/type/${dictcode}`, { | |||||
| method: 'get', | |||||
| }); | |||||
| } | |||||
| @@ -114,3 +114,18 @@ export type ComputingResource = { | |||||
| standard: string; | standard: string; | ||||
| create_by: string; | create_by: string; | ||||
| }; | }; | ||||
| // 主体边规格 | |||||
| export type subjectEdgeModelSerialize = { | |||||
| label:string, | |||||
| id: number, | |||||
| source: string, | |||||
| labelCfg: string, | |||||
| curveOffset: string, | |||||
| curvePosition: string, | |||||
| endPoint: string, | |||||
| sourceAnchor: string, | |||||
| startPoint: string, | |||||
| style: string, | |||||
| target: string, | |||||
| type: string, | |||||
| }; | |||||