| @@ -65,3 +65,4 @@ mvnw | |||||
| /react-ui/types/tsconfig.tsbuildinfo | /react-ui/types/tsconfig.tsbuildinfo | ||||
| /react-ui/storybook-static | /react-ui/storybook-static | ||||
| /react-ui/.storybook/scripts | /react-ui/.storybook/scripts | ||||
| /react-ui/dist.zip | |||||
| @@ -18,6 +18,11 @@ spec: | |||||
| image: ${k8s-5auth-image} | image: ${k8s-5auth-image} | ||||
| ports: | ports: | ||||
| - containerPort: 9200 | - containerPort: 9200 | ||||
| env: | |||||
| - name: TZ | |||||
| value: Asia/Shanghai | |||||
| - name: JAVA_TOOL_OPTIONS | |||||
| value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" | |||||
| --- | --- | ||||
| apiVersion: v1 | apiVersion: v1 | ||||
| @@ -28,9 +33,15 @@ metadata: | |||||
| spec: | spec: | ||||
| type: NodePort | type: NodePort | ||||
| ports: | ports: | ||||
| - port: 9200 | |||||
| - name: http | |||||
| port: 9200 | |||||
| nodePort: 31206 | nodePort: 31206 | ||||
| protocol: TCP | protocol: TCP | ||||
| - name: debug | |||||
| nodePort: 31221 | |||||
| port: 5005 | |||||
| protocol: TCP | |||||
| targetPort: 5005 | |||||
| selector: | selector: | ||||
| app: ci4s-auth | app: ci4s-auth | ||||
| @@ -127,6 +127,8 @@ export default defineConfig({ | |||||
| headScripts: [ | headScripts: [ | ||||
| // 解决首次加载时白屏的问题 | // 解决首次加载时白屏的问题 | ||||
| { src: '/scripts/loading.js', async: true }, | { src: '/scripts/loading.js', async: true }, | ||||
| { src: '/scripts/resize.js', async: true }, | |||||
| // { src: '/scripts/resize-breakpoint.js', async: true }, | |||||
| ], | ], | ||||
| // links: [ | // links: [ | ||||
| // { | // { | ||||
| @@ -20,14 +20,18 @@ 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.197:31213', // 开发环境 | |||||
| target: 'http://172.20.32.197:31213', // 开发环境 | |||||
| // target: 'http://172.20.32.235:31213', // 测试环境 | // target: 'http://172.20.32.235:31213', // 测试环境 | ||||
| target: 'http://172.20.32.44:8082', | |||||
| // target: 'http://172.20.32.150:8082', | |||||
| // target: 'http://172.20.32.44:8082', | |||||
| <<<<<<< HEAD | |||||
| // target: 'http://172.20.32.164:8082', | |||||
| ======= | |||||
| target: 'http://172.20.32.164:8082', | |||||
| >>>>>>> dev-zw | |||||
| // 配置了这个可以从 http 代理到 https | // 配置了这个可以从 http 代理到 https | ||||
| // 依赖 origin 的功能可能需要这个,比如 cookie | // 依赖 origin 的功能可能需要这个,比如 cookie | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| pathRewrite: { '^/api': '' }, | |||||
| // pathRewrite: { '^/api': '' }, | |||||
| }, | }, | ||||
| '/profile/avatar/': { | '/profile/avatar/': { | ||||
| target: 'http://172.20.32.235:31213', | target: 'http://172.20.32.235:31213', | ||||
| @@ -13,7 +13,20 @@ | |||||
| export default [ | export default [ | ||||
| { | { | ||||
| path: '/', | path: '/', | ||||
| redirect: '/workspace', | |||||
| redirect: '/home', | |||||
| }, | |||||
| { | |||||
| name: '首页', | |||||
| path: '/home', | |||||
| layout: false, | |||||
| routes: [ | |||||
| { | |||||
| name: '首页', | |||||
| path: '', | |||||
| key: 'home', | |||||
| component: './Home/index', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '工作空间', | name: '工作空间', | ||||
| @@ -141,12 +154,23 @@ export default [ | |||||
| { | { | ||||
| name: '实验对比', | name: '实验对比', | ||||
| path: 'compare', | path: 'compare', | ||||
| component: './Experiment/Comparison/index', | |||||
| routes: [ | |||||
| { | |||||
| name: '实验对比', | |||||
| path: '', | |||||
| component: './Experiment/Comparison/index', | |||||
| }, | |||||
| { | |||||
| name: '可视化对比', | |||||
| path: 'compare-visual', | |||||
| component: './Experiment/Aim/index', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '实验可视化对比', | |||||
| path: 'compare-visual', | |||||
| component: './Experiment/Aim/index', | |||||
| name: '可视化', | |||||
| path: 'visual', | |||||
| component: './Experiment/Tensorboard/index', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -218,7 +242,18 @@ export default [ | |||||
| { | { | ||||
| name: '实验实例详情', | name: '实验实例详情', | ||||
| path: 'instance/:experimentId/:id', | path: 'instance/:experimentId/:id', | ||||
| component: './HyperParameter/Instance/index', | |||||
| routes: [ | |||||
| { | |||||
| name: '实验实例详情', | |||||
| path: '', | |||||
| component: './HyperParameter/Instance/index', | |||||
| }, | |||||
| { | |||||
| name: '可视化对比', | |||||
| path: 'compare-visual', | |||||
| component: './HyperParameter/Aim/index', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -395,6 +430,18 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '知识图谱', | |||||
| path: 'knowledge', | |||||
| routes: [ | |||||
| { | |||||
| name: '知识图谱', | |||||
| path: '', | |||||
| key: 'knowledge', | |||||
| component: './Knowledge/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -561,18 +608,6 @@ export default [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| name: '知识图谱', | |||||
| path: '/knowledge', | |||||
| routes: [ | |||||
| { | |||||
| name: '知识图谱', | |||||
| path: '', | |||||
| key: 'knowledge', | |||||
| component: './Knowledge/index', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| path: '*', | path: '*', | ||||
| layout: false, | layout: false, | ||||
| @@ -0,0 +1,600 @@ | |||||
| import { defineMock } from 'umi'; | |||||
| export default defineMock({ | |||||
| 'GET /api/mmp/workspace/getPublicDatasets': { | |||||
| msg: '操作成功', | |||||
| code: 200, | |||||
| data: { | |||||
| content: [ | |||||
| { | |||||
| name: 'R1蒸馏模型数学推理能力测试集', | |||||
| identifier: 'public_dataset_20250519163052', | |||||
| description: | |||||
| '共728道数学推理题目,包括:\nMATH-500:一组具有挑战性的高中数学竞赛问题数据集,涵盖七个科目(如初等代数、代数、数论)共500道题。\nGPQA-Diamond:该数据集包含物理、化学和生物学子领域的硕士水平多项选择题,共198道题。\nAIME-2024:美国邀请数学竞赛的数据集,包含30道数学题。', | |||||
| is_public: true, | |||||
| time_ago: '2个月前', | |||||
| full_last_update_time: '2025-06-23T14:36:48.000+08:00', | |||||
| id: 91, | |||||
| visits: 1, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '有机化学VLM', | |||||
| identifier: 'public_dataset_20250527113008', | |||||
| description: 'Dataset Card for "Chemistry_text_to_image"', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:06:32.000+08:00', | |||||
| id: 134, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'OQMD 开源量子材料数据集', | |||||
| identifier: 'public_dataset_20250527141950', | |||||
| description: | |||||
| 'QMD 包含了通过密度泛函理论 (DFT) 计算得到的超过 1,226,781 种材料的热力学和结构性质。数据库中的数据来源于无机晶体结构数据库 (ICSD),包括了近 300,000 种化合物的 DFT 总能量计算以及常见晶体结构的修饰', | |||||
| is_public: true, | |||||
| time_ago: '2个月前', | |||||
| full_last_update_time: '2025-06-23T14:38:43.000+08:00', | |||||
| id: 136, | |||||
| visits: 5, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '不可降解和可生物降解的材料数据集', | |||||
| identifier: 'public_dataset_20250527142930', | |||||
| description: | |||||
| '此数据集包含大约 256K 图像(156K 原始数据),代表两类:可生物降解和不可生物降解。\n可生物降解,包含可被微生物自然分解的材料,如食物、植物、水果等。这种材料的废物可以加工成堆肥。\n不可生物降解,包含无法自然分解的材料,例如塑料、金属、无机元素等。这种材料的废料将被回收成新材料。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:06:11.000+08:00', | |||||
| id: 137, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '金属有机框架材料预测', | |||||
| identifier: 'public_dataset_20250527143028', | |||||
| description: | |||||
| '金属有机框架 (MOF) 是一类通过金属离子(或金属簇)和有机配体之间的配位键连接的结晶材料。MOF 材料具有多孔结构、高度可调和巨大的比表面积,使其在吸附、储气、分离、催化等领域具有广泛的应用潜力。预测合成是指通过计算机模拟和机器学习方法对新型 MOF 材料的合成路线和条件进行预测和设计。\n', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:06:01.000+08:00', | |||||
| id: 138, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '纤维增强复合材料的弹性特性', | |||||
| identifier: 'public_dataset_20250527144649', | |||||
| description: | |||||
| '纤维增强复合材料弹性特性数据集主要包含其力学性能参数,如弹性模量(纵向、横向)、剪切模量、泊松比以及应力-应变关系等。数据通常通过实验测试(拉伸、压缩、弯曲试验)或计算模拟(有限元分析、细观力学模型)获得,涵盖不同纤维类型(碳纤维、玻璃纤维、芳纶等)、基体材料(环氧树脂、热塑性塑料等)及铺层方式(单向、编织、多轴向)的组合。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:05:24.000+08:00', | |||||
| id: 142, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'OCR 合成材料', | |||||
| identifier: 'public_dataset_20250527144821', | |||||
| description: | |||||
| 'OCR(光学字符识别)合成材料数据集是用于训练和评估文本识别模型的专用数据集,主要包含人工生成的文本图像,模拟真实场景中的材料标签、说明书、包装文字等。这类数据集通常涵盖多种字体、背景、光照条件、扭曲变形及噪声干扰,以提高模型鲁棒性。数据可能包含金属、塑料、复合材料等工业材料的名称、参数(如成分、规格、批次号)及安全标识', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:05:18.000+08:00', | |||||
| id: 143, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '钙钛矿稳定性', | |||||
| identifier: 'public_dataset_20250527145953', | |||||
| description: | |||||
| '这个钙钛矿稳定性数据集给出了潜在钙钛矿材料成分相对于用 DFT 计算的凸包的能量。钙钛矿数据集还包括包含钙钛矿结构中 A 位点、B 位点和 X 位点信息的列,以便对数据进行更高级的分组。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:04:30.000+08:00', | |||||
| id: 148, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '纳米颗粒毒性数据集', | |||||
| identifier: 'public_dataset_20250527150856', | |||||
| description: | |||||
| '该数据集是一个毒性数据集,由几列组成,捕获了纳米颗粒 (NPs) 的各种属性及其毒理学影响。该数据集包含与纳米颗粒 (NPs) 及其特性相关的各种特征,这些特征可能与毒性分类有关', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:04:41.000+08:00', | |||||
| id: 149, | |||||
| visits: 2, | |||||
| praises_count: 1, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '3D多模态医疗数据集-分割-fanshuai', | |||||
| identifier: 'public_dataset_20250519151852', | |||||
| description: '大规模通用 3D 医疗图像分割数据集 (M3D-Seg)', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-22T10:20:52.000+08:00', | |||||
| id: 82, | |||||
| visits: 0, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '中文基于满血DeepSeek-R1蒸馏数据集', | |||||
| identifier: 'public_dataset_20250519161406', | |||||
| description: | |||||
| '注意:提供了直接SFT使用的版本。将数据中的思考和答案整合成output字段,大部分SFT代码框架均可直接直接加载训练。\n本数据集为中文开源蒸馏满血R1的数据集,数据集中不仅包含math数据,还包括大量的通用类型数据,总数量为110K。\n为什么开源这个数据?\nR1的效果十分强大,并且基于R1蒸馏数据SFT的小模型也展现出了强大的效果,但检索发现,大部分开源的R1蒸馏数据集均为英文数', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-19T16:14:06.000+08:00', | |||||
| id: 88, | |||||
| visits: 0, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: '中文Text2SQL数据集', | |||||
| identifier: 'public_dataset_20250519165142', | |||||
| description: | |||||
| '同时包含用于训练和测试表格问答预训练模型的数据,数据集包含500条训练数据和100条测试数据。\n表格问答预训练模型的训练和测试数据,支持中文,支持通用领域的表格问答。另外,也可以从本model card中,点击数据集文件panel,然后点击数据文件选项,即可下载trian.zip和test.zip文件', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-19T16:51:43.000+08:00', | |||||
| id: 93, | |||||
| visits: 0, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'MatPES', | |||||
| identifier: 'public_dataset_20250521090336', | |||||
| description: | |||||
| '使用元素周期表几乎完全覆盖的势能面数据集来训练基础 电位 (FP),即机器学习原子间电位 (MLIP),几乎完全覆盖了周期性 桌子。MatPES 是材料虚拟实验室和材料项目的一项倡议,旨在解决此类材料 PES 数据集中的关键缺陷。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:10:21.000+08:00', | |||||
| id: 100, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'innovation_contest/innov202305100905418', | |||||
| identifier: 'public_dataset_20250526093119', | |||||
| description: | |||||
| '1. 赛题解读PPT;2.根据流动状态分开的数据集,方便选手测试自己模型的变状态泛化性能\n新的数据中输入输出与均在一个文件夹中\n新的数据集中,模型文件简称对应的状态如下:\nCBFS 曲线后台阶 雷诺数Re=13700\nCDN 收缩扩张管道 Re=12600\nduct 方管 Re在文件名中包含,比如duct_Re1100.csv代表Re=1100\nperhill 周期山 Re=5600,文件名后面', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:08:27.000+08:00', | |||||
| id: 122, | |||||
| visits: 3, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'WIDERFace', | |||||
| identifier: 'public_dataset_20250526094839', | |||||
| description: | |||||
| '32,203张图像,并对393,703张像样本图像中所描述的在尺度、姿势和遮挡方面具有高度可变性的面孔进行标记。较宽的人脸数据集基于61个事件类进行组织。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:08:18.000+08:00', | |||||
| id: 123, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'StanfordSentimentTreebank', | |||||
| identifier: 'public_dataset_20250526095521', | |||||
| description: | |||||
| '用于情感分析的数据集,其中包含11855个句子的语法分析树中215154个短语的细粒度情感标签,并为情感组成提出了新挑战。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:08:12.000+08:00', | |||||
| id: 124, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'COCO', | |||||
| identifier: 'public_dataset_20250526100341', | |||||
| description: | |||||
| 'COCO是大规模的对象检测,分割和字幕数据集。 它包含:330K图像(标为> 200K),150万个对象实例,80个对象类别。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:07:56.000+08:00', | |||||
| id: 125, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'BillionWords', | |||||
| identifier: 'public_dataset_20250526101006', | |||||
| description: '该项目的目的是为语言建模实验提供标准的培训和测试设置,包含10亿字。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:07:47.000+08:00', | |||||
| id: 126, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'car_ims', | |||||
| identifier: 'public_dataset_20250527084401', | |||||
| description: | |||||
| '斯坦福汽车数据集包含196类汽车的16,185张图像。数据被分为8,144个训练图像和8,041个测试图像,其中每个类别已大致分为50-50个分割。', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:07:20.000+08:00', | |||||
| id: 128, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| { | |||||
| name: 'IU-xrays', | |||||
| identifier: 'public_dataset_20250527093542', | |||||
| description: '放射图像', | |||||
| is_public: true, | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:07:12.000+08:00', | |||||
| id: 129, | |||||
| visits: 2, | |||||
| praises_count: 0, | |||||
| create_by: 'fanshuai', | |||||
| owner: 'fanshuai', | |||||
| }, | |||||
| ], | |||||
| pageable: { | |||||
| sort: { | |||||
| sorted: false, | |||||
| unsorted: true, | |||||
| empty: true, | |||||
| }, | |||||
| pageNumber: 0, | |||||
| pageSize: 20, | |||||
| offset: 0, | |||||
| unpaged: false, | |||||
| paged: true, | |||||
| }, | |||||
| last: false, | |||||
| totalElements: 39, | |||||
| totalPages: 2, | |||||
| first: true, | |||||
| number: 0, | |||||
| sort: { | |||||
| sorted: false, | |||||
| unsorted: true, | |||||
| empty: true, | |||||
| }, | |||||
| numberOfElements: 20, | |||||
| size: 20, | |||||
| empty: false, | |||||
| }, | |||||
| }, | |||||
| 'GET /api/mmp/workspace/getPublicModels': { | |||||
| msg: '操作成功', | |||||
| code: 200, | |||||
| data: { | |||||
| content: [ | |||||
| { | |||||
| id: 109, | |||||
| name: '介电', | |||||
| create_by: 'ceshi', | |||||
| description: '介电材料模型', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:09:54.000+08:00', | |||||
| owner: 'ceshi', | |||||
| identifier: 'public_model_20250522110231', | |||||
| is_public: true, | |||||
| praises_count: 2, | |||||
| }, | |||||
| { | |||||
| id: 156, | |||||
| name: 'ChatGLM2-6B', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上', | |||||
| time_ago: '2个月前', | |||||
| full_last_update_time: '2025-06-20T16:09:02.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528093916', | |||||
| is_public: true, | |||||
| praises_count: 1, | |||||
| }, | |||||
| { | |||||
| id: 155, | |||||
| name: '鹏城·脑海(原鹏城·盘古)α-2.6B-CPU', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| '「鹏城·盘古α」由以鹏城实验室为首的技术团队联合攻关,首次基于“鹏城云脑Ⅱ”和国产MindSpore框架的自动混合并行模式实现在2048卡算力集群上的大规模分布式训练,训练出业界首个2000亿参数以中文为核心的预训练生成语言模型。鹏城·盘古α预训练模型支持丰富的场景应用,在知识问答、知识检索、知识推理、阅读理解等文本生成领域表现突出,具备很强的小样本学习能力。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:03:36.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528093254', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 157, | |||||
| name: 'ernie-3.0-base-zh', | |||||
| create_by: 'fanshuai', | |||||
| description: '大规模知识增强预训练,用于语言理解和生成', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:03:08.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528094825', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 158, | |||||
| name: 'FastChat-T5', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'FastChat-T5是一款开源聊天机器人,通过微调Flan-t5-xl (3B参数)并基于从ShareGPT.收集的用户共享对话进行训练。它基于编码器-解码器变压器架构,能够自回归生成对用户输入的响应。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:03:19.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528101831', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 159, | |||||
| name: 'Kolors-IP-Adapter-Plus', | |||||
| create_by: 'fanshuai', | |||||
| description: '基于Kolors-Basemodel提供了IP-Adapter-Plus的权重和推理代码', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:03:01.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528102217', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 160, | |||||
| name: 'Florence-2-base', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'Florence-2是一款先进的视觉基础模型,采用提示式方法处理广泛的视觉和视觉-语言任务。Florence-2能够通过简单的文本提示来执行诸如字幕生成、物体检测和分割等任务。该模型利用了包含54亿个注释的FLD-5B数据集,这些注释覆盖了1.26亿张图像,从而掌握了多任务学习。模型的序列到序列架构使其在零样本和微调设置中表现出色,证明了其作为竞争性视觉基础模型的实力。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:55.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528102750', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 161, | |||||
| name: 'E5-base', | |||||
| create_by: 'fanshuai', | |||||
| description: '弱监督对比预训练的文本嵌入。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:48.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528103059', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 162, | |||||
| name: 'Mini-InternVL-Chat', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| '使用了与InternVL 1相同的数据来训练这个较小的模型。此外,由于较小模型的训练成本较低,我们在训练时采用了8K的上下文长度。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:39.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528105945', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 163, | |||||
| name: 'Verdict-Classifier', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| '该模型是基于xlm-roberta-base的微调版本,基于谷歌事实核查工具API提供的2,500条去重多语言判决,并通过谷歌云翻译API转换成65种语言', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:27.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528110545', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 164, | |||||
| name: 'Text2Vec-Base-Multilingual', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| '这是一个CoSENT(余弦句子)模型,它将句子映射到一个384维的密集向量空间,并可用于任务,例如句子嵌入、文本匹配或语义搜索。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:19.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528110858', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 167, | |||||
| name: 'Latex-OCR', | |||||
| create_by: 'fanshuai', | |||||
| description: '识别图像中的数学公式并转换为Latex源码。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T18:02:09.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528112153', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 169, | |||||
| name: 'XLNet', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'XLNet是一种基于新型广义置换语言建模目标的新型无监督语言表示学习方法。此外,XLNet采用Transformer-XL作为骨干模型,在处理长上下文的语言任务中表现出色。总体而言,XLNet在包括问答、自然语言推理、情感分析和文档排序在内的多种下游语言任务中取得了最先进的(SOTA)成果。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-28T17:29:05.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250528172905', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 173, | |||||
| name: ' GTE-base', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'GTE模型由阿里巴巴达摩学院训练。这些模型主要基于BERT框架,目前提供三种不同规模的版本,分别是GTE-large、GTE-base和GTE-small。GTE模型在大规模的相关文本对语料库上进行训练,涵盖了广泛的领域和场景。这使得GTE模型能够应用于文本嵌入的多种下游任务,如信息检索、语义文本相似性分析、文本重排序等。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T09:14:15.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529091415', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 174, | |||||
| name: 'Tiny-lm', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| '此仓库提供了一个小型的1600万参数语言模型,该模型基于英文和日文维基百科数据训练。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T09:18:57.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529091857', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 175, | |||||
| name: "Snowflake's Arctic-embed-s", | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'snowflake-arctic-embed是一套文本嵌入模型,专注于创建高性能的高质量检索模型。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T09:23:58.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529092358', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 176, | |||||
| name: 'ViTMatte model', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'ViTMatte是一种简单的图像抠图方法,旨在准确估计图像中的前景物体。该模型由一个Vision Transformer(ViT)和一个轻量级头部组成。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T09:41:07.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529094107', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 177, | |||||
| name: 'Wartortle', | |||||
| create_by: 'fanshuai', | |||||
| description: '此模型专为语义自动补全功能而设计。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T09:44:43.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529094443', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 179, | |||||
| name: 'Cerebras-GPT', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'Cerebras-GPT系列的发布旨在通过开放架构和数据集促进对大型语言模型(LLM)扩展规律的研究,并展示在Cerebras软硬件栈上训练LLM的简便性和可扩展性。所有Cerebras-GPT模型均可在Hugging Face.上获取。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T10:01:52.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529100152', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| { | |||||
| id: 180, | |||||
| name: 'Qwen2-1.5B-Instruct-AWQ', | |||||
| create_by: 'fanshuai', | |||||
| description: | |||||
| 'Qwen2是Qwen大语言模型系列的最新成员。我们为Qwen2推出了多个基础语言模型和指令调优语言模型,参数规模从0.5亿到72亿不等,其中包括一个专家混合模型。本仓库包含1.5亿参数的指令调优Qwen2模型。', | |||||
| time_ago: '3个月前', | |||||
| full_last_update_time: '2025-05-29T10:13:53.000+08:00', | |||||
| owner: 'fanshuai', | |||||
| identifier: 'public_model_20250529101353', | |||||
| is_public: true, | |||||
| praises_count: 0, | |||||
| }, | |||||
| ], | |||||
| pageable: { | |||||
| sort: { | |||||
| sorted: false, | |||||
| unsorted: true, | |||||
| empty: true, | |||||
| }, | |||||
| pageNumber: 0, | |||||
| pageSize: 20, | |||||
| offset: 0, | |||||
| unpaged: false, | |||||
| paged: true, | |||||
| }, | |||||
| last: false, | |||||
| totalElements: 28, | |||||
| totalPages: 2, | |||||
| first: true, | |||||
| number: 0, | |||||
| sort: { | |||||
| sorted: false, | |||||
| unsorted: true, | |||||
| empty: true, | |||||
| }, | |||||
| numberOfElements: 20, | |||||
| size: 20, | |||||
| empty: false, | |||||
| }, | |||||
| }, | |||||
| }); | |||||
| @@ -1,115 +0,0 @@ | |||||
| import { Request, Response } from 'express'; | |||||
| const getNotices = (req: Request, res: Response) => { | |||||
| res.json({ | |||||
| data: [ | |||||
| { | |||||
| id: '000000001', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr', | |||||
| title: '你收到了 14 份新周报', | |||||
| datetime: '2017-08-09', | |||||
| type: 'notification', | |||||
| }, | |||||
| { | |||||
| id: '000000002', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr', | |||||
| title: '你推荐的 曲妮妮 已通过第三轮面试', | |||||
| datetime: '2017-08-08', | |||||
| type: 'notification', | |||||
| }, | |||||
| { | |||||
| id: '000000003', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr', | |||||
| title: '这种模板可以区分多种通知类型', | |||||
| datetime: '2017-08-07', | |||||
| read: true, | |||||
| type: 'notification', | |||||
| }, | |||||
| { | |||||
| id: '000000004', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr', | |||||
| title: '左侧图标用于区分不同的类型', | |||||
| datetime: '2017-08-07', | |||||
| type: 'notification', | |||||
| }, | |||||
| { | |||||
| id: '000000005', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr', | |||||
| title: '内容不要超过两行字,超出时自动截断', | |||||
| datetime: '2017-08-07', | |||||
| type: 'notification', | |||||
| }, | |||||
| { | |||||
| id: '000000006', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr', | |||||
| title: '曲丽丽 评论了你', | |||||
| description: '描述信息描述信息描述信息', | |||||
| datetime: '2017-08-07', | |||||
| type: 'message', | |||||
| clickClose: true, | |||||
| }, | |||||
| { | |||||
| id: '000000007', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr', | |||||
| title: '朱偏右 回复了你', | |||||
| description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', | |||||
| datetime: '2017-08-07', | |||||
| type: 'message', | |||||
| clickClose: true, | |||||
| }, | |||||
| { | |||||
| id: '000000008', | |||||
| avatar: | |||||
| 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr', | |||||
| title: '标题', | |||||
| description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', | |||||
| datetime: '2017-08-07', | |||||
| type: 'message', | |||||
| clickClose: true, | |||||
| }, | |||||
| { | |||||
| id: '000000009', | |||||
| title: '任务名称', | |||||
| description: '任务需要在 2017-01-12 20:00 前启动', | |||||
| extra: '未开始', | |||||
| status: 'todo', | |||||
| type: 'event', | |||||
| }, | |||||
| { | |||||
| id: '000000010', | |||||
| title: '第三方紧急代码变更', | |||||
| description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', | |||||
| extra: '马上到期', | |||||
| status: 'urgent', | |||||
| type: 'event', | |||||
| }, | |||||
| { | |||||
| id: '000000011', | |||||
| title: '信息安全考试', | |||||
| description: '指派竹尔于 2017-01-09 前完成更新并发布', | |||||
| extra: '已耗时 8 天', | |||||
| status: 'doing', | |||||
| type: 'event', | |||||
| }, | |||||
| { | |||||
| id: '000000012', | |||||
| title: 'ABCD 版本发布', | |||||
| description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', | |||||
| extra: '进行中', | |||||
| status: 'processing', | |||||
| type: 'event', | |||||
| }, | |||||
| ], | |||||
| }); | |||||
| }; | |||||
| export default { | |||||
| 'GET /api/notices': getNotices, | |||||
| }; | |||||
| @@ -1,324 +0,0 @@ | |||||
| module.exports = { | |||||
| 'GET /api/currentUser': { | |||||
| data: { | |||||
| name: 'Serati Ma', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', | |||||
| userid: '00000001', | |||||
| email: 'antdesign@alipay.com', | |||||
| signature: '海纳百川,有容乃大', | |||||
| title: '交互专家', | |||||
| group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', | |||||
| tags: [ | |||||
| { key: '0', label: '很有想法的' }, | |||||
| { key: '1', label: '专注设计' }, | |||||
| { key: '2', label: '辣~' }, | |||||
| { key: '3', label: '大长腿' }, | |||||
| { key: '4', label: '川妹子' }, | |||||
| { key: '5', label: '海纳百川' }, | |||||
| ], | |||||
| notifyCount: 12, | |||||
| unreadCount: 11, | |||||
| country: 'China', | |||||
| geographic: { | |||||
| province: { label: '浙江省', key: '330000' }, | |||||
| city: { label: '杭州市', key: '330100' }, | |||||
| }, | |||||
| address: '西湖区工专路 77 号', | |||||
| phone: '0752-268888888', | |||||
| }, | |||||
| }, | |||||
| 'GET /api/rule': { | |||||
| data: [ | |||||
| { | |||||
| key: 99, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 99', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 503, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 81, | |||||
| }, | |||||
| { | |||||
| key: 98, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 98', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 164, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 12, | |||||
| }, | |||||
| { | |||||
| key: 97, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 97', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 174, | |||||
| status: '1', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 81, | |||||
| }, | |||||
| { | |||||
| key: 96, | |||||
| disabled: true, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 96', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 914, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 7, | |||||
| }, | |||||
| { | |||||
| key: 95, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 95', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 698, | |||||
| status: '2', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 82, | |||||
| }, | |||||
| { | |||||
| key: 94, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 94', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 488, | |||||
| status: '1', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 14, | |||||
| }, | |||||
| { | |||||
| key: 93, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 93', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 580, | |||||
| status: '2', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 77, | |||||
| }, | |||||
| { | |||||
| key: 92, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 92', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 244, | |||||
| status: '3', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 58, | |||||
| }, | |||||
| { | |||||
| key: 91, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 91', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 959, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 66, | |||||
| }, | |||||
| { | |||||
| key: 90, | |||||
| disabled: true, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 90', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 958, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 72, | |||||
| }, | |||||
| { | |||||
| key: 89, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 89', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 301, | |||||
| status: '2', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 2, | |||||
| }, | |||||
| { | |||||
| key: 88, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 88', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 277, | |||||
| status: '1', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 12, | |||||
| }, | |||||
| { | |||||
| key: 87, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 87', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 810, | |||||
| status: '1', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 82, | |||||
| }, | |||||
| { | |||||
| key: 86, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 86', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 780, | |||||
| status: '3', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 22, | |||||
| }, | |||||
| { | |||||
| key: 85, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 85', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 705, | |||||
| status: '3', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 12, | |||||
| }, | |||||
| { | |||||
| key: 84, | |||||
| disabled: true, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 84', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 203, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 79, | |||||
| }, | |||||
| { | |||||
| key: 83, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 83', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 491, | |||||
| status: '2', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 59, | |||||
| }, | |||||
| { | |||||
| key: 82, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 82', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 73, | |||||
| status: '0', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 100, | |||||
| }, | |||||
| { | |||||
| key: 81, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', | |||||
| name: 'TradeCode 81', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 406, | |||||
| status: '3', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 61, | |||||
| }, | |||||
| { | |||||
| key: 80, | |||||
| disabled: false, | |||||
| href: 'https://ant.design', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', | |||||
| name: 'TradeCode 80', | |||||
| owner: '曲丽丽', | |||||
| desc: '这是一段描述', | |||||
| callNo: 112, | |||||
| status: '2', | |||||
| updatedAt: '2022-12-06T05:00:57.040Z', | |||||
| createdAt: '2022-12-06T05:00:57.040Z', | |||||
| progress: 20, | |||||
| }, | |||||
| ], | |||||
| total: 100, | |||||
| success: true, | |||||
| pageSize: 20, | |||||
| current: 1, | |||||
| }, | |||||
| 'POST /api/login/outLogin': { data: {}, success: true }, | |||||
| 'POST /api/login/account': { | |||||
| status: 'ok', | |||||
| type: 'account', | |||||
| currentAuthority: 'admin', | |||||
| }, | |||||
| }; | |||||
| @@ -1,203 +0,0 @@ | |||||
| import { Request, Response } from 'express'; | |||||
| const waitTime = (time: number = 100) => { | |||||
| return new Promise((resolve) => { | |||||
| setTimeout(() => { | |||||
| resolve(true); | |||||
| }, time); | |||||
| }); | |||||
| }; | |||||
| async function getFakeCaptcha(req: Request, res: Response) { | |||||
| await waitTime(2000); | |||||
| return res.json('captcha-xxx'); | |||||
| } | |||||
| const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; | |||||
| /** | |||||
| * 当前用户的权限,如果为空代表没登录 | |||||
| * current user access, if is '', user need login | |||||
| * 如果是 pro 的预览,默认是有权限的 | |||||
| */ | |||||
| let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; | |||||
| const getAccess = () => { | |||||
| return access; | |||||
| }; | |||||
| // 代码中会兼容本地 service mock 以及部署站点的静态数据 | |||||
| export default { | |||||
| // 支持值为 Object 和 Array | |||||
| 'GET /api/currentUser': (req: Request, res: Response) => { | |||||
| if (!getAccess()) { | |||||
| res.status(401).send({ | |||||
| data: { | |||||
| isLogin: false, | |||||
| }, | |||||
| errorCode: '401', | |||||
| errorMessage: '请先登录!', | |||||
| success: true, | |||||
| }); | |||||
| return; | |||||
| } | |||||
| res.send({ | |||||
| success: true, | |||||
| data: { | |||||
| name: 'Serati Ma', | |||||
| avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', | |||||
| userid: '00000001', | |||||
| email: 'antdesign@alipay.com', | |||||
| signature: '海纳百川,有容乃大', | |||||
| title: '交互专家', | |||||
| group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', | |||||
| tags: [ | |||||
| { | |||||
| key: '0', | |||||
| label: '很有想法的', | |||||
| }, | |||||
| { | |||||
| key: '1', | |||||
| label: '专注设计', | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: '辣~', | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: '大长腿', | |||||
| }, | |||||
| { | |||||
| key: '4', | |||||
| label: '川妹子', | |||||
| }, | |||||
| { | |||||
| key: '5', | |||||
| label: '海纳百川', | |||||
| }, | |||||
| ], | |||||
| notifyCount: 12, | |||||
| unreadCount: 11, | |||||
| country: 'China', | |||||
| access: getAccess(), | |||||
| geographic: { | |||||
| province: { | |||||
| label: '浙江省', | |||||
| key: '330000', | |||||
| }, | |||||
| city: { | |||||
| label: '杭州市', | |||||
| key: '330100', | |||||
| }, | |||||
| }, | |||||
| address: '西湖区工专路 77 号', | |||||
| phone: '0752-268888888', | |||||
| }, | |||||
| }); | |||||
| }, | |||||
| // GET POST 可省略 | |||||
| 'GET /api/users': [ | |||||
| { | |||||
| key: '1', | |||||
| name: 'John Brown', | |||||
| age: 32, | |||||
| address: 'New York No. 1 Lake Park', | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| name: 'Jim Green', | |||||
| age: 42, | |||||
| address: 'London No. 1 Lake Park', | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| name: 'Joe Black', | |||||
| age: 32, | |||||
| address: 'Sidney No. 1 Lake Park', | |||||
| }, | |||||
| ], | |||||
| 'POST /api/login/account': async (req: Request, res: Response) => { | |||||
| const { password, username, type } = req.body; | |||||
| await waitTime(2000); | |||||
| if (password === 'ant.design' && username === 'admin') { | |||||
| res.send({ | |||||
| status: 'ok', | |||||
| type, | |||||
| currentAuthority: 'admin', | |||||
| }); | |||||
| access = 'admin'; | |||||
| return; | |||||
| } | |||||
| if (password === 'ant.design' && username === 'user') { | |||||
| res.send({ | |||||
| status: 'ok', | |||||
| type, | |||||
| currentAuthority: 'user', | |||||
| }); | |||||
| access = 'user'; | |||||
| return; | |||||
| } | |||||
| if (type === 'mobile') { | |||||
| res.send({ | |||||
| status: 'ok', | |||||
| type, | |||||
| currentAuthority: 'admin', | |||||
| }); | |||||
| access = 'admin'; | |||||
| return; | |||||
| } | |||||
| res.send({ | |||||
| status: 'error', | |||||
| type, | |||||
| currentAuthority: 'guest', | |||||
| }); | |||||
| access = 'guest'; | |||||
| }, | |||||
| 'POST /api/login/outLogin': (req: Request, res: Response) => { | |||||
| access = ''; | |||||
| res.send({ data: {}, success: true }); | |||||
| }, | |||||
| 'POST /api/register': (req: Request, res: Response) => { | |||||
| res.send({ status: 'ok', currentAuthority: 'user', success: true }); | |||||
| }, | |||||
| 'GET /api/500': (req: Request, res: Response) => { | |||||
| res.status(500).send({ | |||||
| timestamp: 1513932555104, | |||||
| status: 500, | |||||
| error: 'error', | |||||
| message: 'error', | |||||
| path: '/base/category/list', | |||||
| }); | |||||
| }, | |||||
| 'GET /api/404': (req: Request, res: Response) => { | |||||
| res.status(404).send({ | |||||
| timestamp: 1513932643431, | |||||
| status: 404, | |||||
| error: 'Not Found', | |||||
| message: 'No message available', | |||||
| path: '/base/category/list/2121212', | |||||
| }); | |||||
| }, | |||||
| 'GET /api/403': (req: Request, res: Response) => { | |||||
| res.status(403).send({ | |||||
| timestamp: 1513932555104, | |||||
| status: 403, | |||||
| error: 'Forbidden', | |||||
| message: 'Forbidden', | |||||
| path: '/base/category/list', | |||||
| }); | |||||
| }, | |||||
| 'GET /api/401': (req: Request, res: Response) => { | |||||
| res.status(401).send({ | |||||
| timestamp: 1513932555104, | |||||
| status: 401, | |||||
| error: 'Unauthorized', | |||||
| message: 'Unauthorized', | |||||
| path: '/base/category/list', | |||||
| }); | |||||
| }, | |||||
| 'GET /api/login/captcha': getFakeCaptcha, | |||||
| }; | |||||
| @@ -8,7 +8,7 @@ | |||||
| "build": "max build", | "build": "max build", | ||||
| "deploy": "npm run build && npm run gh-pages", | "deploy": "npm run build && npm run gh-pages", | ||||
| "dev": "npm run start:dev", | "dev": "npm run start:dev", | ||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:dev", | |||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:mock", | |||||
| "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | ||||
| "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | ||||
| "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | ||||
| @@ -35,7 +35,7 @@ | |||||
| "serve": "umi-serve", | "serve": "umi-serve", | ||||
| "start": "cross-env UMI_ENV=dev max dev", | "start": "cross-env UMI_ENV=dev max dev", | ||||
| "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | ||||
| "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev", | |||||
| "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", | |||||
| "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | ||||
| "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | ||||
| "storybook": "storybook dev -p 6006", | "storybook": "storybook dev -p 6006", | ||||
| @@ -1,6 +1,6 @@ | |||||
| @font-face { | @font-face { | ||||
| font-family: Alibaba; | font-family: Alibaba; | ||||
| src: url('./ALIBABA-PUHUITI-MEDIUM.TTF'); | |||||
| src: url('./ALIBABA-PUHUITI-REGULAR.TTF'); | |||||
| font-display: swap; | font-display: swap; | ||||
| } | } | ||||
| @@ -10,4 +10,17 @@ | |||||
| url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */ | url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */ | ||||
| url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */ | url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */ | ||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | font-display: swap; /* 优化页面加载时的字体显示 */ | ||||
| } | |||||
| @font-face { | |||||
| font-family: 'WenYiHei'; | |||||
| src: url('./WenYiHei.ttf'); | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | |||||
| @font-face { | |||||
| font-family: 'YouSheBiaoTiHei'; | |||||
| src: url('./YouSheBiaoTiHei.ttf'); | |||||
| font-display: swap; /* 优化页面加载时的字体显示 */ | |||||
| } | } | ||||
| @@ -0,0 +1,52 @@ | |||||
| (function (doc, win) { | |||||
| 'use strict'; | |||||
| // 配置项 | |||||
| const config = { | |||||
| // 断点设置(单位:px) | |||||
| // 1440 1560 1680 1800 1920 2040 2160 2280 2400 2520 | |||||
| breakpoints: [ | |||||
| { minWidth: 2520, fontSize: 22 }, // 21 | |||||
| { minWidth: 2280, fontSize: 20 }, // 19 | |||||
| { minWidth: 2040, fontSize: 18 }, // 17 | |||||
| { minWidth: 1800, fontSize: 16 }, // 15 | |||||
| { minWidth: 1560, fontSize: 14 }, // 13 | |||||
| { minWidth: 0, fontSize: 12 }, | |||||
| ], | |||||
| delay: 300 // 防抖延迟(ms) | |||||
| }; | |||||
| const docEl = doc.documentElement; | |||||
| const resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'; | |||||
| let resizeTimeout; | |||||
| // 计算当前宽度对应的字体大小 | |||||
| function calculateFontSize() { | |||||
| const clientWidth = docEl.clientWidth || win.innerWidth; | |||||
| if (!clientWidth) return; | |||||
| // 从大到小匹配断点 | |||||
| const targetBreakpoint = config.breakpoints.find( | |||||
| bp => clientWidth >= bp.minWidth | |||||
| ); | |||||
| // 设置字体大小 | |||||
| docEl.style.fontSize = targetBreakpoint.fontSize + 'px'; | |||||
| // 调试输出(可选) | |||||
| console.debug('[REM-Resize]', | |||||
| 'Width:', clientWidth + 'px', | |||||
| 'Font-Size:', targetBreakpoint.fontSize + 'px'); | |||||
| } | |||||
| // 防抖处理 | |||||
| function handleResize() { | |||||
| clearTimeout(resizeTimeout); | |||||
| resizeTimeout = setTimeout(calculateFontSize, config.delay); | |||||
| } | |||||
| calculateFontSize(); | |||||
| // 初始化监听 | |||||
| win.addEventListener(resizeEvt, handleResize, false); | |||||
| })(document, window); | |||||
| @@ -0,0 +1,44 @@ | |||||
| // rem-resize.js | |||||
| (function (doc, win) { | |||||
| 'use strict'; | |||||
| // 配置项 | |||||
| const config = { | |||||
| designWidth: 1920, // 设计稿宽度 | |||||
| baseFontSize: 16, // 基础字体大小(设计稿下1rem = 16px) | |||||
| minFontSize: 12, // 最小字体限制 | |||||
| maxFontSize: 24, // 最大字体限制 | |||||
| delay: 300, // 窗口变化时的延迟执行(ms) | |||||
| }; | |||||
| const docEl = doc.documentElement; | |||||
| const resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'; | |||||
| let resizeTimeout; | |||||
| function calculateFontSize() { | |||||
| const clientWidth = docEl.clientWidth || win.innerWidth; | |||||
| if (!clientWidth) return; | |||||
| const fontSize = Math.min( | |||||
| Math.max((clientWidth / config.designWidth) * config.baseFontSize, config.minFontSize), | |||||
| config.maxFontSize, | |||||
| ); | |||||
| docEl.style.fontSize = fontSize + 'px'; | |||||
| // 可选:调试输出 | |||||
| if (win.console) { | |||||
| console.debug('[REM-Resize]', 'width:', clientWidth, 'font-size:', fontSize + 'px'); | |||||
| } | |||||
| } | |||||
| function resizeHandler() { | |||||
| clearTimeout(resizeTimeout); | |||||
| resizeTimeout = setTimeout(calculateFontSize, config.delay); | |||||
| } | |||||
| calculateFontSize(); | |||||
| // 初始化监听 | |||||
| win.addEventListener(resizeEvt, resizeHandler, false); | |||||
| })(document, window); | |||||
| @@ -20,7 +20,9 @@ import { | |||||
| setRemoteMenu, | setRemoteMenu, | ||||
| } from './services/session'; | } from './services/session'; | ||||
| import './styles/menu.less'; | import './styles/menu.less'; | ||||
| import { needAuth } from './utils'; | |||||
| import { isLoginPage, needAuth } from './utils'; | |||||
| import { HomeUrl } from './utils/constant'; | |||||
| import { closeAllModals } from './utils/modal'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| export { requestConfig as request } from './requestConfig'; | export { requestConfig as request } from './requestConfig'; | ||||
| @@ -29,28 +31,24 @@ export { requestConfig as request } from './requestConfig'; | |||||
| */ | */ | ||||
| export async function getInitialState(): Promise<GlobalInitialState> { | export async function getInitialState(): Promise<GlobalInitialState> { | ||||
| const fetchUserInfo = async () => { | const fetchUserInfo = async () => { | ||||
| globalGetSeverTime(); | |||||
| try { | try { | ||||
| globalGetSeverTime(); | |||||
| const response = await getUserInfo(); | const response = await getUserInfo(); | ||||
| return { | return { | ||||
| ...response.user, | ...response.user, | ||||
| avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), | avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), | ||||
| permissions: response.permissions, | permissions: response.permissions, | ||||
| roles: response.roles, | |||||
| roleNames: response.user.roles, | |||||
| roleNames: response.roles, | |||||
| } as API.CurrentUser; | } as API.CurrentUser; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error('getInitialState', error); | console.error('getInitialState', error); | ||||
| gotoLoginPage(); | |||||
| gotoLoginPage(true); | |||||
| } | } | ||||
| return undefined; | return undefined; | ||||
| }; | }; | ||||
| // 如果不是登录页面,执行 | |||||
| const { location } = history; | |||||
| // console.log('getInitialState', needAuth(location.pathname)); | |||||
| if (needAuth(location.pathname)) { | |||||
| const token = getAccessToken(); | |||||
| if (token) { | |||||
| const currentUser = await fetchUserInfo(); | const currentUser = await fetchUserInfo(); | ||||
| return { | return { | ||||
| fetchUserInfo, | fetchUserInfo, | ||||
| @@ -71,9 +69,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| return { | return { | ||||
| ErrorBoundary: ErrorBoundary, | ErrorBoundary: ErrorBoundary, | ||||
| rightContentRender: false, | rightContentRender: false, | ||||
| waterMarkProps: { | |||||
| // content: initialState?.currentUser?.nickName, | |||||
| }, | |||||
| menu: { | menu: { | ||||
| locale: false, | locale: false, | ||||
| // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request | // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request | ||||
| @@ -84,45 +79,9 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| if (!initialState?.currentUser?.userId) { | if (!initialState?.currentUser?.userId) { | ||||
| return []; | return []; | ||||
| } | } | ||||
| // console.log('get menus') | |||||
| // initialState.currentUser 中包含了所有用户信息 | |||||
| // console.log('get routers') | |||||
| // setInitialState((preInitialState) => ({ | |||||
| // ...preInitialState, | |||||
| // menus, | |||||
| // })); | |||||
| return getRemoteMenu(); | return getRemoteMenu(); | ||||
| }, | }, | ||||
| }, | }, | ||||
| onPageChange: () => { | |||||
| const { location } = history; | |||||
| // 如果没有登录,重定向到 login | |||||
| if (!initialState?.currentUser && needAuth(location.pathname)) { | |||||
| gotoLoginPage(); | |||||
| } | |||||
| }, | |||||
| layoutBgImgList: [ | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr', | |||||
| left: 85, | |||||
| bottom: 100, | |||||
| height: '303px', | |||||
| }, | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr', | |||||
| bottom: -68, | |||||
| right: -45, | |||||
| height: '303px', | |||||
| }, | |||||
| { | |||||
| src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr', | |||||
| bottom: 0, | |||||
| left: 0, | |||||
| width: '331px', | |||||
| }, | |||||
| ], | |||||
| // 自定义 403 页面 | |||||
| // unAccessible: <div>unAccessible</div>, | |||||
| childrenRender: (children) => { | childrenRender: (children) => { | ||||
| // 增加一个 loading 的状态 | // 增加一个 loading 的状态 | ||||
| // if (initialState?.loading) return <PageLoading />; | // if (initialState?.loading) return <PageLoading />; | ||||
| @@ -159,10 +118,28 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||||
| }; | }; | ||||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | ||||
| // console.log('onRouteChange'); | |||||
| // 路由切换时,尤其是回退时,关闭打开的弹框 | |||||
| closeAllModals(); | |||||
| const { location } = e; | const { location } = e; | ||||
| const pathname = location.pathname; | |||||
| const token = getAccessToken(); | |||||
| // 没有 token,跳转到登录页面 | |||||
| if (!token && needAuth(pathname)) { | |||||
| gotoLoginPage(false); | |||||
| return; | |||||
| } | |||||
| // 有 token, 登录页面直接跳转到首页 | |||||
| if (token && isLoginPage(pathname)) { | |||||
| history.push(HomeUrl); | |||||
| } | |||||
| const menus = getRemoteMenu(); | const menus = getRemoteMenu(); | ||||
| // console.log('onRouteChange', menus); | |||||
| if (menus === null && needAuth(location.pathname)) { | |||||
| // 没有菜单,刷新页面 | |||||
| if (menus === null && needAuth(pathname)) { | |||||
| history.go(0); | history.go(0); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -179,10 +156,12 @@ export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | |||||
| export function render(oldRender: () => void) { | export function render(oldRender: () => void) { | ||||
| // console.log('render'); | // console.log('render'); | ||||
| const token = getAccessToken(); | const token = getAccessToken(); | ||||
| if (!token || token?.length === 0) { | |||||
| if (!token) { | |||||
| oldRender(); | oldRender(); | ||||
| return; | return; | ||||
| } | } | ||||
| // 有 token,获取路由 | |||||
| getRoutersInfo() | getRoutersInfo() | ||||
| .then((res) => { | .then((res) => { | ||||
| setRemoteMenu(res); | setRemoteMenu(res); | ||||
| @@ -1,11 +1,22 @@ | |||||
| .code-config-item { | .code-config-item { | ||||
| position: relative; | position: relative; | ||||
| width: calc(25% - 7.5px); | |||||
| width: calc(33.33% - 7px); | |||||
| padding: 15px; | padding: 15px; | ||||
| background-color: .addAlpha(@primary-color, 0.04) []; | background-color: .addAlpha(@primary-color, 0.04) []; | ||||
| border: 1px solid transparent; | border: 1px solid transparent; | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| cursor: pointer; | |||||
| &__checkbox { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| :global { | |||||
| .ant-checkbox + span { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__name { | &__name { | ||||
| margin-right: 8px; | margin-right: 8px; | ||||
| @@ -38,6 +49,8 @@ | |||||
| margin-bottom: 10px !important; | margin-bottom: 10px !important; | ||||
| color: @text-color-secondary; | color: @text-color-secondary; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| cursor: pointer; | |||||
| word-break: break-all; | |||||
| } | } | ||||
| &__branch { | &__branch { | ||||
| @@ -46,11 +59,17 @@ | |||||
| } | } | ||||
| &:hover { | &:hover { | ||||
| background-color: .addAlpha(@primary-color, 0.08) []; | |||||
| } | |||||
| &--active { | |||||
| border-color: @primary-color; | border-color: @primary-color; | ||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | ||||
| } | } | ||||
| &:hover &__name { | |||||
| &--active &__name { | |||||
| color: @primary-color; | color: @primary-color; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,25 +1,51 @@ | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { Flex, Typography } from 'antd'; | |||||
| import { getGitUrl } from '@/utils'; | |||||
| import { Checkbox, Flex, Typography } from 'antd'; | |||||
| import { type CheckboxChangeEvent } from 'antd/es/checkbox'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| item: CodeConfigData; | item: CodeConfigData; | ||||
| onClick?: (item: CodeConfigData) => void; | |||||
| checked: boolean; | |||||
| onChange?: (item: CodeConfigData, checked: boolean) => void; | |||||
| }; | }; | ||||
| function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| function CodeConfigItem({ item, checked, onChange }: CodeConfigItemProps) { | |||||
| const [isEllipsis, setIsEllipsis] = useState(false); | const [isEllipsis, setIsEllipsis] = useState(false); | ||||
| const openProject = (e: React.MouseEvent<HTMLElement, MouseEvent>) => { | |||||
| e.stopPropagation(); | |||||
| const { git_url, git_branch } = item; | |||||
| const url = getGitUrl(git_url, git_branch); | |||||
| window.open(url, '_blank'); | |||||
| }; | |||||
| const handleChange = (e: CheckboxChangeEvent) => { | |||||
| onChange?.(item, e.target.checked); | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | |||||
| <div | |||||
| id={`code-config-item-${item.id}`} | |||||
| className={classNames(styles['code-config-item'], { | |||||
| [styles['code-config-item--active']]: checked, | |||||
| })} | |||||
| > | |||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}> | ||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| <Checkbox | |||||
| className={styles['code-config-item__checkbox']} | |||||
| checked={checked} | |||||
| onChange={handleChange} | |||||
| > | > | ||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__name']} | |||||
| ellipsis={{ tooltip: item.code_repo_name }} | |||||
| > | |||||
| {item.code_repo_name} | |||||
| </Typography.Paragraph> | |||||
| </Checkbox> | |||||
| <div | <div | ||||
| className={classNames( | className={classNames( | ||||
| styles['code-config-item__tag'], | styles['code-config-item__tag'], | ||||
| @@ -35,9 +61,10 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { | |||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ | ellipsis={{ | ||||
| rows: 2, | rows: 2, | ||||
| tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), | |||||
| tooltip: isEllipsis ? item.git_url : false, | |||||
| onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), // 必须这样,不然不能省略 | |||||
| }} | }} | ||||
| onClick={openProject} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| @@ -4,7 +4,7 @@ | |||||
| * @Description: 流水线选择代码配置表单 | * @Description: 流水线选择代码配置表单 | ||||
| */ | */ | ||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -18,7 +18,9 @@ export { | |||||
| type ParameterInputValue, | type ParameterInputValue, | ||||
| } from '../ParameterInput'; | } from '../ParameterInput'; | ||||
| type CodeSelectProps = ParameterInputProps; | |||||
| export interface CodeSelectProps extends ParameterInputProps { | |||||
| value?: CodeConfigData; | |||||
| } | |||||
| /** 代码配置选择表单组件 */ | /** 代码配置选择表单组件 */ | ||||
| function CodeSelect({ | function CodeSelect({ | ||||
| @@ -32,26 +34,18 @@ function CodeSelect({ | |||||
| }: CodeSelectProps) { | }: CodeSelectProps) { | ||||
| // 选择代码配置 | // 选择代码配置 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const defaultSelected: CodeConfigData | undefined = | |||||
| value && typeof value === 'object' ? value : undefined; | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | const { close } = openAntdModal(CodeSelectorModal, { | ||||
| defaultSelected: defaultSelected, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { id, code_repo_name, git_url, git_branch, git_user_name, git_password, ssh_key } = | |||||
| res; | |||||
| const jsonObj = { | |||||
| id, | |||||
| name: code_repo_name, | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| username: git_user_name, | |||||
| password: git_password, | |||||
| ssh_private_key: ssh_key, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { code_repo_name } = res; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...res, | |||||
| value: code_repo_name, | |||||
| showValue: code_repo_name, | showValue: code_repo_name, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| onChange?.(undefined); | onChange?.(undefined); | ||||
| @@ -17,6 +17,7 @@ | |||||
| margin-bottom: 30px; | margin-bottom: 30px; | ||||
| overflow-x: hidden; | overflow-x: hidden; | ||||
| overflow-y: auto; | overflow-y: auto; | ||||
| padding-bottom: 10px; | |||||
| } | } | ||||
| &__empty { | &__empty { | ||||
| @@ -7,7 +7,8 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { getCodeConfigListReq } from '@/services/codeConfig'; | |||||
| import { getCodeConfigListReq, getCodeConfigPageNumReq } from '@/services/codeConfig'; | |||||
| import { CustomPartial } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import type { ModalProps, PaginationProps } from 'antd'; | import type { ModalProps, PaginationProps } from 'antd'; | ||||
| import { Empty, Input, Pagination } from 'antd'; | import { Empty, Input, Pagination } from 'antd'; | ||||
| @@ -17,24 +18,68 @@ import './index.less'; | |||||
| export { type CodeConfigData }; | export { type CodeConfigData }; | ||||
| export type SelectCodeData = CustomPartial< | |||||
| CodeConfigData, | |||||
| | 'id' | |||||
| | 'code_repo_name' | |||||
| | 'git_url' | |||||
| | 'git_branch' | |||||
| | 'git_user_name' | |||||
| | 'git_password' | |||||
| | 'ssh_key' | |||||
| | 'is_public' | |||||
| >; | |||||
| export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| onOk?: (params: CodeConfigData | undefined) => void; | |||||
| defaultSelected?: SelectCodeData; | |||||
| onOk?: (params: SelectCodeData | undefined) => void; | |||||
| } | } | ||||
| /** 选择代码配置的弹窗,推荐使用函数的方式打开 */ | /** 选择代码配置的弹窗,推荐使用函数的方式打开 */ | ||||
| function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| function CodeSelectorModal({ defaultSelected, onOk, ...rest }: CodeSelectorModalProps) { | |||||
| const DefaultPageSize = 18; | |||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | const [dataList, setDataList] = useState<CodeConfigData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: 1, | |||||
| pageSize: 20, | |||||
| }); | |||||
| const [searchText, setSearchText] = useState<string | undefined>(undefined); | const [searchText, setSearchText] = useState<string | undefined>(undefined); | ||||
| const [inputText, setInputText] = useState<string | undefined>(undefined); | const [inputText, setInputText] = useState<string | undefined>(undefined); | ||||
| const [selected, setSelected] = useState(defaultSelected); | |||||
| const [isScrolled, setIsScrolled] = useState(false); | |||||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||||
| current: defaultSelected?.id ? 0 : 1, // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| useEffect(() => { | |||||
| const getCodeConfigPageNum = async (id: number, size: number) => { | |||||
| const [res] = await to( | |||||
| getCodeConfigPageNumReq(id, { | |||||
| size, | |||||
| }), | |||||
| ); | |||||
| if (res) { | |||||
| setPagination({ | |||||
| current: typeof res.data === 'number' ? Math.max(0, res.data) + 1 : 1, | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| } else { | |||||
| setPagination({ | |||||
| current: 1, | |||||
| pageSize: DefaultPageSize, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| if (defaultSelected?.id) { | |||||
| getCodeConfigPageNum(defaultSelected?.id, DefaultPageSize); | |||||
| } | |||||
| }, [defaultSelected?.id]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取数据请求 | // 获取数据请求 | ||||
| const getDataList = async () => { | const getDataList = async () => { | ||||
| // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 | |||||
| if (pagination.current === 0) { | |||||
| return; | |||||
| } | |||||
| const params = { | const params = { | ||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| @@ -50,6 +95,16 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| getDataList(); | getDataList(); | ||||
| }, [pagination, searchText]); | }, [pagination, searchText]); | ||||
| useEffect(() => { | |||||
| if (dataList.length > 0 && !isScrolled && defaultSelected?.id) { | |||||
| const selectedItem = document.getElementById(`code-config-item-${defaultSelected?.id}`); | |||||
| if (selectedItem) { | |||||
| selectedItem.scrollIntoView(); | |||||
| } | |||||
| setIsScrolled(true); | |||||
| } | |||||
| }, [isScrolled, dataList, defaultSelected?.id]); | |||||
| // 搜索 | // 搜索 | ||||
| const handleSearch = (value: string) => { | const handleSearch = (value: string) => { | ||||
| setSearchText(value); | setSearchText(value); | ||||
| @@ -59,8 +114,12 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| })); | })); | ||||
| }; | }; | ||||
| const handleClick = (item: CodeConfigData) => { | |||||
| onOk?.(item); | |||||
| const handleChange = (item: CodeConfigData, checked: boolean) => { | |||||
| if (checked) { | |||||
| setSelected(item); | |||||
| } else { | |||||
| setSelected(undefined); | |||||
| } | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -77,7 +136,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| title="选择代码配置" | title="选择代码配置" | ||||
| image={require('@/assets/img/modal-code-config.png')} | image={require('@/assets/img/modal-code-config.png')} | ||||
| width={920} | width={920} | ||||
| footer={null} | |||||
| onOk={() => onOk?.(selected)} | |||||
| destroyOnClose | destroyOnClose | ||||
| > | > | ||||
| <div className="kf-code-selector-modal"> | <div className="kf-code-selector-modal"> | ||||
| @@ -93,23 +152,31 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| prefix={ | prefix={ | ||||
| <KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} /> | <KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} /> | ||||
| } | } | ||||
| // prefix={ | |||||
| // <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} /> | |||||
| // } | |||||
| /> | /> | ||||
| {dataList?.length !== 0 ? ( | {dataList?.length !== 0 ? ( | ||||
| <> | <> | ||||
| <div className="kf-code-selector-modal__content"> | <div className="kf-code-selector-modal__content"> | ||||
| {dataList?.map((item) => ( | {dataList?.map((item) => ( | ||||
| <CodeConfigItem item={item} key={item.id} onClick={handleClick} /> | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| checked={item.id === selected?.id} | |||||
| onChange={handleChange} | |||||
| /> | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="center" | |||||
| align="end" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| defaultPageSize={DefaultPageSize} | |||||
| pageSizeOptions={[ | |||||
| DefaultPageSize, | |||||
| 2 * DefaultPageSize, | |||||
| 3 * DefaultPageSize, | |||||
| 4 * DefaultPageSize, | |||||
| 5 * DefaultPageSize, | |||||
| ]} | |||||
| showQuickJumper | showQuickJumper | ||||
| onChange={handlePageChange} | onChange={handlePageChange} | ||||
| {...pagination} | {...pagination} | ||||
| @@ -1,4 +1,5 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | ||||
| import { HomeUrl } from '@/utils/constant'; | |||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| import { Component, ReactNode } from 'react'; | import { Component, ReactNode } from 'react'; | ||||
| @@ -55,7 +56,7 @@ function ErrorBoundaryFallback({ error }: { error: Error | null }) { | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| onClick={() => { | onClick={() => { | ||||
| window.history.pushState({}, '', '/'); | |||||
| window.history.pushState({}, '', HomeUrl); | |||||
| window.location.reload(); | window.location.reload(); | ||||
| }} | }} | ||||
| > | > | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||||
| import { formatEnum } from '@/utils/format'; | import { formatEnum } from '@/utils/format'; | ||||
| import { Typography, type SelectProps } from 'antd'; | import { Typography, type SelectProps } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| @@ -16,6 +17,8 @@ type FormInfoProps = { | |||||
| options?: SelectProps['options']; | options?: SelectProps['options']; | ||||
| /** 自定义节点 label、value 的字段 */ | /** 自定义节点 label、value 的字段 */ | ||||
| fieldNames?: SelectProps['fieldNames']; | fieldNames?: SelectProps['fieldNames']; | ||||
| /** 全局参数 */ | |||||
| globalParams?: PipelineGlobalParam[] | null; | |||||
| /** 自定义类名 */ | /** 自定义类名 */ | ||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | /** 自定义样式 */ | ||||
| @@ -32,12 +35,29 @@ function FormInfo({ | |||||
| select = false, | select = false, | ||||
| options, | options, | ||||
| fieldNames, | fieldNames, | ||||
| globalParams, | |||||
| className, | className, | ||||
| style, | style, | ||||
| }: FormInfoProps) { | }: FormInfoProps) { | ||||
| let showValue = value; | let showValue = value; | ||||
| if (value && typeof value === 'object' && valuePropName) { | if (value && typeof value === 'object' && valuePropName) { | ||||
| showValue = value[valuePropName]; | showValue = value[valuePropName]; | ||||
| const reg = /^\$\{(.*)\}$/; | |||||
| if (value.fromSelect && Array.isArray(globalParams) && globalParams.length > 0) { | |||||
| const match = reg.exec(showValue); | |||||
| if (match) { | |||||
| const paramName = match[1]; | |||||
| const foundParam = globalParams.find((v) => v.param_name === paramName); | |||||
| if (foundParam) { | |||||
| showValue = | |||||
| foundParam.param_type === PipelineGlobalParamType.Boolean // 布尔类型转换 | |||||
| ? foundParam.param_value | |||||
| ? 'true' | |||||
| : 'false' | |||||
| : foundParam.param_value; | |||||
| } | |||||
| } | |||||
| } | |||||
| } else if (select === true && options) { | } else if (select === true && options) { | ||||
| let _options: SelectProps['options'] = options; | let _options: SelectProps['options'] = options; | ||||
| if (fieldNames) { | if (fieldNames) { | ||||
| @@ -1,12 +1,11 @@ | |||||
| import FullScreenFrame from '@/components/FullScreenFrame'; | import FullScreenFrame from '@/components/FullScreenFrame'; | ||||
| import KFSpin from '@/components/KFSpin'; | |||||
| import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; | import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; | ||||
| import Loading from '@/utils/loading'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { FloatButton } from 'antd'; | import { FloatButton } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { createPortal } from 'react-dom'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export enum IframePageType { | export enum IframePageType { | ||||
| @@ -54,9 +53,13 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||||
| type IframePageProps = { | type IframePageProps = { | ||||
| /** 子系统 */ | /** 子系统 */ | ||||
| type: IframePageType; | |||||
| type?: IframePageType; | |||||
| /** url */ | |||||
| url?: string; | |||||
| /** 是否可以在页签上打开 */ | /** 是否可以在页签上打开 */ | ||||
| openInTab?: boolean; | openInTab?: boolean; | ||||
| /** 是否显示加载 */ | |||||
| showLoading?: boolean; | |||||
| /** 自定义样式类名 */ | /** 自定义样式类名 */ | ||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | /** 自定义样式 */ | ||||
| @@ -64,32 +67,60 @@ type IframePageProps = { | |||||
| }; | }; | ||||
| /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | ||||
| function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | |||||
| function IframePage({ | |||||
| type, | |||||
| url, | |||||
| showLoading = true, | |||||
| openInTab = false, | |||||
| className, | |||||
| style, | |||||
| }: IframePageProps) { | |||||
| const [iframeUrl, setIframeUrl] = useState(''); | const [iframeUrl, setIframeUrl] = useState(''); | ||||
| const [loading, setLoading] = useState(false); | |||||
| // const [loading, setLoading] = useState(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const requestIframeUrl = async () => { | |||||
| setLoading(true); | |||||
| const requestIframeUrl = async (type: IframePageType) => { | |||||
| if (showLoading) { | |||||
| Loading.show(); | |||||
| } | |||||
| const [res] = await to(getRequestAPI(type)()); | const [res] = await to(getRequestAPI(type)()); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setIframeUrl(res.data); | setIframeUrl(res.data); | ||||
| } else { | } else { | ||||
| setLoading(false); | |||||
| if (showLoading) { | |||||
| Loading.hide(); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| requestIframeUrl(); | |||||
| }, [type]); | |||||
| if (type) { | |||||
| requestIframeUrl(type); | |||||
| } else if (url) { | |||||
| if (showLoading) { | |||||
| Loading.show(); | |||||
| } | |||||
| setIframeUrl(url); | |||||
| } | |||||
| }, [type, url, showLoading]); | |||||
| const handleLoad = () => { | |||||
| if (showLoading) { | |||||
| Loading.hide(); | |||||
| } | |||||
| }; | |||||
| const hideLoading = () => { | |||||
| setLoading(false); | |||||
| const handleError = (error?: React.SyntheticEvent<HTMLIFrameElement, Event>) => { | |||||
| console.log('error', error); | |||||
| if (showLoading) { | |||||
| Loading.hide(); | |||||
| } | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={classNames('kf-iframe-page', className)} style={style}> | <div className={classNames('kf-iframe-page', className)} style={style}> | ||||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | |||||
| <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | |||||
| {/* {loading && createPortal(<KFSpin size="large" />, document.body)} */} | |||||
| {iframeUrl && <FullScreenFrame url={iframeUrl} onLoad={handleLoad} onError={handleError} />} | |||||
| {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | import { ConfigProvider, Form, Input, Typography } from 'antd'; | ||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { ReactNode } from 'react'; | |||||
| import './index.less'; | import './index.less'; | ||||
| // 如果值是对象时的类型 | // 如果值是对象时的类型 | ||||
| @@ -55,6 +56,8 @@ export interface ParameterInputProps { | |||||
| disabled?: boolean; | disabled?: boolean; | ||||
| /** 元素 id */ | /** 元素 id */ | ||||
| id?: string; | id?: string; | ||||
| /** 带标签的 input,设置后置标签 */ | |||||
| addonAfter?: ReactNode; | |||||
| } | } | ||||
| function ParameterInput({ | function ParameterInput({ | ||||
| @@ -75,7 +78,7 @@ function ParameterInput({ | |||||
| const valueObj = | const valueObj = | ||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | ||||
| if (valueObj && !valueObj.showValue) { | if (valueObj && !valueObj.showValue) { | ||||
| valueObj.showValue = valueObj.value; | |||||
| valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : ''; | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | const placeholder = valueObj?.placeholder || rest?.placeholder; | ||||
| @@ -1,29 +1,34 @@ | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | |||||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | import { ServiceData } from '@/pages/ModelDeployment/types'; | ||||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | ||||
| import { getServiceListReq } from '@/services/modelDeployment'; | import { getServiceListReq } from '@/services/modelDeployment'; | ||||
| import type { JCCResourceImage, JCCResourceStandard, JCCResourceType } from '@/state/jcdResource'; | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/state/systemResource'; | |||||
| import { type SelectProps } from 'antd'; | import { type SelectProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| // id 从 number 转换为 string | |||||
| const convertId = (item: any) => ({ | |||||
| ...item, | |||||
| id: JSON.stringify({ | |||||
| id: `${item.id}`, | |||||
| name: item.name, | |||||
| identifier: item.identifier, | |||||
| owner: item.owner, | |||||
| }), | |||||
| }); | |||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||||
| getOptions?: () => Promise<any>; // 获取下拉数据 | |||||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | ||||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | ||||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | filterOption?: SelectProps['filterOption']; // 过滤函数 | ||||
| isObjectValue: boolean; // value 是对象 | |||||
| getValue?: (value: any) => string | number; // 对象类型时,获取其值 | |||||
| getLabel?: (value: any) => string; // 对象类型时,获取其 label | |||||
| }; | }; | ||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| export const ParameterSelectTypeList = [ | |||||
| 'dataset', | |||||
| 'model', | |||||
| 'service', | |||||
| 'resource', | |||||
| 'remote-image', | |||||
| 'remote-resource-type', | |||||
| 'remote-resource', | |||||
| ] as const; | |||||
| export type ParameterSelectDataType = (typeof ParameterSelectTypeList)[number]; | |||||
| export const paramSelectConfig: Record<ParameterSelectDataType, SelectPropsConfig> = { | |||||
| dataset: { | dataset: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| const res = await getDatasetList({ | const res = await getDatasetList({ | ||||
| @@ -31,13 +36,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: DatasetData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: DatasetData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| model: { | model: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -46,13 +54,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: ModelData) => { | |||||
| return value.id; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| getLabel: (value: ModelData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| service: { | service: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -60,25 +71,58 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| }); | }); | ||||
| return ( | |||||
| res?.data?.content?.map((item: ServiceData) => ({ | |||||
| label: item.service_name, | |||||
| value: JSON.stringify(pick(item, ['id', 'service_name'])), | |||||
| })) ?? [] | |||||
| ); | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'label', | |||||
| value: 'value', | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| optionFilterProp: 'label', | optionFilterProp: 'label', | ||||
| getValue: (value: ServiceData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: ServiceData) => { | |||||
| return value.service_name; | |||||
| }, | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| resource: { | resource: { | ||||
| getOptions: async () => { | |||||
| // 不需要这个函数 | |||||
| return []; | |||||
| }, | |||||
| fieldNames: resourceFieldNames, | fieldNames: resourceFieldNames, | ||||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | filterOption: filterResourceStandard as SelectProps['filterOption'], | ||||
| isObjectValue: false, | |||||
| }, | |||||
| 'remote-resource-type': { | |||||
| optionFilterProp: 'label', | |||||
| isObjectValue: false, | |||||
| getValue: (value: JCCResourceType) => { | |||||
| return value.value; | |||||
| }, | |||||
| getLabel: (value: JCCResourceType) => { | |||||
| return value.label; | |||||
| }, | |||||
| }, | |||||
| 'remote-image': { | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: JCCResourceImage) => { | |||||
| return value.imageID; | |||||
| }, | |||||
| getLabel: (value: JCCResourceImage) => { | |||||
| return value.name; | |||||
| }, | |||||
| isObjectValue: true, | |||||
| }, | |||||
| 'remote-resource': { | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: JCCResourceStandard) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: JCCResourceStandard) => { | |||||
| const cpu = value.baseResourceSpecs.find((v) => v.type === 'CPU'); | |||||
| const ram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'RAM'); | |||||
| const vram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'VRAM'); | |||||
| const cpuText = cpu ? `CPU:${cpu.availableValue}, ` : ''; | |||||
| const ramText = ram ? `内存: ${ram.availableValue}${ram.availableUnit?.toUpperCase()}` : ''; | |||||
| const vramText = vram | |||||
| ? `(显存${vram.availableValue}${vram.availableUnit?.toUpperCase()})` | |||||
| : ''; | |||||
| return `${value.type}: ${value.availableCount}*${value.name}${vramText}, ${cpuText}${ramText}`; | |||||
| }, | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -4,19 +4,25 @@ | |||||
| * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | ||||
| */ | */ | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import jccResourceState from '@/state/jcdResource'; | |||||
| import systemResourceState, { getSystemResources } from '@/state/systemResource'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useSnapshot } from '@umijs/max'; | |||||
| import { Select, type SelectProps } from 'antd'; | import { Select, type SelectProps } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||||
| import FormInfo from '../FormInfo'; | import FormInfo from '../FormInfo'; | ||||
| import { paramSelectConfig } from './config'; | |||||
| import { paramSelectConfig, type ParameterSelectDataType } from './config'; | |||||
| export { ParameterSelectTypeList, type ParameterSelectDataType } from './config'; | |||||
| export type ParameterSelectObject = { | export type ParameterSelectObject = { | ||||
| value: any; | value: any; | ||||
| [key: string]: any; | [key: string]: any; | ||||
| }; | }; | ||||
| export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource'; | |||||
| type SelectOptions = SelectProps['options']; | |||||
| const identityFunc = (value: any) => value; | |||||
| export interface ParameterSelectProps extends SelectProps { | export interface ParameterSelectProps extends SelectProps { | ||||
| /** 类型 */ | /** 类型 */ | ||||
| @@ -25,8 +31,6 @@ export interface ParameterSelectProps extends SelectProps { | |||||
| display?: boolean; | display?: boolean; | ||||
| /** 值,支持对象,对象必须包含 value */ | /** 值,支持对象,对象必须包含 value */ | ||||
| value?: string | ParameterSelectObject; | value?: string | ParameterSelectObject; | ||||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||||
| isPipeline?: boolean; | |||||
| /** 修改后回调 */ | /** 修改后回调 */ | ||||
| onChange?: (value: string | ParameterSelectObject) => void; | onChange?: (value: string | ParameterSelectObject) => void; | ||||
| } | } | ||||
| @@ -36,69 +40,126 @@ function ParameterSelect({ | |||||
| dataType, | dataType, | ||||
| display = false, | display = false, | ||||
| value, | value, | ||||
| isPipeline = false, | |||||
| onChange, | onChange, | ||||
| ...rest | ...rest | ||||
| }: ParameterSelectProps) { | }: ParameterSelectProps) { | ||||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||||
| const [options, setOptions] = useState<SelectOptions>([]); | |||||
| const propsConfig = paramSelectConfig[dataType]; | const propsConfig = paramSelectConfig[dataType]; | ||||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | |||||
| const [resourceStandardList] = useComputingResource(); | |||||
| const computingResource = isPipeline | |||||
| ? resourceStandardList.map((v) => ({ | |||||
| ...v, | |||||
| id: String(v.id), | |||||
| })) | |||||
| : resourceStandardList; | |||||
| const { | |||||
| getLabel = identityFunc, | |||||
| getValue = identityFunc, | |||||
| getOptions, | |||||
| filterOption, | |||||
| fieldNames, | |||||
| optionFilterProp, | |||||
| isObjectValue, | |||||
| } = propsConfig; | |||||
| const selectValue = typeof value === 'object' && value !== null ? value.value : value; | |||||
| // 数据集、模型、服务,对象转换成 json 字符串 | |||||
| const valueText = | |||||
| typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; | |||||
| const jccResourceSnap = useSnapshot(jccResourceState); | |||||
| const { getResourceTypes } = jccResourceSnap; | |||||
| const systemResourceSnap = useSnapshot(systemResourceState); | |||||
| const objectOptions = useMemo(() => { | |||||
| return dataType === 'remote-resource-type' | |||||
| ? jccResourceSnap.types | |||||
| : dataType === 'remote-image' | |||||
| ? jccResourceSnap.images | |||||
| : dataType === 'remote-resource' | |||||
| ? jccResourceSnap.resources | |||||
| : options; | |||||
| }, [dataType, options, jccResourceSnap.types, jccResourceSnap.images, jccResourceSnap.resources]); | |||||
| // 将对象类型转换为 Select Options | |||||
| const converObjectToOptions = useCallback( | |||||
| (v: any) => { | |||||
| return { | |||||
| label: getLabel(v), | |||||
| value: getValue(v), | |||||
| }; | |||||
| }, | |||||
| [getLabel, getValue], | |||||
| ); | |||||
| // 数据集、模型、服务获取数据后,进行转换 | |||||
| const objectSelectOptions = useMemo(() => { | |||||
| return objectOptions?.map(converObjectToOptions); | |||||
| }, [converObjectToOptions, objectOptions]); | |||||
| // 快速得到选中的对象 | |||||
| const valueMap = useMemo(() => { | |||||
| const map = new Map<string | number, any>(); | |||||
| objectOptions?.forEach((v) => { | |||||
| map.set(getValue(v), v); | |||||
| }); | |||||
| return map; | |||||
| }, [objectOptions, getValue]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取下拉数据 | // 获取下拉数据 | ||||
| const getSelectOptions = async () => { | const getSelectOptions = async () => { | ||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| if (getOptions) { | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| } else if (dataType === 'remote-resource-type') { | |||||
| getResourceTypes(); | |||||
| } else if (dataType === 'resource') { | |||||
| getSystemResources(); | |||||
| } | } | ||||
| }; | }; | ||||
| getSelectOptions(); | getSelectOptions(); | ||||
| }, [propsConfig]); | |||||
| }, [getOptions, dataType, getResourceTypes]); | |||||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||||
| const selectOptions = ( | |||||
| dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions | |||||
| ) as SelectOptions; | |||||
| const handleChange = (text: string) => { | const handleChange = (text: string) => { | ||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: text, | |||||
| }); | |||||
| // 数据集、模型、服务,转换成对象 | |||||
| if (isObjectValue) { | |||||
| // 设置为 null 是因为 antv g6 的 bug | |||||
| // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 | |||||
| const selectValue = text ? valueMap.get(text) : null; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } else { | } else { | ||||
| onChange?.(text); | |||||
| const selectValue = text ? text : ''; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| // 只用于展示,FormInfo 组件带有 Tooltip | // 只用于展示,FormInfo 组件带有 Tooltip | ||||
| if (display) { | if (display) { | ||||
| return ( | return ( | ||||
| <FormInfo | |||||
| select | |||||
| value={valueText} | |||||
| options={selectOptions} | |||||
| fieldNames={propsConfig?.fieldNames} | |||||
| ></FormInfo> | |||||
| <FormInfo select value={valueText} options={selectOptions} fieldNames={fieldNames}></FormInfo> | |||||
| ); | ); | ||||
| } | } | ||||
| return ( | return ( | ||||
| <Select | <Select | ||||
| {...rest} | {...rest} | ||||
| filterOption={propsConfig?.filterOption} | |||||
| options={selectOptions} | options={selectOptions} | ||||
| fieldNames={propsConfig?.fieldNames} | |||||
| optionFilterProp={propsConfig?.optionFilterProp} | |||||
| fieldNames={fieldNames} | |||||
| optionFilterProp={optionFilterProp} | |||||
| filterOption={filterOption} | |||||
| value={valueText} | value={valueText} | ||||
| onChange={handleChange} | onChange={handleChange} | ||||
| showSearch | showSearch | ||||
| @@ -10,10 +10,10 @@ import ResourceSelectorModal, { | |||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button, ConfigProvider } from 'antd'; | import { Button, ConfigProvider } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { pick } from 'lodash'; | |||||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -27,6 +27,8 @@ export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse | |||||
| interface ResourceSelectProps extends ParameterInputProps { | interface ResourceSelectProps extends ParameterInputProps { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| type: ResourceSelectorType; | type: ResourceSelectorType; | ||||
| /** 值 */ | |||||
| value?: ResourceSelectorResponse; | |||||
| } | } | ||||
| // 获取选择数据集、模型、镜像后面按钮 icon | // 获取选择数据集、模型、镜像后面按钮 icon | ||||
| @@ -47,68 +49,51 @@ function ResourceSelect({ | |||||
| }: ResourceSelectProps) { | }: ResourceSelectProps) { | ||||
| const { componentSize } = ConfigProvider.useConfig(); | const { componentSize } = ConfigProvider.useConfig(); | ||||
| const mySize = size || componentSize; | const mySize = size || componentSize; | ||||
| let selectedResource: ResourceSelectorResponse | undefined = undefined; | |||||
| if ( | |||||
| value && | |||||
| typeof value === 'object' && | |||||
| value.activeTab && | |||||
| value.id && | |||||
| value.name && | |||||
| value.version && | |||||
| value.path && | |||||
| (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) | |||||
| ) { | |||||
| selectedResource = pick(value, [ | |||||
| 'activeTab', | |||||
| 'id', | |||||
| 'identifier', | |||||
| 'name', | |||||
| 'owner', | |||||
| 'version', | |||||
| 'path', | |||||
| ]) as ResourceSelectorResponse; | |||||
| let defaultActiveTab: CommonTabKeys | undefined, | |||||
| defaultExpandedKeys: string[] = [], | |||||
| defaultCheckedKeys: string[] = []; | |||||
| if (value && typeof value === 'object') { | |||||
| defaultActiveTab = value.activeTab; | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| if (value.image_id && value.id) { | |||||
| defaultExpandedKeys = [`${value.image_id}`]; | |||||
| defaultCheckedKeys = [`${value.image_id}-${value.id}`]; | |||||
| } | |||||
| } else if (value.id && value.version) { | |||||
| defaultExpandedKeys = [value.id]; | |||||
| defaultCheckedKeys = [`${value.id}-${value.version}`]; | |||||
| } | |||||
| } | } | ||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| type, | type, | ||||
| defaultExpandedKeys: selectedResource ? [selectedResource.id] : [], | |||||
| defaultCheckedKeys: selectedResource | |||||
| ? [`${selectedResource.id}-${selectedResource.version}`] | |||||
| : [], | |||||
| defaultActiveTab: selectedResource?.activeTab, | |||||
| defaultExpandedKeys: defaultExpandedKeys, | |||||
| defaultCheckedKeys: defaultCheckedKeys, | |||||
| defaultActiveTab: defaultActiveTab, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, ...rest } = res; | |||||
| const { url } = rest; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: path, | |||||
| showValue: path, | |||||
| ...rest, | |||||
| value: url, | |||||
| showValue: url, | |||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| const jsonObj = { | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { activeTab, ...rest } = res; | |||||
| const { name, version } = rest; | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...rest, | |||||
| value: showValue, | |||||
| showValue, | showValue, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } | } | ||||
| } else { | } else { | ||||
| @@ -1,8 +1,8 @@ | |||||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | import datasetImg from '@/assets/img/modal-select-dataset.png'; | ||||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | import mirrorImg from '@/assets/img/modal-select-mirror.png'; | ||||
| import modelImg from '@/assets/img/modal-select-model.png'; | import modelImg from '@/assets/img/modal-select-model.png'; | ||||
| import { AvailableRange, CommonTabKeys } from '@/enums'; | |||||
| import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums'; | |||||
| import { DatasetData, ModelData, ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { MirrorVersionData } from '@/pages/Mirror/Info'; | import { MirrorVersionData } from '@/pages/Mirror/Info'; | ||||
| import { MirrorData } from '@/pages/Mirror/List'; | import { MirrorData } from '@/pages/Mirror/List'; | ||||
| import { | import { | ||||
| @@ -24,11 +24,11 @@ export enum ResourceSelectorType { | |||||
| } | } | ||||
| // 数据集、模型列表转为树形结构 | // 数据集、模型列表转为树形结构 | ||||
| const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => { | |||||
| const convertDatasetToTreeData = (list: ResourceData[], isPublic: boolean): TreeDataNode[] => { | |||||
| return list.map((v) => ({ | return list.map((v) => ({ | ||||
| ...v, | ...v, | ||||
| key: `${v.id}`, | key: `${v.id}`, | ||||
| title: v.name, | |||||
| title: isPublic ? `${v.name} (${v.owner})` : v.name, | |||||
| isLeaf: false, | isLeaf: false, | ||||
| checkable: false, | checkable: false, | ||||
| })); | })); | ||||
| @@ -52,9 +52,9 @@ const convertResourceVersionToTreeData = ( | |||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: ResourceVersionData) => ({ | return list.map((item: ResourceVersionData) => ({ | ||||
| ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ||||
| version: item.name, | |||||
| title: item.name, | |||||
| key: `${parentId}-${item.name}`, | key: `${parentId}-${item.name}`, | ||||
| title: item.name, | |||||
| version: item.name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| })); | })); | ||||
| @@ -66,9 +66,9 @@ const convertMirrorVersionToTreeData = ( | |||||
| list: MirrorVersionData[], | list: MirrorVersionData[], | ||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: MirrorVersionData) => ({ | return list.map((item: MirrorVersionData) => ({ | ||||
| url: item.url, | |||||
| title: item.tag_name, | |||||
| ...item, | |||||
| key: `${parentId}-${item.id}`, | key: `${parentId}-${item.id}`, | ||||
| title: item.tag_name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| })); | })); | ||||
| @@ -106,7 +106,7 @@ export class DatasetSelector implements SelectorTypeInfo { | |||||
| const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); | const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const list = res.data.content || []; | const list = res.data.content || []; | ||||
| return convertDatasetToTreeData(list); | |||||
| return convertDatasetToTreeData(list, isPublic); | |||||
| } else { | } else { | ||||
| return Promise.reject('获取数据集列表失败'); | return Promise.reject('获取数据集列表失败'); | ||||
| } | } | ||||
| @@ -125,11 +125,16 @@ export class DatasetSelector implements SelectorTypeInfo { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | ||||
| const res = await getDatasetInfo(params); | const res = await getDatasetInfo(params); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.dataset_version_vos || []; | |||||
| const dataset = res.data as DatasetData; | |||||
| const { | |||||
| relative_paths: path = '', | |||||
| dataset_version_vos: list = [], | |||||
| version_desc: versionDesc = '', | |||||
| } = dataset; | |||||
| return { | return { | ||||
| path, | path, | ||||
| content: list, | content: list, | ||||
| versionDesc, | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| return Promise.reject('获取数据集文件列表失败'); | return Promise.reject('获取数据集文件列表失败'); | ||||
| @@ -158,7 +163,7 @@ export class ModelSelector implements SelectorTypeInfo { | |||||
| const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); | const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const list = res.data.content || []; | const list = res.data.content || []; | ||||
| return convertDatasetToTreeData(list); | |||||
| return convertDatasetToTreeData(list, isPublic); | |||||
| } else { | } else { | ||||
| return Promise.reject('获取模型列表失败'); | return Promise.reject('获取模型列表失败'); | ||||
| } | } | ||||
| @@ -177,11 +182,17 @@ export class ModelSelector implements SelectorTypeInfo { | |||||
| const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version', 'is_public']); | ||||
| const res = await getModelInfo(params); | const res = await getModelInfo(params); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const path = res.data.relative_paths || ''; | |||||
| const list = res.data.model_version_vos || []; | |||||
| const model = res.data as ModelData; | |||||
| const { | |||||
| relative_paths: path = '', | |||||
| model_version_vos: list = [], | |||||
| version_desc: versionDesc = '', | |||||
| } = model; | |||||
| return { | return { | ||||
| path, | path, | ||||
| content: list, | content: list, | ||||
| versionDesc, | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| return Promise.reject('获取模型文件列表失败'); | return Promise.reject('获取模型文件列表失败'); | ||||
| @@ -224,8 +235,7 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| image_id: parentKey, | image_id: parentKey, | ||||
| page: 0, | page: 0, | ||||
| size: 2000, | size: 2000, | ||||
| status: 'available', | |||||
| state: 1, | |||||
| status: MirrorVersionStatus.Available, | |||||
| }); | }); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const list = res.data.content || []; | const list = res.data.content || []; | ||||
| @@ -236,7 +246,7 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| } | } | ||||
| async getFiles(_parentKey: string, parentNode: MirrorVersionData) { | async getFiles(_parentKey: string, parentNode: MirrorVersionData) { | ||||
| const { url } = parentNode; | |||||
| const { url, description } = parentNode; | |||||
| return { | return { | ||||
| path: url, | path: url, | ||||
| content: [ | content: [ | ||||
| @@ -245,6 +255,7 @@ export class MirrorSelector implements SelectorTypeInfo { | |||||
| file_name: `${url}`, | file_name: `${url}`, | ||||
| }, | }, | ||||
| ], | ], | ||||
| versionDesc: description, | |||||
| }; | }; | ||||
| } | } | ||||
| } | } | ||||
| @@ -65,7 +65,7 @@ | |||||
| border-bottom: 1px solid rgba(22, 100, 255, 0.1); | border-bottom: 1px solid rgba(22, 100, 255, 0.1); | ||||
| } | } | ||||
| &__files { | &__files { | ||||
| height: calc(100% - 75px); | |||||
| height: calc(100% - 61px); | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| &__file { | &__file { | ||||
| @@ -76,7 +76,22 @@ | |||||
| word-break: break-all; | word-break: break-all; | ||||
| background: rgba(4, 3, 3, 0.06); | background: rgba(4, 3, 3, 0.06); | ||||
| border-radius: 4px; | border-radius: 4px; | ||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| &__desc { | |||||
| margin-bottom: 10px; | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | |||||
| word-break: break-all; | |||||
| background: rgba(4, 3, 3, 0.06); | |||||
| border-radius: 4px; | |||||
| max-height: calc(100% - 61px); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { ResourceFileData } from '@/pages/Dataset/config'; | import { ResourceFileData } from '@/pages/Dataset/config'; | ||||
| import { type MirrorVersionData } from '@/pages/Mirror/Info'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | ||||
| import { Input, Tabs, Tree } from 'antd'; | import { Input, Tabs, Tree } from 'antd'; | ||||
| @@ -19,13 +20,13 @@ export { ResourceSelectorType, selectorTypeConfig }; | |||||
| // 选择数据集、模型、镜像的返回类型 | // 选择数据集、模型、镜像的返回类型 | ||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| id: string; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| identifier: string; // 数据集\模型 identifier,镜像这个字段为空 | |||||
| owner: string; // 数据集\模型 owner,镜像这个字段为空 | |||||
| }; | |||||
| id: string; // 数据集\模型 id | |||||
| name: string; // 数据集\模型 name | |||||
| identifier: string; // 数据集\模型 identifier | |||||
| owner: string; // 数据集\模型 owner | |||||
| version: string; // 数据集\模型 version | |||||
| path: string; // 数据集\模型 版本路径 | |||||
| } & MirrorVersionData; | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| @@ -84,6 +85,7 @@ function ResourceSelectorModal({ | |||||
| const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]); | ||||
| const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]); | ||||
| const [files, setFiles] = useState<ResourceFileData[]>([]); | const [files, setFiles] = useState<ResourceFileData[]>([]); | ||||
| const [versionDesc, setVersionDesc] = useState<string | undefined>(undefined); | |||||
| const [versionPath, setVersionPath] = useState(''); | const [versionPath, setVersionPath] = useState(''); | ||||
| const [searchText, setSearchText] = useState(''); | const [searchText, setSearchText] = useState(''); | ||||
| const [firstLoadList, setFirstLoadList] = useState(false); | const [firstLoadList, setFirstLoadList] = useState(false); | ||||
| @@ -119,6 +121,7 @@ function ResourceSelectorModal({ | |||||
| setCheckedKeys([]); | setCheckedKeys([]); | ||||
| setLoadedKeys([]); | setLoadedKeys([]); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setSearchText(''); | setSearchText(''); | ||||
| getTreeData(); | getTreeData(); | ||||
| @@ -169,9 +172,11 @@ function ResourceSelectorModal({ | |||||
| if (res) { | if (res) { | ||||
| setVersionPath(res.path); | setVersionPath(res.path); | ||||
| setFiles(res.content); | setFiles(res.content); | ||||
| setVersionDesc(res.versionDesc); | |||||
| } else { | } else { | ||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -201,6 +206,7 @@ function ResourceSelectorModal({ | |||||
| } else { | } else { | ||||
| setVersionPath(''); | setVersionPath(''); | ||||
| setFiles([]); | setFiles([]); | ||||
| setVersionDesc(undefined); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -236,15 +242,22 @@ function ResourceSelectorModal({ | |||||
| const name = (treeNode?.title ?? '') as string; | const name = (treeNode?.title ?? '') as string; | ||||
| const identifier = (treeNode?.identifier ?? '') as string; | const identifier = (treeNode?.identifier ?? '') as string; | ||||
| const owner = (treeNode?.owner ?? '') as string; | const owner = (treeNode?.owner ?? '') as string; | ||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| activeTab: activeTab, | |||||
| }; | |||||
| const childNode = treeNode.children.filter((v: TreeDataNode) => v.key === last)[0]; | |||||
| const res = | |||||
| type === ResourceSelectorType.Mirror | |||||
| ? { | |||||
| activeTab: activeTab, | |||||
| ...childNode, | |||||
| } | |||||
| : { | |||||
| activeTab: activeTab, | |||||
| id: Number(id), | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| onOk?.(res); | onOk?.(res); | ||||
| } else { | } else { | ||||
| onOk?.(undefined); | onOk?.(undefined); | ||||
| @@ -253,8 +266,9 @@ function ResourceSelectorModal({ | |||||
| const title = `选择${config.name}`; | const title = `选择${config.name}`; | ||||
| const palceholder = `请输入${config.name}名称`; | const palceholder = `请输入${config.name}名称`; | ||||
| const fileLen = files.length > 0 ? `(${files.length})` : ''; | |||||
| const fileTitle = | const fileTitle = | ||||
| type === ResourceSelectorType.Mirror ? '已选镜像' : `已选${config.name}文件(${files.length})`; | |||||
| type === ResourceSelectorType.Mirror ? '镜像地址' : `${config.name}版本文件${fileLen}`; | |||||
| const tabItems = config.tabItems; | const tabItems = config.tabItems; | ||||
| const titleImg = config.modalIcon; | const titleImg = config.modalIcon; | ||||
| @@ -312,14 +326,24 @@ function ResourceSelectorModal({ | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className={styles['model-selector__right']}> | <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 style={{ height: '50%' }}> | |||||
| <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> | ||||
| {versionDesc && ( | |||||
| <div style={{ height: '50%' }}> | |||||
| <div | |||||
| className={styles['model-selector__right__title']} | |||||
| >{`${config.name}版本描述`}</div> | |||||
| <div className={styles['model-selector__right__desc']}>{versionDesc}</div> | |||||
| </div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -1,22 +1,25 @@ | |||||
| import { clearSessionToken } from '@/access'; | import { clearSessionToken } from '@/access'; | ||||
| import DefaultAvatar from '@/assets/img/avatar-default.png'; | |||||
| import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | |||||
| import { setRemoteMenu } from '@/services/session'; | import { setRemoteMenu } from '@/services/session'; | ||||
| import { logout } from '@/services/system/auth'; | import { logout } from '@/services/system/auth'; | ||||
| import { ClientInfo } from '@/types'; | import { ClientInfo } from '@/types'; | ||||
| import { sleep } from '@/utils/promise'; | |||||
| import { sleep, to } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import { gotoLoginPage, oauthLogout } from '@/utils/ui'; | import { gotoLoginPage, oauthLogout } from '@/utils/ui'; | ||||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | ||||
| import { setAlpha } from '@ant-design/pro-components'; | import { setAlpha } from '@ant-design/pro-components'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | import { useEmotionCss } from '@ant-design/use-emotion-css'; | ||||
| import { history, useModel } from '@umijs/max'; | |||||
| import { useModel, useNavigate } from '@umijs/max'; | |||||
| import { Avatar, Spin } from 'antd'; | import { Avatar, Spin } from 'antd'; | ||||
| import type { MenuInfo } from 'rc-menu/lib/interface'; | import type { MenuInfo } from 'rc-menu/lib/interface'; | ||||
| import React, { useCallback } from 'react'; | |||||
| import React from 'react'; | |||||
| import { flushSync } from 'react-dom'; | import { flushSync } from 'react-dom'; | ||||
| import HeaderDropdown from '../HeaderDropdown'; | import HeaderDropdown from '../HeaderDropdown'; | ||||
| export type GlobalHeaderRightProps = { | export type GlobalHeaderRightProps = { | ||||
| menu?: boolean; | menu?: boolean; | ||||
| isHome?: boolean; | |||||
| }; | }; | ||||
| const Name = () => { | const Name = () => { | ||||
| @@ -55,24 +58,47 @@ const AvatarLogo = () => { | |||||
| }, | }, | ||||
| }; | }; | ||||
| }); | }); | ||||
| return <Avatar size="small" className={avatarClassName} src={currentUser?.avatar} alt="avatar" />; | |||||
| return ( | |||||
| <Avatar | |||||
| size="small" | |||||
| className={avatarClassName} | |||||
| src={currentUser?.avatar || DefaultAvatar} | |||||
| alt="avatar" | |||||
| /> | |||||
| ); | |||||
| }; | }; | ||||
| const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, isHome = false }) => { | |||||
| const navigate = useNavigate(); | |||||
| /** | /** | ||||
| * 退出登录,并且将当前的 url 保存 | * 退出登录,并且将当前的 url 保存 | ||||
| */ | */ | ||||
| const loginOut = async () => { | const loginOut = async () => { | ||||
| oauthLogout('http://172.20.32.197:31209/oauth/logout'); | |||||
| const { origin } = location; | |||||
| const [res] = await to(getLabelStudioUrl()); | |||||
| if (res && res.data) { | |||||
| oauthLogout(`${res.data}/oauth/logout`); | |||||
| } | |||||
| // 至少 1 秒后跳转,希望子系统能完成注销 | // 至少 1 秒后跳转,希望子系统能完成注销 | ||||
| await Promise.all([logout(), sleep(1000)]); | await Promise.all([logout(), sleep(1000)]); | ||||
| clearSessionToken(); | clearSessionToken(); | ||||
| setRemoteMenu(null); | setRemoteMenu(null); | ||||
| gotoLoginPage(); | |||||
| // 退出 oauth2 | |||||
| const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); | const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); | ||||
| if (clientInfo) { | if (clientInfo) { | ||||
| const { logoutUri } = clientInfo; | const { logoutUri } = clientInfo; | ||||
| location.replace(logoutUri); | location.replace(logoutUri); | ||||
| if (isHome) { | |||||
| setTimeout(() => { | |||||
| location.replace(origin); | |||||
| }, 1); | |||||
| } | |||||
| } else { | |||||
| if (isHome) { | |||||
| location.reload(); | |||||
| } else { | |||||
| gotoLoginPage(true); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| const actionClassName = useEmotionCss(({ token }) => { | const actionClassName = useEmotionCss(({ token }) => { | ||||
| @@ -92,20 +118,17 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||||
| }); | }); | ||||
| const { initialState, setInitialState } = useModel('@@initialState'); | const { initialState, setInitialState } = useModel('@@initialState'); | ||||
| const onMenuClick = useCallback( | |||||
| (event: MenuInfo) => { | |||||
| const { key } = event; | |||||
| if (key === 'logout') { | |||||
| flushSync(() => { | |||||
| setInitialState((s) => ({ ...s, currentUser: undefined })); | |||||
| }); | |||||
| loginOut(); | |||||
| return; | |||||
| } | |||||
| history.push(`/account/${key}`); | |||||
| }, | |||||
| [setInitialState], | |||||
| ); | |||||
| const onMenuClick = (event: MenuInfo) => { | |||||
| const { key } = event; | |||||
| if (key === 'logout') { | |||||
| flushSync(() => { | |||||
| setInitialState((s) => ({ ...s, currentUser: undefined })); | |||||
| }); | |||||
| loginOut(); | |||||
| return; | |||||
| } | |||||
| navigate(`/account/${key}`); | |||||
| }; | |||||
| const loading = ( | const loading = ( | ||||
| <span className={actionClassName}> | <span className={actionClassName}> | ||||
| @@ -10,13 +10,21 @@ type RunDurationProps = { | |||||
| }; | }; | ||||
| function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { | function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { | ||||
| const [now] = useServerTime(); | const [now] = useServerTime(); | ||||
| const [currentTime, setCurrentTime] = useState<Date>(now()); | |||||
| const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now()); | |||||
| // console.log( | |||||
| // 'currentTime', | |||||
| // new Date(createTime ?? 0), | |||||
| // currentTime, | |||||
| // (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000, | |||||
| // ); | |||||
| // 定时刷新耗时 | // 定时刷新耗时 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (finishTime) { | if (finishTime) { | ||||
| setCurrentTime(new Date(finishTime)); | setCurrentTime(new Date(finishTime)); | ||||
| } else { | } else { | ||||
| setCurrentTime(now()); | |||||
| const timer = setInterval(() => { | const timer = setInterval(() => { | ||||
| setCurrentTime(now()); | setCurrentTime(now()); | ||||
| }, 1000); | }, 1000); | ||||
| @@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr | |||||
| }; | }; | ||||
| } | } | ||||
| }, [finishTime, now]); | }, [finishTime, now]); | ||||
| return ( | return ( | ||||
| <span className={className} style={style}> | <span className={className} style={style}> | ||||
| {elapsedTime(createTime, currentTime)} | {elapsedTime(createTime, currentTime)} | ||||
| @@ -33,7 +33,7 @@ export enum TensorBoardStatus { | |||||
| Unknown = 'Unknown', // 未知 | Unknown = 'Unknown', // 未知 | ||||
| Pending = 'Pending', // 启动中 | Pending = 'Pending', // 启动中 | ||||
| Running = 'Running', // 运行中 | Running = 'Running', // 运行中 | ||||
| Terminated = 'Terminated', // 未启动或者已终止 | |||||
| Terminated = 'Terminated', // 未启动 | |||||
| Failed = 'Failed', // 失败 | Failed = 'Failed', // 失败 | ||||
| } | } | ||||
| @@ -95,8 +95,8 @@ export enum AutoMLType { | |||||
| export const autoMLTypeOptions = [ | export const autoMLTypeOptions = [ | ||||
| { label: '表格', value: AutoMLType.Table }, | { label: '表格', value: AutoMLType.Table }, | ||||
| { label: '文本分类', value: AutoMLType.Text }, | |||||
| { label: '视频分类', value: AutoMLType.Video }, | |||||
| { label: '文本', value: AutoMLType.Text }, | |||||
| { label: '视频', value: AutoMLType.Video }, | |||||
| ]; | ]; | ||||
| // 自动化任务类型 | // 自动化任务类型 | ||||
| @@ -163,3 +163,11 @@ export enum AutoMLTrailStatus { | |||||
| CANCELLED = 'CANCELLED', // 取消 | CANCELLED = 'CANCELLED', // 取消 | ||||
| MEMOUT = 'MEMOUT', // 内存溢出 | MEMOUT = 'MEMOUT', // 内存溢出 | ||||
| } | } | ||||
| // 流水线组件类型 | |||||
| export enum ComponentType { | |||||
| Ref = 'ref', | |||||
| Select = 'select', | |||||
| Map = 'map', | |||||
| Str = 'str', | |||||
| } | |||||
| @@ -1,4 +1,6 @@ | |||||
| export enum PageEnum { | export enum PageEnum { | ||||
| Root = '/', | |||||
| LOGIN = '/user/login', | LOGIN = '/user/login', | ||||
| Authorize = '/authorize', | Authorize = '/authorize', | ||||
| Home = '/home', | |||||
| } | } | ||||
| @@ -19,6 +19,30 @@ const clearCache = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| const doubleText = () => { | |||||
| if (process.env.NODE_ENV === 'development') { | |||||
| document.body.addEventListener( | |||||
| 'click', | |||||
| (e) => { | |||||
| const target = e.target; | |||||
| if ( | |||||
| // e.altKey && | |||||
| e.ctrlKey && | |||||
| target && | |||||
| target.innerText && | |||||
| target.nodeType === Node.ELEMENT_NODE | |||||
| ) { | |||||
| e.stopPropagation(); | |||||
| e.preventDefault(); | |||||
| const times = 2; | |||||
| target.innerText = target.innerText.repeat(times); | |||||
| } | |||||
| }, | |||||
| true, | |||||
| ); | |||||
| } | |||||
| }; | |||||
| // if pwa is true | // if pwa is true | ||||
| if (pwa) { | if (pwa) { | ||||
| // Notify user if offline now | // Notify user if offline now | ||||
| @@ -89,3 +113,5 @@ if (pwa) { | |||||
| clearCache(); | clearCache(); | ||||
| } | } | ||||
| doubleText(); | |||||
| @@ -4,66 +4,90 @@ | |||||
| * @Description: 资源规格 hook | * @Description: 资源规格 hook | ||||
| */ | */ | ||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { ComputingResource } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { type SelectProps } from 'antd'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| // import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| // import { ComputingResource } from '@/types'; | |||||
| // import { to } from '@/utils/promise'; | |||||
| // import { type SelectProps } from 'antd'; | |||||
| // import { useCallback, useEffect, useState } from 'react'; | |||||
| const computingResource: ComputingResource[] = []; | |||||
| // const computingResource: ComputingResource[] = []; | |||||
| /** 过滤资源规格 */ | |||||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||||
| input: string, | |||||
| option?: ComputingResource, | |||||
| ) => { | |||||
| return ( | |||||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||||
| ); | |||||
| }; | |||||
| // /** 过滤资源规格 */ | |||||
| // export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||||
| // input: string, | |||||
| // option?: ComputingResource, | |||||
| // ) => { | |||||
| // return ( | |||||
| // option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||||
| // ); | |||||
| // }; | |||||
| /** 资源规格字段 */ | |||||
| export const resourceFieldNames = { | |||||
| label: 'description', | |||||
| value: 'id', | |||||
| }; | |||||
| // /** 资源规格字段 */ | |||||
| // export const resourceFieldNames = { | |||||
| // label: 'description', | |||||
| // value: 'id', | |||||
| // }; | |||||
| /** 获取资源规格 */ | |||||
| export function useComputingResource() { | |||||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||||
| // /** 获取资源规格 */ | |||||
| // export function useComputingResource() { | |||||
| // const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||||
| useEffect(() => { | |||||
| // 获取资源规格列表数据 | |||||
| const getComputingResource = async () => { | |||||
| const params = { | |||||
| page: 0, | |||||
| size: 1000, | |||||
| resource_type: '', | |||||
| }; | |||||
| const [res] = await to(getComputingResourceReq(params)); | |||||
| if (res && res.data && Array.isArray(res.data.content)) { | |||||
| setResourceStandardList(res.data.content); | |||||
| computingResource.splice(0, computingResource.length, ...res.data.content); | |||||
| } | |||||
| }; | |||||
| // useEffect(() => { | |||||
| // // 获取资源规格列表数据 | |||||
| // const getComputingResource = async () => { | |||||
| // const params = { | |||||
| // page: 0, | |||||
| // size: 1000, | |||||
| // resource_type: '', | |||||
| // }; | |||||
| // const [res] = await to(getComputingResourceReq(params)); | |||||
| // if (res && res.data && Array.isArray(res.data.content)) { | |||||
| // setResourceStandardList(res.data.content); | |||||
| // computingResource.splice(0, computingResource.length, ...res.data.content); | |||||
| // } | |||||
| // }; | |||||
| // if (computingResource.length > 0) { | |||||
| // setResourceStandardList(computingResource); | |||||
| // } else { | |||||
| // getComputingResource(); | |||||
| // } | |||||
| // }, []); | |||||
| if (computingResource.length > 0) { | |||||
| setResourceStandardList(computingResource); | |||||
| } else { | |||||
| getComputingResource(); | |||||
| } | |||||
| // // 根据 standard 获取 description | |||||
| // const getDescription = useCallback( | |||||
| // (id?: string | number) => { | |||||
| // if (!id) { | |||||
| // return undefined; | |||||
| // } | |||||
| // return resourceStandardList.find((item) => Number(item.id) === Number(id))?.description; | |||||
| // }, | |||||
| // [resourceStandardList], | |||||
| // ); | |||||
| // return [resourceStandardList, getDescription] as const; | |||||
| // } | |||||
| import state, { getSystemResources } from '@/state/systemResource'; | |||||
| import { useSnapshot } from '@umijs/max'; | |||||
| import { useCallback, useEffect } from 'react'; | |||||
| export const useSystemResource = () => { | |||||
| useEffect(() => { | |||||
| getSystemResources(); | |||||
| }, []); | }, []); | ||||
| // 根据 standard 获取 description | |||||
| const snap = useSnapshot(state); | |||||
| /* 根据 standard 获取 description */ | |||||
| const getDescription = useCallback( | const getDescription = useCallback( | ||||
| (id?: string | number) => { | (id?: string | number) => { | ||||
| if (!id) { | if (!id) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| return resourceStandardList.find((item) => Number(item.id) === Number(id))?.description; | |||||
| return snap.resources.find((item) => Number(item.id) === Number(id))?.description; | |||||
| }, | }, | ||||
| [resourceStandardList], | |||||
| [snap.resources], | |||||
| ); | ); | ||||
| return [resourceStandardList, getDescription] as const; | |||||
| } | |||||
| return getDescription; | |||||
| }; | |||||
| @@ -1,11 +1,24 @@ | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useEffect } from 'react'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { useEffect } from 'react'; | |||||
| export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void | |||||
| export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => { | |||||
| const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running | |||||
| export type MessageHandler = ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: string, | |||||
| finishTime: string, | |||||
| nodes: Record<string, NodeStatus>, | |||||
| ) => void; | |||||
| export const useSSE = ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: ExperimentStatus, | |||||
| name: string, | |||||
| namespace: string, | |||||
| onMessage: MessageHandler, | |||||
| ) => { | |||||
| const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (isRunning) { | if (isRunning) { | ||||
| const { origin } = location; | const { origin } = location; | ||||
| @@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: | |||||
| const dataJson = parseJsonText(data); | const dataJson = parseJsonText(data); | ||||
| const statusData = dataJson?.result?.object?.status; | const statusData = dataJson?.result?.object?.status; | ||||
| if (statusData) { | if (statusData) { | ||||
| const { finishedAt, phase, nodes } = statusData; | |||||
| onMessage(experimentInsId, phase, finishedAt, nodes); | |||||
| const { finishedAt, phase, nodes } = statusData; | |||||
| onMessage(experimentId, experimentInsId, phase, finishedAt, nodes); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: | |||||
| return () => { | return () => { | ||||
| evtSource.close(); | evtSource.close(); | ||||
| } | |||||
| }; | |||||
| } | } | ||||
| }, [experimentInsId, isRunning, name, namespace, onMessage]); | |||||
| }, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]); | |||||
| }; | }; | ||||
| @@ -1,4 +1,5 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | ||||
| import { HomeUrl } from '@/utils/constant'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| const NoFoundPage = () => { | const NoFoundPage = () => { | ||||
| @@ -12,7 +13,7 @@ const NoFoundPage = () => { | |||||
| content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} | content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} | ||||
| hasFooter={true} | hasFooter={true} | ||||
| buttonTitle="返回首页" | buttonTitle="返回首页" | ||||
| onButtonClick={() => navigate('/')} | |||||
| onButtonClick={() => navigate(HomeUrl)} | |||||
| ></KFEmpty> | ></KFEmpty> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -51,13 +51,12 @@ function ActiveLearnInstance() { | |||||
| const [res] = await to(getActiveLearnInsReq(instanceId)); | const [res] = await to(getActiveLearnInsReq(instanceId)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const info = res.data as ActiveLearnInstanceData; | const info = res.data as ActiveLearnInstanceData; | ||||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; | |||||
| const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; | |||||
| // 解析配置参数 | // 解析配置参数 | ||||
| const paramJson = parseJsonText(param); | const paramJson = parseJsonText(param); | ||||
| if (paramJson) { | if (paramJson) { | ||||
| setExperimentInfo({ | setExperimentInfo({ | ||||
| ...paramJson.data, | ...paramJson.data, | ||||
| create_time, | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -69,7 +68,7 @@ function ActiveLearnInstance() { | |||||
| return; | return; | ||||
| } | } | ||||
| // 进行节点状态 | |||||
| // 设置总 workflow 状态 | |||||
| const nodeStatusJson = parseJsonText(node_status); | const nodeStatusJson = parseJsonText(node_status); | ||||
| if (nodeStatusJson) { | if (nodeStatusJson) { | ||||
| setNodes(nodeStatusJson); | setNodes(nodeStatusJson); | ||||
| @@ -106,18 +105,17 @@ function ActiveLearnInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| // 节点 | |||||
| // 设置节点 | |||||
| setNodes(nodes); | setNodes(nodes); | ||||
| // 设置总 workflow 状态 | |||||
| const workflowStatus = Object.values(nodes).find((node: any) => | const workflowStatus = Object.values(nodes).find((node: any) => | ||||
| node.displayName.startsWith(NodePrefix), | node.displayName.startsWith(NodePrefix), | ||||
| ) as NodeStatus; | ) as NodeStatus; | ||||
| // 设置工作流状态 | |||||
| if (workflowStatus) { | if (workflowStatus) { | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| // 实验结束,关闭 SSE | |||||
| // 实验结束,关闭 SSE,获取实验实例结果 | |||||
| if ( | if ( | ||||
| workflowStatus.phase !== ExperimentStatus.Pending && | workflowStatus.phase !== ExperimentStatus.Pending && | ||||
| workflowStatus.phase !== ExperimentStatus.Running | workflowStatus.phase !== ExperimentStatus.Running | ||||
| @@ -152,8 +150,8 @@ function ActiveLearnInstance() { | |||||
| <ActiveLearnBasic | <ActiveLearnBasic | ||||
| className={styles['active-learn-instance__basic']} | className={styles['active-learn-instance__basic']} | ||||
| info={experimentInfo} | info={experimentInfo} | ||||
| runStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status} | |||||
| workflowStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status as ExperimentStatus} | |||||
| isInstance | isInstance | ||||
| /> | /> | ||||
| ), | ), | ||||
| @@ -181,7 +179,7 @@ function ActiveLearnInstance() { | |||||
| }, | }, | ||||
| { | { | ||||
| key: TabKeys.History, | key: TabKeys.History, | ||||
| label: '训练列表', | |||||
| label: '运行列表', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentHistory | <ExperimentHistory | ||||
| @@ -1,6 +1,6 @@ | |||||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | ||||
| import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { | import { | ||||
| classifierAlgorithms, | classifierAlgorithms, | ||||
| FrameworkType, | FrameworkType, | ||||
| @@ -28,18 +28,18 @@ type BasicInfoProps = { | |||||
| info?: ActiveLearnData; | info?: ActiveLearnData; | ||||
| className?: string; | className?: string; | ||||
| isInstance?: boolean; | isInstance?: boolean; | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| }; | }; | ||||
| function BasicInfo({ | function BasicInfo({ | ||||
| info, | info, | ||||
| className, | className, | ||||
| runStatus, | |||||
| workflowStatus, | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: BasicInfoProps) { | }: BasicInfoProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -154,7 +154,7 @@ function BasicInfo({ | |||||
| value: info.dataset_py, | value: info.dataset_py, | ||||
| }, | }, | ||||
| { | { | ||||
| label: '数据集类名', | |||||
| label: '数据集处理类名', | |||||
| value: info.dataset_class_name, | value: info.dataset_class_name, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -212,12 +212,8 @@ function BasicInfo({ | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['active-learn-basic'], className)}> | <div className={classNames(styles['active-learn-basic'], className)}> | ||||
| {isInstance && runStatus && ( | |||||
| <ExperimentRunBasic | |||||
| create_time={info?.create_time} | |||||
| runStatus={runStatus} | |||||
| instanceStatus={instanceStatus} | |||||
| /> | |||||
| {isInstance && workflowStatus && ( | |||||
| <ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} /> | |||||
| )} | )} | ||||
| {!isInstance && ( | {!isInstance && ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -17,6 +17,11 @@ import { | |||||
| function ExecuteConfig() { | function ExecuteConfig() { | ||||
| const form = Form.useFormInstance(); | const form = Form.useFormInstance(); | ||||
| const task_type = Form.useWatch('task_type', form); | |||||
| const queryStrategiesOptions = | |||||
| task_type === AutoMLTaskType.Classification | |||||
| ? queryStrategies.slice(0, 2) | |||||
| : queryStrategies.slice(2); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| @@ -101,16 +106,16 @@ function ExecuteConfig() { | |||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集类名" | |||||
| label="数据集处理类名" | |||||
| name="dataset_class_name" | name="dataset_class_name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入数据集类名', | |||||
| message: '请输入数据集处理类名', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据集类名" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入数据集处理类名" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -488,7 +493,12 @@ function ExecuteConfig() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Select placeholder="请选择查询策略" options={queryStrategies} showSearch allowClear /> | |||||
| <Select | |||||
| placeholder="请选择查询策略" | |||||
| options={queryStrategiesOptions} | |||||
| showSearch | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -87,4 +87,8 @@ export const queryStrategies = [ | |||||
| label: 'upper_confidence_bound', | label: 'upper_confidence_bound', | ||||
| value: 'upper_confidence_bound', | value: 'upper_confidence_bound', | ||||
| }, | }, | ||||
| { | |||||
| label: 'probability_of_improvement', | |||||
| value: 'probability_of_improvement', | |||||
| }, | |||||
| ]; | ]; | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types'; | import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types'; | ||||
| import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty'; | |||||
| import LogList from '@/pages/Experiment/components/LogList'; | import LogList from '@/pages/Experiment/components/LogList'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import { Tabs } from 'antd'; | import { Tabs } from 'antd'; | ||||
| @@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| // icon: <KFIcon type="icon-rizhi1" />, | // icon: <KFIcon type="icon-rizhi1" />, | ||||
| children: ( | children: ( | ||||
| <div className={styles['experiment-log__tabs__log']}> | <div className={styles['experiment-log__tabs__log']}> | ||||
| {trainCloneNodeStatus && ( | |||||
| {trainCloneNodeStatus ? ( | |||||
| <LogList | <LogList | ||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| @@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| instanceNodeStartTime={trainCloneNodeStatus.startedAt} | instanceNodeStartTime={trainCloneNodeStatus.startedAt} | ||||
| instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} | instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} | ||||
| ></LogList> | ></LogList> | ||||
| ) : ( | |||||
| <EmptyLog /> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| @@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| // icon: <KFIcon type="icon-rizhi1" />, | // icon: <KFIcon type="icon-rizhi1" />, | ||||
| children: ( | children: ( | ||||
| <div className={styles['experiment-log__tabs__log']}> | <div className={styles['experiment-log__tabs__log']}> | ||||
| {hpoNodeStatus && ( | |||||
| {hpoNodeStatus ? ( | |||||
| <LogList | <LogList | ||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| @@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| instanceNodeStartTime={hpoNodeStatus.startedAt} | instanceNodeStartTime={hpoNodeStatus.startedAt} | ||||
| instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} | instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} | ||||
| ></LogList> | ></LogList> | ||||
| ) : ( | |||||
| <EmptyLog /> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| @@ -1,6 +1,7 @@ | |||||
| import { setSessionToken } from '@/access'; | import { setSessionToken } from '@/access'; | ||||
| import { loginByOauth2Req } from '@/services/auth'; | import { loginByOauth2Req } from '@/services/auth'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import { history, useModel, useSearchParams } from '@umijs/max'; | import { history, useModel, useSearchParams } from '@umijs/max'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { useCallback, useEffect } from 'react'; | import { useCallback, useEffect } from 'react'; | ||||
| @@ -36,7 +37,11 @@ function Authorize() { | |||||
| setSessionToken(access_token, access_token, expires_in); | setSessionToken(access_token, access_token, expires_in); | ||||
| message.success('登录成功!'); | message.success('登录成功!'); | ||||
| await fetchUserInfo(); | await fetchUserInfo(); | ||||
| history.push(redirect || '/'); | |||||
| const redierctUrl = SessionStorage.getItem(SessionStorage.redirectUrl); | |||||
| console.log('redirect', redirect); | |||||
| console.log('redierctUrl', redierctUrl); | |||||
| history.replace(redirect || redierctUrl || '/workspace'); | |||||
| // SessionStorage.removeItem(SessionStorage.redirectUrl); | |||||
| } | } | ||||
| }, [fetchUserInfo, redirect, code]); | }, [fetchUserInfo, redirect, code]); | ||||
| @@ -51,7 +51,7 @@ function AutoMLInstance() { | |||||
| const [res] = await to(getExperimentInsReq(instanceId)); | const [res] = await to(getExperimentInsReq(instanceId)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const info = res.data as AutoMLInstanceData; | const info = res.data as AutoMLInstanceData; | ||||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info; | |||||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, type } = info; | |||||
| setType(type); | setType(type); | ||||
| // 解析配置参数 | // 解析配置参数 | ||||
| @@ -59,7 +59,6 @@ function AutoMLInstance() { | |||||
| if (paramJson) { | if (paramJson) { | ||||
| setAutoMLInfo({ | setAutoMLInfo({ | ||||
| ...paramJson.data, | ...paramJson.data, | ||||
| create_time, | |||||
| type, | type, | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -95,7 +94,10 @@ function AutoMLInstance() { | |||||
| }; | }; | ||||
| const setupSSE = (name: string, namespace: string) => { | const setupSSE = (name: string, namespace: string) => { | ||||
| const { origin } = location; | |||||
| let { origin } = location; | |||||
| if (process.env.NODE_ENV === 'development') { | |||||
| origin = 'http://172.20.32.235:31213'; | |||||
| } | |||||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | ||||
| const evtSource = new EventSource( | const evtSource = new EventSource( | ||||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | ||||
| @@ -110,17 +112,17 @@ function AutoMLInstance() { | |||||
| if (dataJson) { | if (dataJson) { | ||||
| const nodes = dataJson?.result?.object?.status?.nodes; | const nodes = dataJson?.result?.object?.status?.nodes; | ||||
| if (nodes) { | if (nodes) { | ||||
| // 节点 | |||||
| // 设置节点 | |||||
| setNodes(nodes); | setNodes(nodes); | ||||
| // 设置总 workflow 状态 | |||||
| const workflowStatus = Object.values(nodes).find((node: any) => | const workflowStatus = Object.values(nodes).find((node: any) => | ||||
| node.displayName.startsWith(NodePrefix), | node.displayName.startsWith(NodePrefix), | ||||
| ) as NodeStatus; | ) as NodeStatus; | ||||
| if (workflowStatus) { | if (workflowStatus) { | ||||
| setWorkflowStatus(workflowStatus); | setWorkflowStatus(workflowStatus); | ||||
| // 实验结束,关闭 SSE | |||||
| // 实验结束,关闭 SSE,获取实验实例结果 | |||||
| if ( | if ( | ||||
| workflowStatus.phase !== ExperimentStatus.Pending && | workflowStatus.phase !== ExperimentStatus.Pending && | ||||
| workflowStatus.phase !== ExperimentStatus.Running | workflowStatus.phase !== ExperimentStatus.Running | ||||
| @@ -155,8 +157,8 @@ function AutoMLInstance() { | |||||
| <AutoMLBasic | <AutoMLBasic | ||||
| className={styles['auto-ml-instance__basic']} | className={styles['auto-ml-instance__basic']} | ||||
| info={autoMLInfo} | info={autoMLInfo} | ||||
| runStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status} | |||||
| workflowStatus={workflowStatus} | |||||
| instanceStatus={instanceInfo?.status as ExperimentStatus} | |||||
| isInstance | isInstance | ||||
| /> | /> | ||||
| ), | ), | ||||
| @@ -202,7 +204,7 @@ function AutoMLInstance() { | |||||
| } | } | ||||
| : { | : { | ||||
| key: TabKeys.History, | key: TabKeys.History, | ||||
| label: '试验列表', | |||||
| label: '运行列表', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | icon: <KFIcon type="icon-Trialliebiao" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentHistory | <ExperimentHistory | ||||
| @@ -1,7 +1,7 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 自主机器学习列表 | |||||
| * @Description: 自动机器学习列表 | |||||
| */ | */ | ||||
| import ExperimentList, { ExperimentListType } from '../components/ExperimentList'; | import ExperimentList, { ExperimentListType } from '../components/ExperimentList'; | ||||
| @@ -6,11 +6,22 @@ import { | |||||
| autoMLEnsembleClassOptions, | autoMLEnsembleClassOptions, | ||||
| autoMLTaskTypeOptions, | autoMLTaskTypeOptions, | ||||
| } from '@/enums'; | } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { | |||||
| classificationAlgorithms, | |||||
| featureAlgorithms, | |||||
| regressorAlgorithms, | |||||
| } from '@/pages/AutoML/components/CreateForm/utils'; | |||||
| import { AutoMLData } from '@/pages/AutoML/types'; | import { AutoMLData } from '@/pages/AutoML/types'; | ||||
| import { type NodeStatus } from '@/types'; | import { type NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format'; | |||||
| import { | |||||
| formatBoolean, | |||||
| formatDataset, | |||||
| formatDate, | |||||
| formatEnum, | |||||
| type EnumOptions, | |||||
| } from '@/utils/format'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import ExperimentRunBasic from '../ExperimentRunBasic'; | import ExperimentRunBasic from '../ExperimentRunBasic'; | ||||
| @@ -21,6 +32,7 @@ const formatOptimizeMode = (value: boolean) => { | |||||
| return value ? '越大越好' : '越小越好'; | return value ? '越大越好' : '越小越好'; | ||||
| }; | }; | ||||
| // 格式化权重 | |||||
| const formatMetricsWeight = (value: string) => { | const formatMetricsWeight = (value: string) => { | ||||
| if (!value) { | if (!value) { | ||||
| return '--'; | return '--'; | ||||
| @@ -34,22 +46,37 @@ const formatMetricsWeight = (value: string) => { | |||||
| .join('\n'); | .join('\n'); | ||||
| }; | }; | ||||
| // 格式化算法 | |||||
| const formatAlgorithm = (algorithms: EnumOptions[]) => { | |||||
| return (value: string) => { | |||||
| if (!value) { | |||||
| return '--'; | |||||
| } | |||||
| const list = value | |||||
| .split(',') | |||||
| .filter((v) => v !== '') | |||||
| .map((v) => v.trim()); | |||||
| return list.map((v) => formatEnum(algorithms)(v)).join(','); | |||||
| }; | |||||
| }; | |||||
| type AutoMLBasicProps = { | type AutoMLBasicProps = { | ||||
| info?: AutoMLData; | info?: AutoMLData; | ||||
| className?: string; | className?: string; | ||||
| isInstance?: boolean; | isInstance?: boolean; | ||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| instanceCreateTime?: string; | |||||
| }; | }; | ||||
| function AutoMLBasic({ | function AutoMLBasic({ | ||||
| info, | info, | ||||
| className, | className, | ||||
| runStatus, | |||||
| workflowStatus, | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: AutoMLBasicProps) { | }: AutoMLBasicProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -95,10 +122,12 @@ function AutoMLBasic({ | |||||
| { | { | ||||
| label: '特征预处理算法', | label: '特征预处理算法', | ||||
| value: info.include_feature_preprocessor, | value: info.include_feature_preprocessor, | ||||
| format: formatAlgorithm(featureAlgorithms), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '排除的特征预处理算法', | label: '排除的特征预处理算法', | ||||
| value: info.exclude_feature_preprocessor, | value: info.exclude_feature_preprocessor, | ||||
| format: formatAlgorithm(featureAlgorithms), | |||||
| }, | }, | ||||
| { | { | ||||
| label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', | ||||
| @@ -106,6 +135,11 @@ function AutoMLBasic({ | |||||
| info.task_type === AutoMLTaskType.Regression | info.task_type === AutoMLTaskType.Regression | ||||
| ? info.include_regressor | ? info.include_regressor | ||||
| : info.include_classifier, | : info.include_classifier, | ||||
| format: formatAlgorithm( | |||||
| info.task_type === AutoMLTaskType.Regression | |||||
| ? regressorAlgorithms | |||||
| : classificationAlgorithms, | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', | label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', | ||||
| @@ -113,6 +147,11 @@ function AutoMLBasic({ | |||||
| info.task_type === AutoMLTaskType.Regression | info.task_type === AutoMLTaskType.Regression | ||||
| ? info.exclude_regressor | ? info.exclude_regressor | ||||
| : info.exclude_classifier, | : info.exclude_classifier, | ||||
| format: formatAlgorithm( | |||||
| info.task_type === AutoMLTaskType.Regression | |||||
| ? regressorAlgorithms | |||||
| : classificationAlgorithms, | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '集成方式', | label: '集成方式', | ||||
| @@ -292,12 +331,8 @@ function AutoMLBasic({ | |||||
| return ( | return ( | ||||
| <div className={classNames(styles['auto-ml-basic'], className)}> | <div className={classNames(styles['auto-ml-basic'], className)}> | ||||
| {isInstance && runStatus && ( | |||||
| <ExperimentRunBasic | |||||
| create_time={info?.create_time} | |||||
| runStatus={runStatus} | |||||
| instanceStatus={instanceStatus} | |||||
| /> | |||||
| {isInstance && workflowStatus && ( | |||||
| <ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} /> | |||||
| )} | )} | ||||
| {!isInstance && ( | {!isInstance && ( | ||||
| <ConfigInfo | <ConfigInfo | ||||
| @@ -8,69 +8,7 @@ import { | |||||
| autoMLTaskTypeOptions, | autoMLTaskTypeOptions, | ||||
| } from '@/enums'; | } from '@/enums'; | ||||
| import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; | import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; | ||||
| // 分类算法 | |||||
| const classificationAlgorithms = [ | |||||
| 'adaboost', | |||||
| 'bernoulli_nb', | |||||
| 'decision_tree', | |||||
| 'extra_trees', | |||||
| 'gaussian_nb', | |||||
| 'gradient_boosting', | |||||
| 'k_nearest_neighbors', | |||||
| 'lda', | |||||
| 'liblinear_svc', | |||||
| 'libsvm_svc', | |||||
| 'mlp', | |||||
| 'multinomial_nb', | |||||
| 'passive_aggressive', | |||||
| 'qda', | |||||
| 'random_forest', | |||||
| 'sgd', | |||||
| 'LightGBMClassification', | |||||
| 'XGBoostClassification', | |||||
| 'StackingClassification', | |||||
| ].map((name) => ({ label: name, value: name })); | |||||
| // 回归算法 | |||||
| const regressorAlgorithms = [ | |||||
| 'adaboost', | |||||
| 'ard_regression', | |||||
| 'decision_tree', | |||||
| 'extra_trees', | |||||
| 'gaussian_process', | |||||
| 'gradient_boosting', | |||||
| 'k_nearest_neighbors', | |||||
| 'liblinear_svr', | |||||
| 'libsvm_svr', | |||||
| 'mlp', | |||||
| 'random_forest', | |||||
| 'sgd', | |||||
| 'LightGBMRegression', | |||||
| 'XGBoostRegression', | |||||
| ].map((name) => ({ label: name, value: name })); | |||||
| // 特征预处理算法 | |||||
| const featureAlgorithms = [ | |||||
| 'densifier', | |||||
| 'extra_trees_preproc_for_classification', | |||||
| 'extra_trees_preproc_for_regression', | |||||
| 'fast_ica', | |||||
| 'feature_agglomeration', | |||||
| 'kernel_pca', | |||||
| 'kitchen_sinks', | |||||
| 'liblinear_svc_preprocessor', | |||||
| 'no_preprocessing', | |||||
| 'nystroem_sampler', | |||||
| 'pca', | |||||
| 'polynomial', | |||||
| 'random_trees_embedding', | |||||
| 'select_percentile_classification', | |||||
| 'select_percentile_regression', | |||||
| 'select_rates_classification', | |||||
| 'select_rates_regression', | |||||
| 'truncatedSVD', | |||||
| ].map((name) => ({ label: name, value: name })); | |||||
| import { classificationAlgorithms, featureAlgorithms, regressorAlgorithms } from './utils'; | |||||
| // 分类指标 | // 分类指标 | ||||
| export const classificationMetrics = [ | export const classificationMetrics = [ | ||||
| @@ -280,9 +218,9 @@ function ExecuteConfig() { | |||||
| <Form.Item | <Form.Item | ||||
| label="集成模型数量" | label="集成模型数量" | ||||
| name="ensemble_size" | name="ensemble_size" | ||||
| tooltip="集成模型数量,如果设置为0,则没有集成。默认50" | |||||
| tooltip="集成模型数量,必须是大于等于1的整数,默认50" | |||||
| > | > | ||||
| <InputNumber placeholder="请输入集成模型数量" min={0} precision={0} /> | |||||
| <InputNumber placeholder="请输入集成模型数量" min={1} precision={0} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -292,7 +230,7 @@ function ExecuteConfig() { | |||||
| <Form.Item | <Form.Item | ||||
| label="集成最佳模型数量" | label="集成最佳模型数量" | ||||
| name="ensemble_nbest" | name="ensemble_nbest" | ||||
| tooltip="仅集成最佳的N个模型" | |||||
| tooltip="仅集成最佳的N个模型,必须是大于等于1的整数" | |||||
| > | > | ||||
| <InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} /> | <InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} /> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -419,6 +357,7 @@ function ExecuteConfig() { | |||||
| <Form.Item | <Form.Item | ||||
| label="交叉验证折数" | label="交叉验证折数" | ||||
| name="folds" | name="folds" | ||||
| tooltip="交叉验证折数必须是大于等于2的整数" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -426,7 +365,7 @@ function ExecuteConfig() { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} /> | |||||
| <InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -0,0 +1,85 @@ | |||||
| // 分类算法 | |||||
| export const classificationAlgorithms = [ | |||||
| { label: 'adaboost (自适应提升算法)', value: 'adaboost' }, | |||||
| { label: 'bernoulli_nb (伯努利朴素贝叶斯)', value: 'bernoulli_nb' }, | |||||
| { label: 'decision_tree (决策树)', value: 'decision_tree' }, | |||||
| { label: 'extra_trees (极端随机树)', value: 'extra_trees' }, | |||||
| { label: 'gaussian_nb (高斯朴素贝叶斯)', value: 'gaussian_nb' }, | |||||
| { label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' }, | |||||
| { label: 'k_nearest_neighbors (k近邻)', value: 'k_nearest_neighbors' }, | |||||
| { label: 'lda (线性判别分析)', value: 'lda' }, | |||||
| { label: 'liblinear_svc (liblinear支持向量分类)', value: 'liblinear_svc' }, | |||||
| { label: 'libsvm_svc (libsvm支持向量分类)', value: 'libsvm_svc' }, | |||||
| { label: 'mlp (多层感知器)', value: 'mlp' }, | |||||
| { label: 'multinomial_nb (多项式朴素贝叶斯)', value: 'multinomial_nb' }, | |||||
| { label: 'passive_aggressive (被动攻击算法)', value: 'passive_aggressive' }, | |||||
| { label: 'qda (二次判别式分析)', value: 'qda' }, | |||||
| { label: 'random_forest (随机森林)', value: 'random_forest' }, | |||||
| { label: 'sgd (随机梯度下降)', value: 'sgd' }, | |||||
| { label: 'tablenet (表格网络)', value: 'tablenet' }, | |||||
| { label: 'LightGBMClassification (轻量梯度提升机分类)', value: 'LightGBMClassification' }, | |||||
| { label: 'XGBoostClassification (极端梯度提升机分类)', value: 'XGBoostClassification' }, | |||||
| { label: 'StackingClassification (堆叠泛化)', value: 'StackingClassification' }, | |||||
| ]; | |||||
| // 回归算法 | |||||
| export const regressorAlgorithms = [ | |||||
| { label: 'adaboost (自适应提升算法)', value: 'adaboost' }, | |||||
| { label: 'ard_regression (自动相关性确定回归)', value: 'ard_regression' }, | |||||
| { label: 'decision_tree (决策树)', value: 'decision_tree' }, | |||||
| { label: 'extra_trees (极端随机树)', value: 'extra_trees' }, | |||||
| { label: 'gaussian_process (高斯过程回归)', value: 'gaussian_process' }, | |||||
| { label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' }, | |||||
| { label: 'k_nearest_neighbors (梯度提升)', value: 'k_nearest_neighbors' }, | |||||
| { label: 'liblinear_svr (liblinear支持向量回归)', value: 'liblinear_svr' }, | |||||
| { label: 'libsvm_svr (libsvm支持向量回归)', value: 'libsvm_svr' }, | |||||
| { label: 'mlp (多层感知器)', value: 'mlp' }, | |||||
| { label: 'random_forest (随机森林)', value: 'random_forest' }, | |||||
| { label: 'sgd (随机梯度下降)', value: 'sgd' }, | |||||
| { label: 'LightGBMRegression (轻量梯度提升机回归)', value: 'LightGBMRegression' }, | |||||
| { label: 'XGBoostRegression (极端梯度提升机回归)', value: 'XGBoostRegression' }, | |||||
| ]; | |||||
| // 特征预处理算法 | |||||
| export const featureAlgorithms = [ | |||||
| { label: 'densifier (特征变换-数据增稠)', value: 'densifier' }, | |||||
| { | |||||
| label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)', | |||||
| value: 'extra_trees_preproc_for_classification', | |||||
| }, | |||||
| { | |||||
| label: 'extra_trees_preproc_for_regression (特征选择-回归任务极端随机树)', | |||||
| value: 'extra_trees_preproc_for_regression', | |||||
| }, | |||||
| { label: 'fast_ica (特征选择-快速独立成分分析)', value: 'fast_ica' }, | |||||
| { label: 'feature_agglomeration (特征变换-特征聚合)', value: 'feature_agglomeration' }, | |||||
| { label: 'kernel_pca (特征选择-核主成分分析)', value: 'kernel_pca' }, | |||||
| { label: 'kitchen_sinks (特征变换-随机特征映射)', value: 'kitchen_sinks' }, | |||||
| { | |||||
| label: 'liblinear_svc_preprocessor (特征选择-线性svc预处理器)', | |||||
| value: 'liblinear_svc_preprocessor', | |||||
| }, | |||||
| { label: 'miss_value_impute (缺失值填充)', value: 'miss_value_impute' }, | |||||
| { label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' }, | |||||
| { label: 'nystroem_sampler (特征变换-尼斯特罗姆采样器)', value: 'nystroem_sampler' }, | |||||
| { label: 'pca (特征选择-主成分分析)', value: 'pca' }, | |||||
| { label: 'polynomial (特征变换-多项式特征扩展)', value: 'polynomial' }, | |||||
| { label: 'random_trees_embedding (特征变换-随机森林特征嵌入)', value: 'random_trees_embedding' }, | |||||
| { | |||||
| label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)', | |||||
| value: 'select_percentile_classification', | |||||
| }, | |||||
| { | |||||
| label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)', | |||||
| value: 'select_percentile_regression', | |||||
| }, | |||||
| { | |||||
| label: 'select_rates_classification (特征选择-基于比率的分类特征选择)', | |||||
| value: 'select_rates_classification', | |||||
| }, | |||||
| { | |||||
| label: 'select_rates_regression (特征选择-基于比率的回归特征选择)', | |||||
| value: 'select_rates_regression', | |||||
| }, | |||||
| { label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' }, | |||||
| ]; | |||||
| @@ -54,10 +54,6 @@ | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| width: 200px; | width: 200px; | ||||
| .statusIcon { | |||||
| visibility: visible; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -34,7 +34,14 @@ function ExperimentInstanceList({ | |||||
| }: ExperimentInstanceListProps) { | }: ExperimentInstanceListProps) { | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const allIntanceIds = useMemo(() => { | const allIntanceIds = useMemo(() => { | ||||
| return experimentInsList?.map((item) => item.id) || []; | |||||
| return ( | |||||
| experimentInsList | |||||
| ?.filter( | |||||
| (item) => | |||||
| item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending, | |||||
| ) | |||||
| .map((item) => item.id) || [] | |||||
| ); | |||||
| }, [experimentInsList]); | }, [experimentInsList]); | ||||
| const [ | const [ | ||||
| selectedIns, | selectedIns, | ||||
| @@ -126,7 +133,12 @@ function ExperimentInstanceList({ | |||||
| <div> | <div> | ||||
| <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> | ||||
| <div className={styles.check}> | <div className={styles.check}> | ||||
| <Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox> | |||||
| <Checkbox | |||||
| checked={checked} | |||||
| indeterminate={indeterminate} | |||||
| disabled={allIntanceIds.length === 0} | |||||
| onChange={checkAll} | |||||
| ></Checkbox> | |||||
| </div> | </div> | ||||
| <div className={styles.index}>序号</div> | <div className={styles.index}>序号</div> | ||||
| <div className={styles.description}>运行时长</div> | <div className={styles.description}>运行时长</div> | ||||
| @@ -171,12 +183,8 @@ function ExperimentInstanceList({ | |||||
| {index + 1} | {index + 1} | ||||
| </a> | </a> | ||||
| <ExperimentInstanceComponent | <ExperimentInstanceComponent | ||||
| create_time={item.create_time} | |||||
| finish_time={item.finish_time} | |||||
| status={item.status as ExperimentStatus} | |||||
| argo_ins_name={item.argo_ins_name} | |||||
| argo_ins_ns={item.argo_ins_ns} | |||||
| experimentInsId={item.id} | |||||
| experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number} | |||||
| instance={item} | |||||
| ></ExperimentInstanceComponent> | ></ExperimentInstanceComponent> | ||||
| <div className={styles.operation}> | <div className={styles.operation}> | ||||
| <Button | <Button | ||||
| @@ -2,67 +2,70 @@ import RunDuration from '@/components/RunDuration'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | import { useSSE, type MessageHandler } from '@/hooks/useSSE'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { ExperimentInstance, NodeStatus } from '@/types'; | |||||
| import { ExperimentCompleted } from '@/utils/constant'; | import { ExperimentCompleted } from '@/utils/constant'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { getWorkflowStatus } from '@/utils/experiment'; | |||||
| import { Typography } from 'antd'; | import { Typography } from 'antd'; | ||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentInstanceProps = { | |||||
| create_time?: string; | |||||
| finish_time?: string; | |||||
| status: ExperimentStatus; | |||||
| argo_ins_name: string; | |||||
| argo_ins_ns: string; | |||||
| experimentInsId: number; | |||||
| type ExperimentInstanceComponentProps = { | |||||
| experimentId: number; | |||||
| instance: ExperimentInstance; | |||||
| }; | }; | ||||
| function ExperimentInstance({ | |||||
| create_time, | |||||
| finish_time, | |||||
| status, | |||||
| argo_ins_name, | |||||
| argo_ins_ns, | |||||
| experimentInsId, | |||||
| }: ExperimentInstanceProps) { | |||||
| function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) { | |||||
| const { id, argo_ins_name, argo_ins_ns, node_status } = instance; | |||||
| const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined; | |||||
| const status = instance.status as ExperimentStatus; | |||||
| const createTime = workflowStatus?.startedAt; | |||||
| const finishTime = workflowStatus?.finishedAt; | |||||
| const statusInfo = experimentStatusInfo[status]; | |||||
| const handleSSEMessage: MessageHandler = useCallback( | const handleSSEMessage: MessageHandler = useCallback( | ||||
| (experimentInsId: number, status: string, finish_time: string) => { | |||||
| (experimentId: number, experimentInsId: number, status: string, finishTime: string) => { | |||||
| window.postMessage({ | window.postMessage({ | ||||
| type: ExperimentCompleted, | type: ExperimentCompleted, | ||||
| payload: { | payload: { | ||||
| id: experimentInsId, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | status, | ||||
| finish_time, | |||||
| finishTime, | |||||
| }, | }, | ||||
| }); | }); | ||||
| }, | }, | ||||
| [], | [], | ||||
| ); | ); | ||||
| useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage); | |||||
| return ( | return ( | ||||
| <React.Fragment> | <React.Fragment> | ||||
| <div className={styles.description}> | <div className={styles.description}> | ||||
| <RunDuration createTime={create_time} finishTime={finish_time} /> | |||||
| <RunDuration createTime={createTime} finishTime={finishTime} /> | |||||
| </div> | </div> | ||||
| <div className={styles.startTime}> | <div className={styles.startTime}> | ||||
| <Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}> | |||||
| {formatDate(create_time)} | |||||
| <Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}> | |||||
| {formatDate(createTime)} | |||||
| </Typography.Text> | </Typography.Text> | ||||
| </div> | </div> | ||||
| <div className={styles.statusBox}> | <div className={styles.statusBox}> | ||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={experimentStatusInfo[status]?.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}> | |||||
| {experimentStatusInfo[status]?.label} | |||||
| </span> | |||||
| {statusInfo ? ( | |||||
| <> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span style={{ color: statusInfo.color }}>{statusInfo.label}</span> | |||||
| </> | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| </div> | </div> | ||||
| </React.Fragment> | </React.Fragment> | ||||
| ); | ); | ||||
| } | } | ||||
| export default ExperimentInstance; | |||||
| export default ExperimentInstanceComponent; | |||||
| @@ -8,6 +8,7 @@ import { | |||||
| batchDeleteActiveLearnInsReq, | batchDeleteActiveLearnInsReq, | ||||
| deleteActiveLearnInsReq, | deleteActiveLearnInsReq, | ||||
| deleteActiveLearnReq, | deleteActiveLearnReq, | ||||
| editActiveLearnInsReq, | |||||
| getActiveLearnInsListReq, | getActiveLearnInsListReq, | ||||
| getActiveLearnListReq, | getActiveLearnListReq, | ||||
| runActiveLearnReq, | runActiveLearnReq, | ||||
| @@ -17,6 +18,7 @@ import { | |||||
| batchDeleteExperimentInsReq, | batchDeleteExperimentInsReq, | ||||
| deleteAutoMLReq, | deleteAutoMLReq, | ||||
| deleteExperimentInsReq, | deleteExperimentInsReq, | ||||
| editExperimentInsReq, | |||||
| getAutoMLListReq, | getAutoMLListReq, | ||||
| getExperimentInsListReq, | getExperimentInsListReq, | ||||
| runAutoMLReq, | runAutoMLReq, | ||||
| @@ -26,6 +28,7 @@ import { | |||||
| batchDeleteRayInsReq, | batchDeleteRayInsReq, | ||||
| deleteRayInsReq, | deleteRayInsReq, | ||||
| deleteRayReq, | deleteRayReq, | ||||
| editRayInsReq, | |||||
| getRayInsListReq, | getRayInsListReq, | ||||
| getRayListReq, | getRayListReq, | ||||
| runRayReq, | runRayReq, | ||||
| @@ -39,18 +42,20 @@ export enum ExperimentListType { | |||||
| } | } | ||||
| type ExperimentListInfo = { | type ExperimentListInfo = { | ||||
| getListReq: (params: any) => Promise<any>; // 获取列表 | |||||
| getInsListReq: (params: any) => Promise<any>; // 获取实例列表 | |||||
| getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表 | |||||
| getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表 | |||||
| deleteRecordReq: (params: any) => Promise<any>; // 删除 | deleteRecordReq: (params: any) => Promise<any>; // 删除 | ||||
| runRecordReq: (params: any) => Promise<any>; // 运行 | runRecordReq: (params: any) => Promise<any>; // 运行 | ||||
| deleteInsReq: (params: any) => Promise<any>; // 删除实例 | deleteInsReq: (params: any) => Promise<any>; // 删除实例 | ||||
| batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 | batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 | ||||
| stopInsReq: (params: any) => Promise<any>; // 终止实例 | stopInsReq: (params: any) => Promise<any>; // 终止实例 | ||||
| editInsReq: (params: any) => Promise<any>; // 编辑实例 | |||||
| title: string; // 标题 | title: string; // 标题 | ||||
| pathPrefix: string; // 路由路径前缀 | pathPrefix: string; // 路由路径前缀 | ||||
| idProperty: string; // ID属性 | idProperty: string; // ID属性 | ||||
| nameProperty: string; // 名称属性 | nameProperty: string; // 名称属性 | ||||
| descProperty: string; // 描述属性 | descProperty: string; // 描述属性 | ||||
| idInsProperty: string; // 实例返回的ID属性 | |||||
| }; | }; | ||||
| export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { | export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { | ||||
| @@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteExperimentInsReq, | deleteInsReq: deleteExperimentInsReq, | ||||
| batchDeleteInsReq: batchDeleteExperimentInsReq, | batchDeleteInsReq: batchDeleteExperimentInsReq, | ||||
| stopInsReq: stopExperimentInsReq, | stopInsReq: stopExperimentInsReq, | ||||
| title: '自主机器学习', | |||||
| editInsReq: editExperimentInsReq, | |||||
| title: '自动机器学习', | |||||
| pathPrefix: 'automl', | pathPrefix: 'automl', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'machineLearnId', | idProperty: 'machineLearnId', | ||||
| idInsProperty: 'machine_learn_id', | |||||
| }, | }, | ||||
| [ExperimentListType.HyperParameter]: { | [ExperimentListType.HyperParameter]: { | ||||
| getListReq: getRayListReq, | getListReq: getRayListReq, | ||||
| @@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteRayInsReq, | deleteInsReq: deleteRayInsReq, | ||||
| batchDeleteInsReq: batchDeleteRayInsReq, | batchDeleteInsReq: batchDeleteRayInsReq, | ||||
| stopInsReq: stopRayInsReq, | stopInsReq: stopRayInsReq, | ||||
| editInsReq: editRayInsReq, | |||||
| title: '超参数自动寻优', | title: '超参数自动寻优', | ||||
| pathPrefix: 'hyperparameter', | pathPrefix: 'hyperparameter', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'rayId', | idProperty: 'rayId', | ||||
| idInsProperty: 'ray_id', | |||||
| }, | }, | ||||
| [ExperimentListType.ActiveLearn]: { | [ExperimentListType.ActiveLearn]: { | ||||
| getListReq: getActiveLearnListReq, | getListReq: getActiveLearnListReq, | ||||
| @@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo | |||||
| deleteInsReq: deleteActiveLearnInsReq, | deleteInsReq: deleteActiveLearnInsReq, | ||||
| batchDeleteInsReq: batchDeleteActiveLearnInsReq, | batchDeleteInsReq: batchDeleteActiveLearnInsReq, | ||||
| stopInsReq: stopActiveLearnInsReq, | stopInsReq: stopActiveLearnInsReq, | ||||
| editInsReq: editActiveLearnInsReq, | |||||
| title: '自动学习', | title: '自动学习', | ||||
| pathPrefix: 'active-learn', | pathPrefix: 'active-learn', | ||||
| nameProperty: 'name', | nameProperty: 'name', | ||||
| descProperty: 'description', | descProperty: 'description', | ||||
| idProperty: 'activeLearnId', | idProperty: 'activeLearnId', | ||||
| idInsProperty: 'active_learn_id', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -30,7 +30,7 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import ExperimentInstanceList from '../ExperimentInstanceList'; | import ExperimentInstanceList from '../ExperimentInstanceList'; | ||||
| import { ExperimentListType, experimentListConfig } from './config'; | import { ExperimentListType, experimentListConfig } from './config'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); | const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); | ||||
| const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); | const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); | ||||
| const [experimentInsTotal, setExperimentInsTotal] = useState(0); | const [experimentInsTotal, setExperimentInsTotal] = useState(0); | ||||
| const [now] = useServerTime(); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| cacheState?.pagination ?? { | cacheState?.pagination ?? { | ||||
| current: 1, | current: 1, | ||||
| @@ -60,38 +59,37 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| }, | }, | ||||
| ); | ); | ||||
| const config = experimentListConfig[type]; | const config = experimentListConfig[type]; | ||||
| const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>(); | |||||
| // 获取自主机器学习或超参数自动优化列表 | |||||
| const getAutoMLList = useCallback(async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| [config.nameProperty]: searchText || undefined, | |||||
| }; | |||||
| const request = config.getListReq; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }, [pagination, searchText, config]); | |||||
| const [now] = useServerTime(); | |||||
| useEffect(() => { | |||||
| getAutoMLList(); | |||||
| }, [getAutoMLList]); | |||||
| // 获取实验列表 | |||||
| const getExperimentList = useCallback( | |||||
| async (skipLoading: boolean = false) => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| [config.nameProperty]: searchText || undefined, | |||||
| }; | |||||
| const request = config.getListReq; | |||||
| const [res] = await to(request(params, skipLoading)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }, | |||||
| [pagination, searchText, config], | |||||
| ); | |||||
| // 获取实验实例列表 | // 获取实验实例列表 | ||||
| const getExperimentInsList = useCallback( | const getExperimentInsList = useCallback( | ||||
| async (recordId: number, page: number, size: number) => { | |||||
| async (recordId: number, page: number, size: number, skipLoading: boolean = false) => { | |||||
| const params = { | const params = { | ||||
| [config.idProperty]: recordId, | [config.idProperty]: recordId, | ||||
| page: page, | page: page, | ||||
| size: size, | size: size, | ||||
| }; | }; | ||||
| const request = config.getInsListReq; | const request = config.getInsListReq; | ||||
| const [res] = await to(request(params)); | |||||
| const [res] = await to(request(params, skipLoading)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| try { | try { | ||||
| @@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // 刷新实验列表状态, | // 刷新实验列表状态, | ||||
| // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 | // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 | ||||
| const refreshExperimentList = useCallback(() => { | |||||
| getAutoMLList(); | |||||
| }, [getAutoMLList]); | |||||
| const refreshExperimentList = useCallback( | |||||
| (skipLoading: boolean = false) => { | |||||
| getExperimentList(skipLoading); | |||||
| }, | |||||
| [getExperimentList], | |||||
| ); | |||||
| // 刷新实验实例列表 | // 刷新实验实例列表 | ||||
| const refreshExperimentIns = useCallback( | const refreshExperimentIns = useCallback( | ||||
| (experimentId: number) => { | |||||
| (experimentId: number, skipLoading: boolean = false) => { | |||||
| const length = experimentInsList.length; | const length = experimentInsList.length; | ||||
| getExperimentInsList(experimentId, 0, length); | |||||
| getExperimentInsList(experimentId, 0, length, skipLoading); | |||||
| }, | }, | ||||
| [getExperimentInsList, experimentInsList], | [getExperimentInsList, experimentInsList], | ||||
| ); | ); | ||||
| // 新增,删除版本时,重置分页,然后刷新版本列表 | |||||
| // 更新实验实例状态 | |||||
| const editExperimentIns = useCallback( | |||||
| async ( | |||||
| experimentId: number, | |||||
| experimentInsId: number, | |||||
| status: ExperimentStatus, | |||||
| argo_ins_name: string, | |||||
| argo_ins_ns: string, | |||||
| ) => { | |||||
| const params = { | |||||
| [config.idInsProperty]: experimentId, | |||||
| id: experimentInsId, | |||||
| status: status, | |||||
| argo_ins_name, | |||||
| argo_ins_ns, | |||||
| }; | |||||
| const request = config.editInsReq; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data) { | |||||
| refreshExperimentIns(experimentId, true); | |||||
| refreshExperimentList(true); | |||||
| } | |||||
| }, | |||||
| [config, refreshExperimentIns, refreshExperimentList], | |||||
| ); | |||||
| // 获取实验列表 | |||||
| useEffect(() => { | |||||
| getExperimentList(); | |||||
| }, [getExperimentList]); | |||||
| // expandedRowKeys 变化 | |||||
| useEffect(() => { | |||||
| if (expandedRowKeys.length > 0) { | |||||
| getExperimentInsList(expandedRowKeys[0], 0, 5); | |||||
| refreshExperimentList(); | |||||
| } | |||||
| }, [expandedRowKeys, getExperimentInsList, refreshExperimentList]); | |||||
| // 实验实例状态变化 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const handleMessage = (e: MessageEvent) => { | const handleMessage = (e: MessageEvent) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { id, status, finish_time } = payload; | |||||
| const { experimentId, experimentInsId, status /*finishTime*/ } = payload; | |||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | |||||
| // console.log( | |||||
| // '实验实例状态变化', | |||||
| // currentIns?.status, | |||||
| // status, | |||||
| // experimentId, | |||||
| // experimentInsId, | |||||
| // finishTime, | |||||
| // ); | |||||
| if ( | |||||
| !currentIns || | |||||
| currentIns.status === ExperimentStatus.Terminated || | |||||
| currentIns.status === status | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| // 修改实例的状态和结束时间 | |||||
| setExperimentInsList((prev) => | |||||
| prev.map((v) => | |||||
| v.id === id | |||||
| ? { | |||||
| ...v, | |||||
| status: status, | |||||
| finish_time: finish_time, | |||||
| } | |||||
| : v, | |||||
| ), | |||||
| // refreshExperimentList(true); | |||||
| // refreshExperimentIns(experimentId); | |||||
| editExperimentIns( | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| status, | |||||
| currentIns.argo_ins_name, | |||||
| currentIns.argo_ins_ns, | |||||
| ); | ); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| } | |||||
| timerRef.current = setTimeout(() => { | |||||
| refreshExperimentList(); | |||||
| }, 10000); | |||||
| // 修改实例的状态和结束时间 | |||||
| // setExperimentInsList((prev) => | |||||
| // prev.map((v) => | |||||
| // v.id === experimentInsId | |||||
| // ? { | |||||
| // ...v, | |||||
| // status: status, | |||||
| // finish_time: finishTime, | |||||
| // } | |||||
| // : v, | |||||
| // ), | |||||
| // ); | |||||
| } | } | ||||
| }; | }; | ||||
| window.addEventListener('message', handleMessage); | window.addEventListener('message', handleMessage); | ||||
| return () => { | return () => { | ||||
| window.removeEventListener('message', handleMessage); | window.removeEventListener('message', handleMessage); | ||||
| if (timerRef.current) { | |||||
| clearTimeout(timerRef.current); | |||||
| timerRef.current = undefined; | |||||
| } | |||||
| }; | }; | ||||
| }, [refreshExperimentList]); | |||||
| }, [experimentInsList, editExperimentIns]); | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch: SearchProps['onSearch'] = (value) => { | const onSearch: SearchProps['onSearch'] = (value) => { | ||||
| @@ -207,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| expandedRowKeys, | |||||
| }); | }); | ||||
| if (record) { | if (record) { | ||||
| @@ -225,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| expandedRowKeys, | |||||
| }); | }); | ||||
| navigate(`info/${record.id}`); | navigate(`info/${record.id}`); | ||||
| @@ -237,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| if (res) { | if (res) { | ||||
| message.success('运行成功'); | message.success('运行成功'); | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| refreshExperimentList(); | |||||
| getExperimentInsList(record.id, 0, 5); | |||||
| // getExperimentInsList(record.id, 0, 5); | |||||
| // refreshExperimentList(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -248,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| setExperimentInsList([]); | setExperimentInsList([]); | ||||
| if (expanded) { | if (expanded) { | ||||
| setExpandedRowKeys([record.id]); | setExpandedRowKeys([record.id]); | ||||
| getExperimentInsList(record.id, 0, 5); | |||||
| refreshExperimentList(); | |||||
| // getExperimentInsList(record.id, 0, 5); | |||||
| // refreshExperimentList(); | |||||
| } else { | } else { | ||||
| setExpandedRowKeys([]); | setExpandedRowKeys([]); | ||||
| } | } | ||||
| @@ -257,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // 跳转到实验实例详情 | // 跳转到实验实例详情 | ||||
| const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { | const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { | ||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| expandedRowKeys, | |||||
| }); | |||||
| navigate(`instance/${autoML.id}/${record.id}`); | navigate(`instance/${autoML.id}/${record.id}`); | ||||
| }; | }; | ||||
| @@ -269,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| // 实验实例终止 | // 实验实例终止 | ||||
| const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { | const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { | ||||
| // 刷新实验列表 | |||||
| refreshExperimentList(); | |||||
| // 修改实例的状态和结束时间 | |||||
| setExperimentInsList((prevList) => { | setExperimentInsList((prevList) => { | ||||
| return prevList.map((item) => { | return prevList.map((item) => { | ||||
| if (item.id === experimentIns.id) { | if (item.id === experimentIns.id) { | ||||
| @@ -283,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| return item; | return item; | ||||
| }); | }); | ||||
| }); | }); | ||||
| // 刷新实验列表和实例列表 | |||||
| refreshExperimentList(true); | |||||
| if (expandedRowKeys.length > 0) { | |||||
| refreshExperimentIns(expandedRowKeys[0]); | |||||
| } | |||||
| }; | }; | ||||
| // --------------------------- Table --------------------------- | // --------------------------- Table --------------------------- | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -330,7 +395,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| }, | }, | ||||
| ...diffColumns, | ...diffColumns, | ||||
| { | { | ||||
| title: '创建时间', | |||||
| title: '更新时间', | |||||
| dataIndex: 'update_time', | dataIndex: 'update_time', | ||||
| key: 'update_time', | key: 'update_time', | ||||
| width: '20%', | width: '20%', | ||||
| @@ -0,0 +1,7 @@ | |||||
| import styles from './index.less'; | |||||
| function EmptyLog() { | |||||
| return <div className={styles['empty-log']}>暂无日志</div>; | |||||
| } | |||||
| export default EmptyLog; | |||||
| @@ -5,3 +5,14 @@ | |||||
| height: 100%; | height: 100%; | ||||
| } | } | ||||
| } | } | ||||
| .empty-log { | |||||
| height: 100%; | |||||
| padding: 15px; | |||||
| color: white; | |||||
| font-size: 14px; | |||||
| white-space: pre-line; | |||||
| text-align: center; | |||||
| word-break: break-all; | |||||
| background: #19253b; | |||||
| } | |||||
| @@ -2,6 +2,7 @@ import { ExperimentStatus } from '@/enums'; | |||||
| import { AutoMLInstanceData } from '@/pages/AutoML/types'; | import { AutoMLInstanceData } from '@/pages/AutoML/types'; | ||||
| import LogList from '@/pages/Experiment/components/LogList'; | import LogList from '@/pages/Experiment/components/LogList'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import EmptyLog from './empty'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const NodePrefix = 'auto-ml'; | const NodePrefix = 'auto-ml'; | ||||
| @@ -19,7 +20,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['experiment-log']}> | <div className={styles['experiment-log']}> | ||||
| <div className={styles['experiment-log__log']}> | <div className={styles['experiment-log__log']}> | ||||
| {nodeStatus && ( | |||||
| {nodeStatus ? ( | |||||
| <LogList | <LogList | ||||
| instanceName={instanceInfo.argo_ins_name} | instanceName={instanceInfo.argo_ins_name} | ||||
| instanceNamespace={instanceInfo.argo_ins_ns} | instanceNamespace={instanceInfo.argo_ins_ns} | ||||
| @@ -28,6 +29,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||||
| instanceNodeStartTime={nodeStatus.startedAt} | instanceNodeStartTime={nodeStatus.startedAt} | ||||
| instanceNodeStatus={nodeStatus.phase as ExperimentStatus} | instanceNodeStatus={nodeStatus.phase as ExperimentStatus} | ||||
| ></LogList> | ></LogList> | ||||
| ) : ( | |||||
| <EmptyLog /> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -3,59 +3,61 @@ import RunDuration from '@/components/RunDuration'; | |||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { type NodeStatus } from '@/types'; | import { type NodeStatus } from '@/types'; | ||||
| import { getExperimentInstanceStatus } from '@/utils/experiment'; | |||||
| import { formatDate } from '@/utils/format'; | import { formatDate } from '@/utils/format'; | ||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| type ExperimentRunBasicProps = { | type ExperimentRunBasicProps = { | ||||
| create_time?: string; | |||||
| runStatus?: NodeStatus; | |||||
| workflowStatus?: NodeStatus; | |||||
| instanceStatus?: ExperimentStatus; | instanceStatus?: ExperimentStatus; | ||||
| }; | }; | ||||
| function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) { | |||||
| function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) { | |||||
| const instanceDatas = useMemo(() => { | const instanceDatas = useMemo(() => { | ||||
| if (!runStatus) { | |||||
| return []; | |||||
| } | |||||
| const status = | |||||
| instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase; | |||||
| const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus); | |||||
| const statusInfo = experimentStatusInfo[status]; | const statusInfo = experimentStatusInfo[status]; | ||||
| return [ | return [ | ||||
| { | { | ||||
| label: '启动时间', | label: '启动时间', | ||||
| value: formatDate(create_time), | |||||
| value: formatDate(workflowStatus?.startedAt), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '执行时长', | label: '执行时长', | ||||
| value: <RunDuration createTime={create_time} finishTime={runStatus.finishedAt} />, | |||||
| value: ( | |||||
| <RunDuration | |||||
| createTime={workflowStatus?.startedAt} | |||||
| finishTime={workflowStatus?.finishedAt} | |||||
| /> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| label: '状态', | label: '状态', | ||||
| value: ( | |||||
| value: statusInfo ? ( | |||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={statusInfo?.icon} | |||||
| src={statusInfo.icon} | |||||
| draggable={false} | draggable={false} | ||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| <div | <div | ||||
| style={{ | style={{ | ||||
| color: statusInfo?.color, | |||||
| color: statusInfo.color, | |||||
| fontSize: '15px', | fontSize: '15px', | ||||
| lineHeight: 1.6, | lineHeight: 1.6, | ||||
| }} | }} | ||||
| > | > | ||||
| {statusInfo?.label} | |||||
| {statusInfo.label} | |||||
| </div> | </div> | ||||
| </Flex> | </Flex> | ||||
| ) : ( | |||||
| '--' | |||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [runStatus, create_time, instanceStatus]); | |||||
| }, [workflowStatus, instanceStatus]); | |||||
| return ( | return ( | ||||
| <ConfigInfo | <ConfigInfo | ||||