| @@ -65,3 +65,4 @@ mvnw | |||
| /react-ui/types/tsconfig.tsbuildinfo | |||
| /react-ui/storybook-static | |||
| /react-ui/.storybook/scripts | |||
| /react-ui/dist.zip | |||
| @@ -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", | |||
| "deploy": "npm run build && npm run gh-pages", | |||
| "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-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | |||
| "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | |||
| @@ -35,7 +35,7 @@ | |||
| "serve": "umi-serve", | |||
| "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: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:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | |||
| "storybook": "storybook dev -p 6006", | |||
| @@ -75,6 +75,7 @@ | |||
| "fabric": "^5.3.0", | |||
| "highlight.js": "^11.7.0", | |||
| "lodash": "^4.17.21", | |||
| "motion": "~12.23.12", | |||
| "omit.js": "^2.0.2", | |||
| "pnpm": "^8.9.0", | |||
| "query-string": "^8.1.0", | |||
| @@ -82,6 +83,7 @@ | |||
| "rc-util": "^5.30.0", | |||
| "react": "^18.2.0", | |||
| "react-activation": "^0.12.4", | |||
| "react-countup": "~6.5.3", | |||
| "react-cropper": "^2.3.3", | |||
| "react-dev-inspector": "^1.8.1", | |||
| "react-dom": "^18.2.0", | |||
| @@ -8,7 +8,7 @@ | |||
| * - Please do NOT serve this file on production. | |||
| */ | |||
| const PACKAGE_VERSION = '2.7.0' | |||
| const PACKAGE_VERSION = '2.7.1' | |||
| const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' | |||
| const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') | |||
| const activeClientIds = new Set() | |||
| @@ -24,7 +24,7 @@ import './styles/menu.less'; | |||
| import { isLoginPage, needAuth } from './utils'; | |||
| import { HomeUrl } from './utils/constant'; | |||
| import { closeAllModals } from './utils/modal'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| import { gotoHomePage } from './utils/ui'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| /** | |||
| @@ -43,7 +43,8 @@ export async function getInitialState(): Promise<GlobalInitialState> { | |||
| } as API.CurrentUser; | |||
| } catch (error) { | |||
| console.error('getInitialState', error); | |||
| gotoLoginPage(true); | |||
| // gotoLoginPage(true); | |||
| gotoHomePage(); | |||
| } | |||
| return undefined; | |||
| }; | |||
| @@ -129,7 +130,7 @@ export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | |||
| const token = getAccessToken(); | |||
| // 没有 token,跳转到登录页面 | |||
| if (!token && needAuth(pathname)) { | |||
| gotoLoginPage(false); | |||
| gotoHomePage(); | |||
| return; | |||
| } | |||
| @@ -4,7 +4,7 @@ | |||
| * @Description: 流水线选择代码配置表单 | |||
| */ | |||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { Button } from 'antd'; | |||
| @@ -18,19 +18,9 @@ export { | |||
| type ParameterInputValue, | |||
| } from '../ParameterInput'; | |||
| export type CodeSelectProps = ParameterInputProps; | |||
| // 服务的需要的代码配置数据格式 | |||
| export type ServerCodeData = { | |||
| id: number; | |||
| name: string; | |||
| code_path: string; | |||
| branch: string; | |||
| username: string; | |||
| password: string; | |||
| ssh_private_key: string; | |||
| is_public: boolean; | |||
| }; | |||
| export interface CodeSelectProps extends ParameterInputProps { | |||
| value?: CodeConfigData; | |||
| } | |||
| /** 代码配置选择表单组件 */ | |||
| function CodeSelect({ | |||
| @@ -44,50 +34,18 @@ function CodeSelect({ | |||
| }: CodeSelectProps) { | |||
| // 选择代码配置 | |||
| const selectResource = () => { | |||
| const codeData = value as ServerCodeData; | |||
| const defaultSelected = | |||
| value && typeof value === 'object' | |||
| ? { | |||
| id: codeData.id, | |||
| code_repo_name: codeData.name, | |||
| git_url: codeData.code_path, | |||
| git_branch: codeData.branch, | |||
| git_user_name: codeData.username, | |||
| git_password: codeData.password, | |||
| ssh_key: codeData.ssh_private_key, | |||
| is_public: codeData.is_public, | |||
| } | |||
| : undefined; | |||
| const defaultSelected: CodeConfigData | undefined = | |||
| value && typeof value === 'object' ? value : undefined; | |||
| const { close } = openAntdModal(CodeSelectorModal, { | |||
| defaultSelected: defaultSelected, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| const { | |||
| id, | |||
| code_repo_name, | |||
| git_url, | |||
| git_branch, | |||
| git_user_name, | |||
| git_password, | |||
| ssh_key, | |||
| is_public, | |||
| } = 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, | |||
| is_public, | |||
| }; | |||
| const jsonObjStr = JSON.stringify(jsonObj); | |||
| const { code_repo_name } = res; | |||
| onChange?.({ | |||
| value: jsonObjStr, | |||
| ...res, | |||
| value: code_repo_name, | |||
| showValue: code_repo_name, | |||
| fromSelect: true, | |||
| ...jsonObj, | |||
| }); | |||
| } else { | |||
| onChange?.(undefined); | |||
| @@ -1,3 +1,4 @@ | |||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||
| import { formatEnum } from '@/utils/format'; | |||
| import { Typography, type SelectProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| @@ -16,6 +17,8 @@ type FormInfoProps = { | |||
| options?: SelectProps['options']; | |||
| /** 自定义节点 label、value 的字段 */ | |||
| fieldNames?: SelectProps['fieldNames']; | |||
| /** 全局参数 */ | |||
| globalParams?: PipelineGlobalParam[] | null; | |||
| /** 自定义类名 */ | |||
| className?: string; | |||
| /** 自定义样式 */ | |||
| @@ -32,12 +35,29 @@ function FormInfo({ | |||
| select = false, | |||
| options, | |||
| fieldNames, | |||
| globalParams, | |||
| className, | |||
| style, | |||
| }: FormInfoProps) { | |||
| let showValue = value; | |||
| if (value && typeof value === 'object' && 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) { | |||
| let _options: SelectProps['options'] = options; | |||
| if (fieldNames) { | |||
| @@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons'; | |||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | |||
| import { RuleObject } from 'antd/es/form'; | |||
| import classNames from 'classnames'; | |||
| import { ReactNode } from 'react'; | |||
| import './index.less'; | |||
| // 如果值是对象时的类型 | |||
| @@ -55,6 +56,8 @@ export interface ParameterInputProps { | |||
| disabled?: boolean; | |||
| /** 元素 id */ | |||
| id?: string; | |||
| /** 带标签的 input,设置后置标签 */ | |||
| addonAfter?: ReactNode; | |||
| } | |||
| function ParameterInput({ | |||
| @@ -75,7 +78,7 @@ function ParameterInput({ | |||
| const valueObj = | |||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | |||
| if (valueObj && !valueObj.showValue) { | |||
| valueObj.showValue = valueObj.value; | |||
| valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : ''; | |||
| } | |||
| const isSelect = valueObj?.fromSelect; | |||
| 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 { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||
| 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 { 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 = { | |||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||
| getOptions?: () => Promise<any>; // 获取下拉数据 | |||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | |||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | |||
| 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: { | |||
| getOptions: async () => { | |||
| const res = await getDatasetList({ | |||
| @@ -31,13 +36,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| size: 1000, | |||
| 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: { | |||
| getOptions: async () => { | |||
| @@ -46,13 +54,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| size: 1000, | |||
| 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: { | |||
| getOptions: async () => { | |||
| @@ -60,25 +71,58 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| page: 0, | |||
| 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', | |||
| getValue: (value: ServiceData) => { | |||
| return value.id; | |||
| }, | |||
| getLabel: (value: ServiceData) => { | |||
| return value.service_name; | |||
| }, | |||
| isObjectValue: true, | |||
| }, | |||
| resource: { | |||
| getOptions: async () => { | |||
| // 不需要这个函数 | |||
| return []; | |||
| }, | |||
| fieldNames: resourceFieldNames, | |||
| 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: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | |||
| */ | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import jccResourceState, { getResourceTypes } from '@/state/jcdResource'; | |||
| import systemResourceState, { getSystemResources } from '@/state/systemResource'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useSnapshot } from '@umijs/max'; | |||
| import { Select, type SelectProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import FormInfo from '../FormInfo'; | |||
| import { paramSelectConfig } from './config'; | |||
| import { paramSelectConfig, type ParameterSelectDataType } from './config'; | |||
| export { ParameterSelectTypeList, type ParameterSelectDataType } from './config'; | |||
| export type ParameterSelectObject = { | |||
| value: 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 { | |||
| /** 类型 */ | |||
| @@ -25,8 +31,6 @@ export interface ParameterSelectProps extends SelectProps { | |||
| display?: boolean; | |||
| /** 值,支持对象,对象必须包含 value */ | |||
| value?: string | ParameterSelectObject; | |||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||
| isPipeline?: boolean; | |||
| /** 修改后回调 */ | |||
| onChange?: (value: string | ParameterSelectObject) => void; | |||
| } | |||
| @@ -36,69 +40,127 @@ function ParameterSelect({ | |||
| dataType, | |||
| display = false, | |||
| value, | |||
| isPipeline = false, | |||
| onChange, | |||
| ...rest | |||
| }: ParameterSelectProps) { | |||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||
| const [options, setOptions] = useState<SelectOptions>([]); | |||
| 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 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(() => { | |||
| // 获取下拉数据 | |||
| 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') { | |||
| if (jccResourceSnap.types.length === 0) { | |||
| getResourceTypes(); | |||
| } | |||
| } else if (dataType === 'resource') { | |||
| getSystemResources(); | |||
| } | |||
| }; | |||
| getSelectOptions(); | |||
| }, [propsConfig]); | |||
| }, [getOptions, dataType, getResourceTypes, jccResourceSnap.types]); | |||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||
| const selectOptions = ( | |||
| dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions | |||
| ) as SelectOptions; | |||
| const handleChange = (text: string) => { | |||
| if (typeof value === 'object' && value !== null) { | |||
| onChange?.({ | |||
| ...value, | |||
| value: text, | |||
| }); | |||
| // 数据集、模型、服务,转换成对象 | |||
| if (isObjectValue) { | |||
| // 设置为 null 是因为 ant 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 { | |||
| onChange?.(text); | |||
| const selectValue = text ? text : ''; | |||
| if (typeof value === 'object' && value !== null) { | |||
| onChange?.({ | |||
| ...value, | |||
| value: selectValue, | |||
| }); | |||
| } else { | |||
| onChange?.(selectValue); | |||
| } | |||
| } | |||
| }; | |||
| // 只用于展示,FormInfo 组件带有 Tooltip | |||
| if (display) { | |||
| return ( | |||
| <FormInfo | |||
| select | |||
| value={valueText} | |||
| options={selectOptions} | |||
| fieldNames={propsConfig?.fieldNames} | |||
| ></FormInfo> | |||
| <FormInfo select value={valueText} options={selectOptions} fieldNames={fieldNames}></FormInfo> | |||
| ); | |||
| } | |||
| return ( | |||
| <Select | |||
| {...rest} | |||
| filterOption={propsConfig?.filterOption} | |||
| options={selectOptions} | |||
| fieldNames={propsConfig?.fieldNames} | |||
| optionFilterProp={propsConfig?.optionFilterProp} | |||
| fieldNames={fieldNames} | |||
| optionFilterProp={optionFilterProp} | |||
| filterOption={filterOption} | |||
| value={valueText} | |||
| onChange={handleChange} | |||
| showSearch | |||
| @@ -10,10 +10,10 @@ import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/components/ResourceSelectorModal'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { Button, ConfigProvider } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { pick } from 'lodash'; | |||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||
| import './index.less'; | |||
| @@ -27,6 +27,8 @@ export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse | |||
| interface ResourceSelectProps extends ParameterInputProps { | |||
| /** 类型,数据集、模型、镜像 */ | |||
| type: ResourceSelectorType; | |||
| /** 值 */ | |||
| value?: ResourceSelectorResponse; | |||
| } | |||
| // 获取选择数据集、模型、镜像后面按钮 icon | |||
| @@ -47,68 +49,51 @@ function ResourceSelect({ | |||
| }: ResourceSelectProps) { | |||
| const { componentSize } = ConfigProvider.useConfig(); | |||
| 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 { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: selectedResource ? [selectedResource.id] : [], | |||
| defaultCheckedKeys: selectedResource | |||
| ? [`${selectedResource.id}-${selectedResource.version}`] | |||
| : [], | |||
| defaultActiveTab: selectedResource?.activeTab, | |||
| defaultExpandedKeys: defaultExpandedKeys, | |||
| defaultCheckedKeys: defaultCheckedKeys, | |||
| defaultActiveTab: defaultActiveTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| const { activeTab, ...rest } = res; | |||
| const { url } = rest; | |||
| onChange?.({ | |||
| value: path, | |||
| showValue: path, | |||
| ...rest, | |||
| value: url, | |||
| showValue: url, | |||
| fromSelect: true, | |||
| activeTab, | |||
| id, | |||
| name, | |||
| version, | |||
| path, | |||
| }); | |||
| } 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}`; | |||
| onChange?.({ | |||
| value: jsonObjStr, | |||
| ...rest, | |||
| value: showValue, | |||
| showValue, | |||
| fromSelect: true, | |||
| activeTab, | |||
| ...jsonObj, | |||
| }); | |||
| } | |||
| } else { | |||
| @@ -52,9 +52,9 @@ const convertResourceVersionToTreeData = ( | |||
| ): TreeDataNode[] => { | |||
| return list.map((item: ResourceVersionData) => ({ | |||
| ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | |||
| version: item.name, | |||
| title: item.name, | |||
| key: `${parentId}-${item.name}`, | |||
| title: item.name, | |||
| version: item.name, | |||
| isLeaf: true, | |||
| checkable: true, | |||
| })); | |||
| @@ -66,12 +66,11 @@ const convertMirrorVersionToTreeData = ( | |||
| list: MirrorVersionData[], | |||
| ): TreeDataNode[] => { | |||
| return list.map((item: MirrorVersionData) => ({ | |||
| url: item.url, | |||
| title: item.tag_name, | |||
| ...item, | |||
| key: `${parentId}-${item.id}`, | |||
| title: item.tag_name, | |||
| isLeaf: true, | |||
| checkable: true, | |||
| description: item.description, | |||
| })); | |||
| }; | |||
| @@ -8,6 +8,7 @@ import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { ResourceFileData } from '@/pages/Dataset/config'; | |||
| import { type MirrorVersionData } from '@/pages/Mirror/Info'; | |||
| import { to } from '@/utils/promise'; | |||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | |||
| import { Input, Tabs, Tree } from 'antd'; | |||
| @@ -19,13 +20,13 @@ export { ResourceSelectorType, selectorTypeConfig }; | |||
| // 选择数据集、模型、镜像的返回类型 | |||
| export type ResourceSelectorResponse = { | |||
| 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'> { | |||
| /** 类型,数据集、模型、镜像 */ | |||
| @@ -241,15 +242,22 @@ function ResourceSelectorModal({ | |||
| const name = (treeNode?.title ?? '') as string; | |||
| const identifier = (treeNode?.identifier ?? '') as string; | |||
| const owner = (treeNode?.owner ?? '') as string; | |||
| const res = { | |||
| id, | |||
| name, | |||
| path: versionPath, | |||
| version, | |||
| identifier, | |||
| owner, | |||
| activeTab: activeTab, | |||
| }; | |||
| 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); | |||
| } else { | |||
| onOk?.(undefined); | |||
| @@ -13,12 +13,13 @@ import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import { useModel, useNavigate } from '@umijs/max'; | |||
| import { Avatar, Spin } from 'antd'; | |||
| import type { MenuInfo } from 'rc-menu/lib/interface'; | |||
| import React, { useCallback } from 'react'; | |||
| import React from 'react'; | |||
| import { flushSync } from 'react-dom'; | |||
| import HeaderDropdown from '../HeaderDropdown'; | |||
| export type GlobalHeaderRightProps = { | |||
| menu?: boolean; | |||
| isHome?: boolean; | |||
| }; | |||
| const Name = () => { | |||
| @@ -67,12 +68,13 @@ const AvatarLogo = () => { | |||
| ); | |||
| }; | |||
| const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, isHome = false }) => { | |||
| const navigate = useNavigate(); | |||
| /** | |||
| * 退出登录,并且将当前的 url 保存 | |||
| */ | |||
| const loginOut = async () => { | |||
| // const { origin } = location; | |||
| const [res] = await to(getLabelStudioUrl()); | |||
| if (res && res.data) { | |||
| oauthLogout(`${res.data}/oauth/logout`); | |||
| @@ -86,8 +88,17 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| if (clientInfo) { | |||
| const { logoutUri } = clientInfo; | |||
| location.replace(logoutUri); | |||
| // if (isHome) { | |||
| // setTimeout(() => { | |||
| // location.replace(origin); | |||
| // }, 1); | |||
| // } | |||
| } else { | |||
| gotoLoginPage(true); | |||
| if (isHome) { | |||
| location.reload(); | |||
| } else { | |||
| gotoLoginPage(true); | |||
| } | |||
| } | |||
| }; | |||
| const actionClassName = useEmotionCss(({ token }) => { | |||
| @@ -107,20 +118,17 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| }); | |||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||
| const onMenuClick = useCallback( | |||
| (event: MenuInfo) => { | |||
| const { key } = event; | |||
| if (key === 'logout') { | |||
| flushSync(() => { | |||
| setInitialState((s) => ({ ...s, currentUser: undefined })); | |||
| }); | |||
| loginOut(); | |||
| return; | |||
| } | |||
| navigate(`/account/${key}`); | |||
| }, | |||
| [setInitialState, navigate], | |||
| ); | |||
| const onMenuClick = (event: MenuInfo) => { | |||
| const { key } = event; | |||
| if (key === 'logout') { | |||
| flushSync(() => { | |||
| setInitialState((s) => ({ ...s, currentUser: undefined })); | |||
| }); | |||
| loginOut(); | |||
| return; | |||
| } | |||
| navigate(`/account/${key}`); | |||
| }; | |||
| const loading = ( | |||
| <span className={actionClassName}> | |||
| @@ -163,3 +163,11 @@ export enum AutoMLTrailStatus { | |||
| CANCELLED = 'CANCELLED', // 取消 | |||
| MEMOUT = 'MEMOUT', // 内存溢出 | |||
| } | |||
| // 流水线组件类型 | |||
| export enum ComponentType { | |||
| Ref = 'ref', | |||
| Select = 'select', | |||
| Map = 'map', | |||
| Str = 'str', | |||
| } | |||
| @@ -19,21 +19,27 @@ const clearCache = () => { | |||
| } | |||
| }; | |||
| const doubleTexxt = () => { | |||
| 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 | |||
| ) { | |||
| const times = 2; | |||
| target.innerText = target.innerText.repeat(times); | |||
| } | |||
| }); | |||
| 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, | |||
| ); | |||
| } | |||
| }; | |||
| @@ -108,4 +114,4 @@ if (pwa) { | |||
| clearCache(); | |||
| } | |||
| doubleTexxt(); | |||
| doubleText(); | |||
| @@ -4,66 +4,90 @@ | |||
| * @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( | |||
| (id?: string | number) => { | |||
| if (!id) { | |||
| 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; | |||
| }; | |||
| @@ -220,35 +220,32 @@ | |||
| height: 100%; | |||
| .ant-pro-grid-content-children { | |||
| height: 100%; | |||
| .ant-pro-layout-watermark-wrapper { | |||
| .ant-pro-page-container-children-container { | |||
| height: 100%; | |||
| .ant-pro-page-container-children-container { | |||
| padding: 0; | |||
| .ant-pro-table { | |||
| display: flex; | |||
| flex-direction: column; | |||
| height: 100%; | |||
| padding: 0; | |||
| .ant-pro-table { | |||
| display: flex; | |||
| flex-direction: column; | |||
| height: 100%; | |||
| .ant-pro-card.ant-pro-table-search { | |||
| flex: none; | |||
| height: auto; | |||
| } | |||
| .ant-pro-card { | |||
| flex: 1; | |||
| min-height: 0; | |||
| .ant-pro-card-body { | |||
| height: 100%; | |||
| .ant-table-wrapper { | |||
| height: calc(100% - 64px); | |||
| .ant-spin-nested-loading { | |||
| .ant-pro-card.ant-pro-table-search { | |||
| flex: none; | |||
| height: auto; | |||
| } | |||
| .ant-pro-card { | |||
| flex: 1; | |||
| min-height: 0; | |||
| .ant-pro-card-body { | |||
| height: 100%; | |||
| .ant-table-wrapper { | |||
| height: calc(100% - 64px - 64px); | |||
| .ant-spin-nested-loading { | |||
| height: 100%; | |||
| .ant-spin-container { | |||
| height: 100%; | |||
| .ant-spin-container { | |||
| .ant-table-fixed-header { | |||
| height: 100%; | |||
| .ant-table-fixed-header { | |||
| .ant-table-container { | |||
| height: 100%; | |||
| .ant-table-container { | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| classifierAlgorithms, | |||
| FrameworkType, | |||
| @@ -39,7 +39,7 @@ function BasicInfo({ | |||
| instanceStatus, | |||
| isInstance = false, | |||
| }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -6,7 +6,7 @@ import { | |||
| autoMLEnsembleClassOptions, | |||
| autoMLTaskTypeOptions, | |||
| } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| classificationAlgorithms, | |||
| featureAlgorithms, | |||
| @@ -76,7 +76,7 @@ function AutoMLBasic({ | |||
| instanceStatus, | |||
| isInstance = false, | |||
| }: AutoMLBasicProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -42,37 +42,44 @@ export const regressorAlgorithms = [ | |||
| // 特征预处理算法 | |||
| export const featureAlgorithms = [ | |||
| { label: 'densifier (数据增稠)', value: 'densifier' }, | |||
| { label: 'densifier (特征变换-数据增稠)', value: 'densifier' }, | |||
| { | |||
| label: 'extra_trees_preproc_for_classification (分类任务极端随机树)', | |||
| label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)', | |||
| value: 'extra_trees_preproc_for_classification', | |||
| }, | |||
| { | |||
| label: 'extra_trees_preproc_for_regression (回归任务极端随机树)', | |||
| 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: '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: '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 (基于百分位的分类特征选择)', | |||
| label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)', | |||
| value: 'select_percentile_classification', | |||
| }, | |||
| { | |||
| label: 'select_percentile_regression (基于百分位的回归特征选择)', | |||
| label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)', | |||
| value: 'select_percentile_regression', | |||
| }, | |||
| { | |||
| label: 'select_rates_classification (基于比率的分类特征选择)', | |||
| label: 'select_rates_classification (特征选择-基于比率的分类特征选择)', | |||
| value: 'select_rates_classification', | |||
| }, | |||
| { label: 'select_rates_regression (基于比率的回归特征选择)', value: 'select_rates_regression' }, | |||
| { label: 'truncatedSVD (截断奇异值分解)', value: 'truncatedSVD' }, | |||
| { | |||
| label: 'select_rates_regression (特征选择-基于比率的回归特征选择)', | |||
| value: 'select_rates_regression', | |||
| }, | |||
| { label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' }, | |||
| ]; | |||
| @@ -169,16 +169,16 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| const handleMessage = (e: MessageEvent) => { | |||
| const { type, payload } = e.data; | |||
| if (type === ExperimentCompleted) { | |||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||
| const { experimentId, experimentInsId, status /*finishTime*/ } = payload; | |||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | |||
| console.log( | |||
| '实验实例状态变化', | |||
| currentIns?.status, | |||
| status, | |||
| experimentId, | |||
| experimentInsId, | |||
| finishTime, | |||
| ); | |||
| // console.log( | |||
| // '实验实例状态变化', | |||
| // currentIns?.status, | |||
| // status, | |||
| // experimentId, | |||
| // experimentInsId, | |||
| // finishTime, | |||
| // ); | |||
| if ( | |||
| !currentIns || | |||
| @@ -0,0 +1,121 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { DataSource, ResourceData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Form, Input, message, type ModalProps } from 'antd'; | |||
| interface EditVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||
| resourceType: ResourceType; | |||
| resourceVersion: ResourceData; | |||
| onOk: () => void; | |||
| } | |||
| function EditVersionModal({ resourceType, resourceVersion, onOk, ...rest }: EditVersionModalProps) { | |||
| const config = resourceConfig[resourceType]; | |||
| const { name: resoureName, version, version_desc } = resourceVersion; | |||
| // 修改请求 | |||
| const editDatasetVersion = async (params: any) => { | |||
| const request = config.editVersion; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| message.success('编辑成功'); | |||
| onOk?.(); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const onFinish = (formData: any) => { | |||
| const params = { | |||
| ...resourceVersion, | |||
| ...formData, | |||
| [config.sourceParamKey]: DataSource.Create, | |||
| }; | |||
| editDatasetVersion(params); | |||
| }; | |||
| const name = config.name; | |||
| return ( | |||
| <KFModal | |||
| {...rest} | |||
| title="编辑版本" | |||
| image={require('@/assets/img/create-experiment.png')} | |||
| width={825} | |||
| okButtonProps={{ | |||
| htmlType: 'submit', | |||
| form: 'form', | |||
| }} | |||
| > | |||
| <Form | |||
| name="form" | |||
| layout="vertical" | |||
| initialValues={{ | |||
| name: resoureName, | |||
| version: version, | |||
| version_desc: version_desc, | |||
| }} | |||
| onFinish={onFinish} | |||
| autoComplete="off" | |||
| > | |||
| <Form.Item | |||
| label={`${name}名称`} | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: `请输入${name}名称`, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input disabled placeholder={`请输入${name}名称`} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label={`${name}版本`} | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: `请输入${name}版本`, | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`, | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| if (value === 'master') { | |||
| return Promise.reject(`${name}版本不能为 master`); | |||
| } else if (value === 'origin') { | |||
| return Promise.reject(`${name}版本不能为 origin`); | |||
| } | |||
| return Promise.resolve(); | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入版本描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入版本描述" | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| maxLength={200} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Form> | |||
| </KFModal> | |||
| ); | |||
| } | |||
| export default EditVersionModal; | |||
| @@ -24,6 +24,7 @@ import { App, Button, Flex, Select, Tabs, Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import AddVersionModal from '../AddVersionModal'; | |||
| import EditVersionModal from '../EditVersionModal'; | |||
| import ResourceIntro from '../ResourceIntro'; | |||
| import ResourceVersion from '../ResourceVersion'; | |||
| import VersionCompareModal from '../VersionCompareModal'; | |||
| @@ -137,6 +138,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| }); | |||
| }; | |||
| // 版本编辑 | |||
| const showEditVersionModal = () => { | |||
| const { close } = openAntdModal(EditVersionModal, { | |||
| resourceType: resourceType, | |||
| resourceVersion: info, | |||
| onOk: () => { | |||
| getResourceDetail(); | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| // 选择版本 | |||
| const showVersionSelector = () => { | |||
| const { close } = openAntdModal(VersionSelectorModal, { | |||
| @@ -9,6 +9,8 @@ import { | |||
| deleteDatasetVersion, | |||
| deleteModel, | |||
| deleteModelVersion, | |||
| editDatasetVersion, | |||
| editModelVersion, | |||
| getDatasetInfo, | |||
| getDatasetList, | |||
| getDatasetNextVersionReq, | |||
| @@ -38,6 +40,7 @@ type ResourceTypeInfo = { | |||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | |||
| deleteRecord: (params: any) => Promise<any>; // 删除 | |||
| addVersion: (params: any) => Promise<any>; // 新增版本 | |||
| editVersion: (params: any) => Promise<any>; // 编辑版本 | |||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | |||
| getInfo: (params: any) => Promise<any>; // 获取详情 | |||
| compareVersion: (params: any) => Promise<any>; // 版本对比 | |||
| @@ -68,6 +71,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| getVersions: getDatasetVersionList, | |||
| deleteRecord: deleteDataset, | |||
| addVersion: addDatasetVersion, | |||
| editVersion: editDatasetVersion, | |||
| deleteVersion: deleteDatasetVersion, | |||
| getInfo: getDatasetInfo, | |||
| compareVersion: compareDatasetVersion, | |||
| @@ -107,6 +111,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| getVersions: getModelVersionList, | |||
| deleteRecord: deleteModel, | |||
| addVersion: addModelVersion, | |||
| editVersion: editModelVersion, | |||
| deleteVersion: deleteModelVersion, | |||
| getInfo: getModelInfo, | |||
| compareVersion: compareModelVersion, | |||
| @@ -4,10 +4,11 @@ | |||
| * @Description: 开发环境列表 | |||
| */ | |||
| import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { DevEditorStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||
| import { | |||
| deleteEditorReq, | |||
| @@ -17,12 +18,7 @@ import { | |||
| } from '@/services/developmentEnvironment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { | |||
| formatCodeConfig, | |||
| formatDataset, | |||
| formatModel, | |||
| type SelectedCodeConfig, | |||
| } from '@/utils/format'; | |||
| import { formatCodeConfig, formatDataset, formatModel } from '@/utils/format'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| @@ -55,7 +51,7 @@ export type EditorData = { | |||
| dataset?: string | DatasetData; | |||
| model?: string | ModelData; | |||
| image?: string; | |||
| code_config?: string | SelectedCodeConfig; | |||
| code_config?: string | CodeConfigData; | |||
| }; | |||
| function EditorList() { | |||
| @@ -70,7 +66,7 @@ function EditorList() { | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| // 获取编辑器列表 | |||
| const getEditorList = useCallback(async () => { | |||
| @@ -211,7 +207,7 @@ function EditorList() { | |||
| const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { | |||
| e.stopPropagation(); | |||
| const codeConfig = record.code_config as SelectedCodeConfig; | |||
| const codeConfig = record.code_config as CodeConfigData; | |||
| const url = formatCodeConfig(codeConfig)?.url; | |||
| if (url) { | |||
| window.open(url, '_blank'); | |||
| @@ -95,16 +95,13 @@ function ExperimentText() { | |||
| return; | |||
| } | |||
| const workflow = parseJsonText(dag); | |||
| const workflow = dag; | |||
| const experimentStatusObjs = parseJsonText(nodes_status); | |||
| if (!workflow || !workflow.nodes) { | |||
| return; | |||
| } | |||
| workflow.nodes.forEach((item) => { | |||
| item.in_parameters = parseJsonText(item.in_parameters); | |||
| item.out_parameters = parseJsonText(item.out_parameters); | |||
| item.control_strategy = parseJsonText(item.control_strategy); | |||
| item.imgName = item.img.slice(0, item.img.length - 4); | |||
| }); | |||
| workflowRef.current = workflow; | |||
| @@ -140,8 +137,11 @@ function ExperimentText() { | |||
| } else if (status === ExperimentStatus.Running) { | |||
| // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 | |||
| const node = | |||
| workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ?? | |||
| workflow.nodes[0]; | |||
| workflow.nodes.find( | |||
| (item) => | |||
| item.experimentStatus === ExperimentStatus.Running || | |||
| item.experimentStatus === ExperimentStatus.Pending, | |||
| ) ?? workflow.nodes[0]; | |||
| if (node) { | |||
| setExperimentNodeData(node); | |||
| openPropsDrawer(); | |||
| @@ -567,12 +567,13 @@ function ExperimentText() { | |||
| instanceNodeStatus={experimentNodeData.experimentStatus} | |||
| instanceNodeStartTime={experimentNodeData.experimentStartTime} | |||
| instanceNodeEndTime={experimentNodeData.experimentEndTime} | |||
| globalParams={experimentIns?.global_param} | |||
| ></ExperimentDrawer> | |||
| ) : null} | |||
| <ParamsModal | |||
| open={paramsModalOpen} | |||
| onCancel={closeParamsModal} | |||
| globalParam={experimentIns?.global_param} | |||
| globalParams={experimentIns?.global_param} | |||
| ></ParamsModal> | |||
| </div> | |||
| ); | |||
| @@ -6,4 +6,10 @@ | |||
| border: 1px solid #e6e6e6; | |||
| border-radius: 6px; | |||
| } | |||
| :global { | |||
| .ant-form-item-row { | |||
| align-items: center; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import createExperimentIcon from '@/assets/img/create-experiment.png'; | |||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { type PipelineGlobalParam } from '@/types'; | |||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | |||
| import { useState } from 'react'; | |||
| @@ -32,7 +32,7 @@ interface Workflow { | |||
| // 根据参数设置输入组件 | |||
| export const getParamComponent = (paramType: number): JSX.Element => { | |||
| // 防止后台返回不是 number 类型 | |||
| if (Number(paramType) === 3) { | |||
| if (Number(paramType) === PipelineGlobalParamType.Boolean) { | |||
| return ( | |||
| <Radio.Group> | |||
| <Radio value={1}>是</Radio> | |||
| @@ -50,7 +50,7 @@ export const getParamComponent = (paramType: number): JSX.Element => { | |||
| export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { | |||
| const rules = []; | |||
| // 防止后台返回不是 number 类型 | |||
| if (Number(paramType) === 2) { | |||
| if (Number(paramType) === PipelineGlobalParamType.Number) { | |||
| rules.push({ | |||
| pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | |||
| message: '整型必须是数字', | |||
| @@ -64,10 +64,10 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||
| // 根据参数设置 label | |||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | |||
| const paramTypes: Readonly<Record<number, string>> = { | |||
| 1: '字符串', | |||
| 2: '整型', | |||
| 3: '布尔类型', | |||
| const paramTypes: Readonly<Record<PipelineGlobalParamType, string>> = { | |||
| [PipelineGlobalParamType.String]: '字符串', | |||
| [PipelineGlobalParamType.Number]: '整型', | |||
| [PipelineGlobalParamType.Boolean]: '布尔类型', | |||
| }; | |||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | |||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | |||
| @@ -1,7 +1,7 @@ | |||
| import RunDuration from '@/components/RunDuration'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { PipelineNodeModelSerialize, type PipelineGlobalParam } from '@/types'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
| import { Drawer, Tabs, Typography } from 'antd'; | |||
| @@ -25,6 +25,7 @@ type ExperimentDrawerProps = { | |||
| instanceNodeStatus?: ExperimentStatus; // 实例节点状态 | |||
| instanceNodeStartTime?: string; // 开始时间 | |||
| instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 | |||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||
| }; | |||
| const ExperimentDrawer = ({ | |||
| @@ -41,6 +42,7 @@ const ExperimentDrawer = ({ | |||
| instanceNodeStatus, | |||
| instanceNodeStartTime, | |||
| instanceNodeEndTime, | |||
| globalParams, | |||
| }: ExperimentDrawerProps) => { | |||
| // 如果性能有问题,可以进一步拆解 | |||
| const items = useMemo( | |||
| @@ -66,7 +68,7 @@ const ExperimentDrawer = ({ | |||
| key: '2', | |||
| label: '配置参数', | |||
| icon: <DatabaseOutlined />, | |||
| children: <ExperimentParameter nodeData={instanceNodeData} />, | |||
| children: <ExperimentParameter nodeData={instanceNodeData} globalParams={globalParams} />, | |||
| }, | |||
| { | |||
| key: '3', | |||
| @@ -94,6 +96,7 @@ const ExperimentDrawer = ({ | |||
| experimentName, | |||
| experimentId, | |||
| pipelineId, | |||
| globalParams, | |||
| ], | |||
| ); | |||
| @@ -15,4 +15,24 @@ | |||
| font-size: @font-size; | |||
| background: #f8fbff; | |||
| } | |||
| &__form-list { | |||
| :global { | |||
| .ant-row { | |||
| padding: 0 !important; | |||
| } | |||
| } | |||
| &:last-child { | |||
| :global { | |||
| .ant-form-item { | |||
| margin-bottom: 0 !important; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &__list-empty { | |||
| color: @text-color-tertiary; | |||
| } | |||
| } | |||
| @@ -1,25 +1,102 @@ | |||
| import FormInfo from '@/components/FormInfo'; | |||
| import ParameterSelect from '@/components/ParameterSelect'; | |||
| import ParameterSelect, { | |||
| type ParameterSelectDataType, | |||
| ParameterSelectTypeList, | |||
| } from '@/components/ParameterSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { PipelineNodeModelSerialize } from '@/types'; | |||
| import { Form } from 'antd'; | |||
| import { ComponentType } from '@/enums'; | |||
| import { setCurrentType } from '@/state/jcdResource'; | |||
| import type { | |||
| PipelineGlobalParam, | |||
| PipelineNodeModelParameter, | |||
| PipelineNodeModelSerialize, | |||
| } from '@/types'; | |||
| import { Flex, Form } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ExperimentParameterProps = { | |||
| nodeData: PipelineNodeModelSerialize; | |||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||
| }; | |||
| function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProps) { | |||
| // 云际组件,设置 store 当前资源类型 | |||
| if (nodeData.id.startsWith('remote-task')) { | |||
| const resourceType = nodeData.in_parameters['--resource_type'].value; | |||
| setCurrentType(resourceType); | |||
| } | |||
| // 表单组件 | |||
| const getFormComponent = ( | |||
| item: { key: string; value: PipelineNodeModelParameter }, | |||
| parentName: string, | |||
| ) => { | |||
| return ( | |||
| <Form.Item | |||
| key={item.key} | |||
| name={[parentName, item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| rules={[{ required: item.value.require ? true : false }]} | |||
| > | |||
| {item.value.type === ComponentType.Map && ( | |||
| <Form.List name={[parentName, item.key, 'value']}> | |||
| {(fields) => ( | |||
| <> | |||
| {fields.length > 0 ? ( | |||
| fields.map(({ key, name, ...restField }) => ( | |||
| <Flex | |||
| key={key} | |||
| gap="0 8px" | |||
| style={{ width: '100%' }} | |||
| className={styles['experiment-parameter__form-list']} | |||
| > | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'name']} | |||
| style={{ flex: 1, minWidth: 0 }} | |||
| > | |||
| <FormInfo /> | |||
| </Form.Item> | |||
| <span style={{ lineHeight: '32px' }}>=</span> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| style={{ flex: 1, minWidth: 0 }} | |||
| > | |||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||
| </Form.Item> | |||
| </Flex> | |||
| )) | |||
| ) : ( | |||
| <div className={styles['experiment-parameter__list-empty']}>无</div> | |||
| )} | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| )} | |||
| {item.value.type === ComponentType.Select && | |||
| (ParameterSelectTypeList.includes(item.value.item_type as ParameterSelectDataType) ? ( | |||
| <ParameterSelect dataType={item.value.item_type as ParameterSelectDataType} display /> | |||
| ) : null)} | |||
| {item.value.type !== ComponentType.Map && item.value.type !== ComponentType.Select && ( | |||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||
| )} | |||
| </Form.Item> | |||
| ); | |||
| }; | |||
| // 基本参数 | |||
| const basicParametersList = Object.entries(nodeData.task_info ?? {}) | |||
| .map(([key, value]) => ({ | |||
| key, | |||
| value, | |||
| })) | |||
| .filter((v) => v.value.visible === true); | |||
| // 控制策略 | |||
| // const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||
| // ([key, value]) => ({ key, value }), | |||
| // ); | |||
| const nodeId = nodeData.id; | |||
| const hasTaskInfo = | |||
| nodeId && | |||
| !nodeId.startsWith('git-clone') && | |||
| !nodeId.startsWith('dataset-export') && | |||
| !nodeId.startsWith('model-export'); | |||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}) | |||
| .map(([key, value]) => ({ key, value })) | |||
| .filter((v) => v.value.visible === true); | |||
| // 输入参数 | |||
| const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | |||
| @@ -80,96 +157,56 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||
| > | |||
| <FormInfo /> | |||
| </Form.Item> | |||
| {hasTaskInfo && ( | |||
| {basicParametersList.length + controlStrategyList.length > 0 && ( | |||
| <div className={styles['experiment-parameter__title']}> | |||
| <SubAreaTitle | |||
| image={require('@/assets/img/duty-message.png')} | |||
| title="任务信息" | |||
| ></SubAreaTitle> | |||
| </div> | |||
| )} | |||
| {/* 基本参数 */} | |||
| {basicParametersList.map((item) => getFormComponent(item, 'task_info'))} | |||
| {/* 控制参数 */} | |||
| {controlStrategyList.map((item) => getFormComponent(item, 'control_strategy'))} | |||
| {/* 输入参数 */} | |||
| {inParametersList.length > 0 && ( | |||
| <> | |||
| <div className={styles['experiment-parameter__title']}> | |||
| <SubAreaTitle | |||
| image={require('@/assets/img/duty-message.png')} | |||
| title="任务信息" | |||
| title="输入参数" | |||
| ></SubAreaTitle> | |||
| </div> | |||
| <Form.Item | |||
| label="镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <FormInfo /> | |||
| </Form.Item> | |||
| <Form.Item label="工作目录" name="working_directory"> | |||
| <FormInfo /> | |||
| </Form.Item> | |||
| {inParametersList.map((item) => getFormComponent(item, 'in_parameters'))} | |||
| </> | |||
| )} | |||
| <Form.Item label="启动命令" name="command"> | |||
| <FormInfo textArea /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resources_standard" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" display /> | |||
| </Form.Item> | |||
| {/* <Form.Item label="挂载路径" name="mount_path"> | |||
| <FormInfo /> | |||
| </Form.Item> */} | |||
| <Form.Item label="环境变量" name="env_variables"> | |||
| <FormInfo textArea /> | |||
| </Form.Item> | |||
| {/* {controlStrategyList.map((item) => ( | |||
| <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> | |||
| <FormInfo valuePropName="showValue" /> | |||
| </Form.Item> | |||
| ))} */} | |||
| {/* 输出参数 */} | |||
| {outParametersList.length > 0 && ( | |||
| <> | |||
| <div className={styles['experiment-parameter__title']}> | |||
| <SubAreaTitle | |||
| image={require('@/assets/img/duty-message.png')} | |||
| title="输出参数" | |||
| ></SubAreaTitle> | |||
| </div> | |||
| {outParametersList.map((item) => ( | |||
| <Form.Item | |||
| key={item.key} | |||
| name={['out_parameters', item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| rules={[{ required: item.value.require ? true : false }]} | |||
| > | |||
| <FormInfo valuePropName="showValue" /> | |||
| </Form.Item> | |||
| ))} | |||
| </> | |||
| )} | |||
| <div className={styles['experiment-parameter__title']}> | |||
| <SubAreaTitle | |||
| image={require('@/assets/img/duty-message.png')} | |||
| title="输入参数" | |||
| ></SubAreaTitle> | |||
| </div> | |||
| {inParametersList.map((item) => ( | |||
| <Form.Item | |||
| key={item.key} | |||
| name={['in_parameters', item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| rules={[{ required: item.value.require ? true : false }]} | |||
| > | |||
| {item.value.type === 'select' ? ( | |||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||
| <ParameterSelect dataType={item.value.item_type as any} display /> | |||
| ) : null | |||
| ) : ( | |||
| <FormInfo valuePropName="showValue" /> | |||
| )} | |||
| </Form.Item> | |||
| ))} | |||
| <div className={styles['experiment-parameter__title']}> | |||
| <SubAreaTitle | |||
| image={require('@/assets/img/duty-message.png')} | |||
| title="输出参数" | |||
| ></SubAreaTitle> | |||
| </div> | |||
| {outParametersList.map((item) => ( | |||
| <Form.Item | |||
| key={item.key} | |||
| name={['out_parameters', item.key]} | |||
| label={item.value.label + '(' + item.key + ')'} | |||
| rules={[{ required: item.value.require ? true : false }]} | |||
| > | |||
| <FormInfo valuePropName="showValue" /> | |||
| </Form.Item> | |||
| ))} | |||
| </Form> | |||
| ); | |||
| } | |||
| @@ -4,6 +4,12 @@ | |||
| overflow-y: auto; | |||
| border: 1px solid #e6e6e6; | |||
| border-radius: 8px; | |||
| :global { | |||
| .ant-form-item-row { | |||
| align-items: center; | |||
| } | |||
| } | |||
| } | |||
| .params-empty { | |||
| :global { | |||
| @@ -14,10 +14,10 @@ import styles from './index.less'; | |||
| type ParamsModalProps = { | |||
| open: boolean; | |||
| onCancel: () => void; | |||
| globalParam?: PipelineGlobalParam[] | null; | |||
| globalParams?: PipelineGlobalParam[] | null; | |||
| }; | |||
| function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||
| function ParamsModal({ open, onCancel, globalParams = [] }: ParamsModalProps) { | |||
| return ( | |||
| <KFModal | |||
| title="执行参数" | |||
| @@ -28,13 +28,13 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||
| cancelButtonProps={{ style: { display: 'none' } }} | |||
| width={825} | |||
| > | |||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||
| {Array.isArray(globalParams) && globalParams.length > 0 ? ( | |||
| <div className={styles['params-container']}> | |||
| <Form | |||
| name="view_params_form" | |||
| labelCol={{ span: 6 }} | |||
| wrapperCol={{ span: 18 }} | |||
| initialValues={{ global_param: globalParam }} | |||
| initialValues={{ global_param: globalParams }} | |||
| labelAlign="left" | |||
| disabled | |||
| > | |||
| @@ -45,9 +45,9 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||
| {...restField} | |||
| key={key} | |||
| name={[name, 'param_value']} | |||
| label={getParamLabel(globalParam[name])} | |||
| label={getParamLabel(globalParams[name])} | |||
| > | |||
| {getParamComponent(globalParam[name]['param_type'])} | |||
| {getParamComponent(globalParams[name]['param_type'])} | |||
| </Form.Item> | |||
| )) | |||
| } | |||
| @@ -226,14 +226,14 @@ function Experiment() { | |||
| if (type === ExperimentCompleted) { | |||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | |||
| console.log( | |||
| '实验实例状态变化', | |||
| currentIns?.status, | |||
| status, | |||
| experimentId, | |||
| experimentInsId, | |||
| finishTime, | |||
| ); | |||
| // console.log( | |||
| // '实验实例状态变化', | |||
| // currentIns?.status, | |||
| // status, | |||
| // experimentId, | |||
| // experimentInsId, | |||
| // finishTime, | |||
| // ); | |||
| if ( | |||
| !currentIns || | |||
| @@ -5,7 +5,7 @@ | |||
| width: 100%; | |||
| &__title { | |||
| color: #020814; | |||
| color: @home-text-color; | |||
| font-weight: 500; | |||
| font-size: 2.25rem; | |||
| text-align: center; | |||
| @@ -1,68 +1,151 @@ | |||
| .dataset { | |||
| .code { | |||
| position: relative; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 0 16.25rem 3.125rem; | |||
| .backgroundFullImage(url(@/assets/img/home/model-bg.png)); | |||
| padding: 4.375rem @home-padding-x 9.375rem; | |||
| .backgroundFullImage(url(@/assets/img/home/code-bg.png)); | |||
| &__item { | |||
| position: relative; | |||
| width: 27.875rem; | |||
| padding: 1.875rem 1.25rem; | |||
| color: #8284a4; | |||
| width: calc((100% - 2 * 1.25rem) / 3); | |||
| padding: 1.625rem; | |||
| color: @home-text-color-tertiary; | |||
| font-size: 0.8125rem; | |||
| background-color: transparent; | |||
| border-radius: 11px; | |||
| cursor: pointer; | |||
| .backgroundFullImage(url(@/assets/img/home/dataset-item-bg.png)); | |||
| &:hover { | |||
| .backgroundFullImage(url(@/assets/img/home/dataset-item-bg-hover.png)); | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 2.75rem; | |||
| height: 2.75rem; | |||
| margin-right: 1rem; | |||
| } | |||
| &__title { | |||
| flex: 1; | |||
| color: #020814; | |||
| color: @home-text-color; | |||
| font-size: 1rem; | |||
| .singleLine(); | |||
| } | |||
| &:hover &__title { | |||
| color: @primary-color; | |||
| } | |||
| &__arrow { | |||
| display: none; | |||
| flex: none; | |||
| width: 1.5625rem; | |||
| margin-left: 0.75rem; | |||
| width: 0.875rem; | |||
| margin-left: 0.5rem; | |||
| } | |||
| &:hover &__arrow { | |||
| display: block; | |||
| } | |||
| // &__type { | |||
| // width: 3.25rem; | |||
| // height: 1.375rem; | |||
| // color: white; | |||
| // font-size: 0.875rem; | |||
| // line-height: 1.375rem; | |||
| // text-align: center; | |||
| // border-radius: 0.75rem; | |||
| // &--public { | |||
| // background: linear-gradient(120.77deg, @primary-color 0%, #79ffa7 100%); | |||
| // } | |||
| // &--private { | |||
| // background: linear-gradient(127.67deg, #ffb716 0%, #e079ff 100%); | |||
| // } | |||
| // } | |||
| // &:hover &__type--public { | |||
| // color: @primary-color; | |||
| // background: linear-gradient(120.77deg, #ffffff 0%, #d0ffe0 100%); | |||
| // } | |||
| // &:hover &__type--private { | |||
| // color: @primary-color; | |||
| // background: linear-gradient(127.67deg, #e079ff 0%, #ffb716 100%); | |||
| // } | |||
| &__desc { | |||
| height: 2.75rem; | |||
| margin-bottom: 0.875rem; | |||
| color: @home-text-color-secondary; | |||
| font-size: 0.875rem; | |||
| line-height: 1.375rem; | |||
| .multiLine(2); | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 1.5rem; | |||
| height: 1.5rem; | |||
| margin-right: 0.875rem; | |||
| } | |||
| &__user { | |||
| .singleLine(); | |||
| } | |||
| &__user-divider { | |||
| flex: none; | |||
| height: 0.625rem; | |||
| margin-right: 0.75rem; | |||
| margin-left: 0.75rem; | |||
| background-color: #8284a4; | |||
| background-color: @home-divider-color; | |||
| } | |||
| &__timestamp-icon { | |||
| flex: none; | |||
| width: 1rem; | |||
| height: 1rem; | |||
| margin-right: 0.375rem; | |||
| } | |||
| &__timestamp { | |||
| flex: none; | |||
| } | |||
| } | |||
| &__item--first { | |||
| background-color: transparent; | |||
| border-radius: 1rem; | |||
| box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); | |||
| .backgroundFullImage(url(@/assets/img/home/code-item-bg.png)); | |||
| &:hover { | |||
| .backgroundFullImage(url(@/assets/img/home/code-item-bg-hover.png)); | |||
| color: white; | |||
| box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); | |||
| } | |||
| } | |||
| &__item--first &__item__arrow { | |||
| display: none !important; | |||
| } | |||
| &__second-line { | |||
| position: relative; | |||
| margin-top: 1.625rem; | |||
| background-color: white; | |||
| border-radius: 1rem; | |||
| box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); | |||
| &__divider { | |||
| position: absolute; | |||
| top: 1.625rem; | |||
| bottom: 1.625rem; | |||
| left: calc((100% - 2 * 1.25rem) / 3 + 0.625rem); | |||
| border-left: 1px dashed rgba(146, 164, 201, 0.56); | |||
| &&--second { | |||
| right: calc((100% - 2 * 1.25rem) / 3 + 0.625rem); | |||
| left: auto; | |||
| } | |||
| } | |||
| } | |||
| &__item--first:hover &__item__title { | |||
| color: white; | |||
| } | |||
| &__item--first:hover &__item__desc { | |||
| color: white; | |||
| } | |||
| &__item--second:hover &__item__title { | |||
| color: @primary-color; | |||
| } | |||
| } | |||
| @@ -3,14 +3,14 @@ import { getPublicCodeConfigsReq } from '@/services/home'; | |||
| import { getGitUrl } from '@/utils'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { gotoPageIfLogin } from '@/utils/ui'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| function CodeConfig() { | |||
| const navigate = useNavigate(); | |||
| const [codeCofigs, setCodeConfigs] = useState<CodeConfigData[]>([]); | |||
| useEffect(() => { | |||
| @@ -18,57 +18,74 @@ function CodeConfig() { | |||
| const [res] = await to(getPublicCodeConfigsReq()); | |||
| if (res && res.data) { | |||
| const { content = [] } = res.data; | |||
| setCodeConfigs(content.slice(0, 6)); | |||
| setCodeConfigs(content.slice(0, 9)); | |||
| } | |||
| }; | |||
| getPublicCodeConfigs(); | |||
| }, []); | |||
| const createItem = (item: CodeConfigData, className: string) => { | |||
| return ( | |||
| <div | |||
| className={classNames(styles['code__item'], className)} | |||
| key={item.id} | |||
| onClick={() => { | |||
| const url = getGitUrl(item.git_url, item.git_branch); | |||
| window.open(url, '_blank'); | |||
| }} | |||
| > | |||
| <Flex align="center" style={{ marginBottom: '1rem' }}> | |||
| <div className={styles['code__item__title']}>{item.code_repo_name}</div> | |||
| <img | |||
| className={styles['code__item__arrow']} | |||
| src={require('@/assets/img/home/code-arrow.png')} | |||
| ></img> | |||
| </Flex> | |||
| <div className={styles['code__item__desc']}>{item.git_url}</div> | |||
| <Flex align="center"> | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar.png')} | |||
| className={styles['code__item__user-avatar']} | |||
| ></img> | |||
| <div className={styles['code__item__user']}>{item.create_by}</div> | |||
| <Divider type="vertical" className={styles['code__item__user-divider']}></Divider> | |||
| <img | |||
| src={require('@/assets/img/home/timestamp.png')} | |||
| className={styles['code__item__timestamp-icon']} | |||
| ></img> | |||
| <div className={styles['code__item__timestamp']}>{formatDate(item.create_time)}</div> | |||
| <div></div> | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| }; | |||
| return ( | |||
| <div className={styles.dataset}> | |||
| <div className={styles.code}> | |||
| <BlockTitle | |||
| title="热门代码配置" | |||
| style={{ marginBottom: '6.75rem' }} | |||
| onClick={() => navigate('/dataset/codeConfig')} | |||
| style={{ marginBottom: '5.25rem' }} | |||
| onClick={() => gotoPageIfLogin('/dataset/codeConfig')} | |||
| ></BlockTitle> | |||
| <Flex align="center" justify="space-between" style={{ width: '100%' }} wrap gap="1.875rem 0"> | |||
| {codeCofigs.map((item) => { | |||
| return ( | |||
| <Flex | |||
| className={styles['dataset__item']} | |||
| key={item.id} | |||
| onClick={() => { | |||
| const url = getGitUrl(item.git_url, item.git_branch); | |||
| window.open(url, '_blank'); | |||
| }} | |||
| > | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar-big.png')} | |||
| className={styles['dataset__item__user-avatar']} | |||
| ></img> | |||
| <div style={{ flex: 1, minWidth: 0 }}> | |||
| <Flex align="center" style={{ marginBottom: '0.625rem' }}> | |||
| <div className={styles['dataset__item__title']}>{item.code_repo_name}</div> | |||
| <img | |||
| className={styles['dataset__item__arrow']} | |||
| src={require('@/assets/img/home/dataset-arrow-right.png')} | |||
| ></img> | |||
| </Flex> | |||
| <div className={styles['dataset__item__desc']}>{item.git_url}</div> | |||
| <Flex align="center"> | |||
| <div>{item.create_by}</div> | |||
| <Divider | |||
| type="vertical" | |||
| className={styles['dataset__item__user-divider']} | |||
| ></Divider> | |||
| <div>{formatDate(item.create_time)}</div> | |||
| <div></div> | |||
| </Flex> | |||
| </div> | |||
| </Flex> | |||
| ); | |||
| })} | |||
| <Flex align="center" style={{ width: '100%' }} wrap gap="1.625rem 1.25rem"> | |||
| {codeCofigs.slice(0, 3).map((item) => createItem(item, styles['code__item--first']))} | |||
| </Flex> | |||
| <Flex | |||
| align="center" | |||
| className={styles['code__second-line']} | |||
| style={{ width: '100%' }} | |||
| wrap | |||
| gap="0 1.25rem" | |||
| > | |||
| {codeCofigs.slice(3).map((item) => createItem(item, styles['code__item--second']))} | |||
| <div className={styles['code__second-line__divider']}></div> | |||
| <div | |||
| className={classNames( | |||
| styles['code__second-line__divider'], | |||
| styles['code__second-line__divider--second'], | |||
| )} | |||
| ></div> | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| @@ -3,34 +3,33 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 0 16.25rem 9.125rem; | |||
| .backgroundFullImage(url(@/assets/img/home/model-bg.png)); | |||
| padding: 0 @home-padding-x 11.125rem; | |||
| &__item { | |||
| position: relative; | |||
| width: 27.875rem; | |||
| padding: 1.875rem 1.25rem; | |||
| color: #8284a4; | |||
| width: calc((100% - 3 * 1.25rem) / 4); | |||
| padding: 1.625rem; | |||
| color: @home-text-color-tertiary; | |||
| font-size: 0.8125rem; | |||
| background-color: transparent; | |||
| border-radius: 11px; | |||
| border-radius: 1rem; | |||
| box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.06); | |||
| cursor: pointer; | |||
| .backgroundFullImage(url(@/assets/img/home/dataset-item-bg.png)); | |||
| &:hover { | |||
| .backgroundFullImage(url(@/assets/img/home/dataset-item-bg-hover.png)); | |||
| &:nth-child(1), | |||
| &:nth-child(2), | |||
| &:nth-child(3) { | |||
| width: calc((100% - 2 * 1.25rem) / 3); | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 2.75rem; | |||
| height: 2.75rem; | |||
| margin-right: 1rem; | |||
| &:hover { | |||
| outline: 2px solid @primary-color; | |||
| box-shadow: 0px 0px 0.75rem rgba(33, 73, 212, 0.15); | |||
| } | |||
| &__title { | |||
| flex: 1; | |||
| color: #020814; | |||
| color: @home-text-color; | |||
| font-size: 1rem; | |||
| .singleLine(); | |||
| } | |||
| @@ -39,30 +38,55 @@ | |||
| color: @primary-color; | |||
| } | |||
| &__arrow { | |||
| display: none; | |||
| flex: none; | |||
| width: 1.5625rem; | |||
| margin-left: 0.75rem; | |||
| } | |||
| &:hover &__arrow { | |||
| display: block; | |||
| &__hot { | |||
| width: 3.25rem; | |||
| height: 1.375rem; | |||
| margin-left: 0.5rem; | |||
| color: white; | |||
| font-size: 0.875rem; | |||
| line-height: 1.375rem; | |||
| text-align: center; | |||
| background: linear-gradient(127.67deg, @primary-color 0%, #e079ff 100%); | |||
| border-radius: 0.6875rem; | |||
| } | |||
| &__desc { | |||
| height: 2.75rem; | |||
| margin-bottom: 0.875rem; | |||
| color: @home-text-color-secondary; | |||
| font-size: 0.875rem; | |||
| line-height: 1.375rem; | |||
| .multiLine(2); | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 1.5rem; | |||
| height: 1.5rem; | |||
| margin-right: 0.875rem; | |||
| } | |||
| &__user { | |||
| .singleLine(); | |||
| } | |||
| &__user-divider { | |||
| flex: none; | |||
| height: 0.625rem; | |||
| margin-right: 0.75rem; | |||
| margin-left: 0.75rem; | |||
| background-color: #8284a4; | |||
| background-color: @home-divider-color; | |||
| } | |||
| &__timestamp-icon { | |||
| flex: none; | |||
| width: 1rem; | |||
| height: 1rem; | |||
| margin-right: 0.375rem; | |||
| } | |||
| &__timestamp { | |||
| flex: none; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,14 +1,13 @@ | |||
| import { DatasetData } from '@/pages/Dataset/config'; | |||
| import { getPublicDatasetsReq } from '@/services/home'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { gotoPageIfLogin } from '@/utils/ui'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| function DatasetBlock() { | |||
| const navigate = useNavigate(); | |||
| const [datasetData, setDatasetData] = useState<DatasetData[]>([]); | |||
| useEffect(() => { | |||
| @@ -16,7 +15,7 @@ function DatasetBlock() { | |||
| const [res] = await to(getPublicDatasetsReq()); | |||
| if (res && res.data) { | |||
| const { content = [] } = res.data; | |||
| setDatasetData(content.slice(0, 6)); | |||
| setDatasetData(content.slice(0, 7)); | |||
| } | |||
| }; | |||
| @@ -27,45 +26,43 @@ function DatasetBlock() { | |||
| <div className={styles.dataset}> | |||
| <BlockTitle | |||
| title="热门数据集" | |||
| style={{ marginBottom: '6.75rem' }} | |||
| onClick={() => navigate('/dataset/dataset')} | |||
| style={{ marginBottom: '3.875rem' }} | |||
| onClick={() => gotoPageIfLogin('/dataset/dataset')} | |||
| ></BlockTitle> | |||
| <Flex align="center" justify="space-between" style={{ width: '100%' }} wrap gap="1.875rem 0"> | |||
| {datasetData.map((item) => { | |||
| <Flex align="center" style={{ width: '100%' }} wrap gap="1.625rem 1.25rem"> | |||
| {datasetData.map((item, index) => { | |||
| return ( | |||
| <Flex | |||
| <div | |||
| className={styles['dataset__item']} | |||
| key={item.id} | |||
| onClick={() => { | |||
| navigate( | |||
| gotoPageIfLogin( | |||
| `/dataset/dataset/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`, | |||
| ); | |||
| }} | |||
| > | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar-big.png')} | |||
| className={styles['dataset__item__user-avatar']} | |||
| ></img> | |||
| <div style={{ flex: 1, minWidth: 0 }}> | |||
| <Flex align="center" style={{ marginBottom: '0.625rem' }}> | |||
| <div className={styles['dataset__item__title']}>{item.name}</div> | |||
| <img | |||
| className={styles['dataset__item__arrow']} | |||
| src={require('@/assets/img/home/dataset-arrow-right.png')} | |||
| ></img> | |||
| </Flex> | |||
| <div className={styles['dataset__item__desc']}>{item.description}</div> | |||
| <Flex align="center"> | |||
| <div>{item.create_by}</div> | |||
| <Divider | |||
| type="vertical" | |||
| className={styles['dataset__item__user-divider']} | |||
| ></Divider> | |||
| <div>{item.time_ago}更新</div> | |||
| <div></div> | |||
| </Flex> | |||
| </div> | |||
| </Flex> | |||
| <Flex align="center" style={{ marginBottom: '1rem' }}> | |||
| <div className={styles['dataset__item__title']}>{item.name}</div> | |||
| {index < 3 && <div className={styles['dataset__item__hot']}>HOT</div>} | |||
| </Flex> | |||
| <div className={styles['dataset__item__desc']}>{item.description}</div> | |||
| <Flex align="center"> | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar.png')} | |||
| className={styles['dataset__item__user-avatar']} | |||
| ></img> | |||
| <div className={styles['dataset__item__user']}>{item.create_by}</div> | |||
| <Divider | |||
| type="vertical" | |||
| className={styles['dataset__item__user-divider']} | |||
| ></Divider> | |||
| <img | |||
| src={require('@/assets/img/home/timestamp.png')} | |||
| className={styles['dataset__item__timestamp-icon']} | |||
| ></img> | |||
| <div className={styles['dataset__item__timestamp']}>{item.time_ago}更新</div> | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| @@ -0,0 +1,44 @@ | |||
| .footer { | |||
| width: 100%; | |||
| padding: 4.375rem 15.625rem 1.25rem; | |||
| color: .addAlpha(@home-text-color, 0.6) []; | |||
| font-size: 0.9375rem; | |||
| font-size: 0.75rem; | |||
| .backgroundFullImage(url(@/assets/img/home/footer-bg.png)); | |||
| &__app-logo { | |||
| width: 1.875rem; | |||
| margin-right: 0.625rem; | |||
| } | |||
| &__app-name { | |||
| margin-right: 5rem; | |||
| color: @home-text-color; | |||
| font-size: 1.5rem; | |||
| font-family: WenYiHei; | |||
| } | |||
| &__about-us { | |||
| width: 24.75rem; | |||
| } | |||
| &__contact { | |||
| align-self: start; | |||
| } | |||
| &__title { | |||
| margin-bottom: 1rem; | |||
| color: @home-text-color; | |||
| font-size: 0.875rem; | |||
| } | |||
| &__desc { | |||
| line-height: 1.5rem; | |||
| } | |||
| &__copyright { | |||
| width: 100%; | |||
| color: @home-text-color-secondary; | |||
| text-align: center; | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { Divider, Flex } from 'antd'; | |||
| import styles from './index.less'; | |||
| function Footer() { | |||
| return ( | |||
| <div className={styles['footer']}> | |||
| <Flex align="center" justify="space-between" style={{ marginBottom: '44px' }}> | |||
| <Flex align="center"> | |||
| <img | |||
| src={require('@/assets/img/home/app-logo-dark.png')} | |||
| className={styles['footer__app-logo']} | |||
| ></img> | |||
| <span className={styles['footer__app-name']}>智能材料科研平台</span> | |||
| </Flex> | |||
| <div className={styles['footer__about-us']}> | |||
| <div className={styles['footer__title']}>关于我们</div> | |||
| <div className={styles['footer__desc']}> | |||
| 我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容我是关于我们的文案简介内容 | |||
| </div> | |||
| </div> | |||
| <div className={styles['footer__contact']}> | |||
| <div className={styles['footer__title']}>联系我们</div> | |||
| <div style={{ marginBottom: '1rem' }}>邮箱:xxxx@163.com</div> | |||
| <div>地址:湖南省长沙市岳麓区中电软件园</div> | |||
| </div> | |||
| </Flex> | |||
| <Divider></Divider> | |||
| <div className={styles['footer__copyright']}>© 2025 国防科技大学所有</div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default Footer; | |||
| @@ -1,18 +1,34 @@ | |||
| .intro { | |||
| position: relative; | |||
| z-index: 10; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding-top: 5.625rem; | |||
| background-image: url(@/assets/img/home/header-bg.png); | |||
| position: fixed; | |||
| top: 0; | |||
| right: 0; | |||
| left: 0; | |||
| z-index: 100; | |||
| height: @home-info-height; | |||
| overflow: hidden; | |||
| background-color: transparent; | |||
| background-repeat: no-repeat; | |||
| background-position: left top; | |||
| background-size: 100% 100%; | |||
| &__content { | |||
| position: relative; | |||
| z-index: 10; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 1.25rem @home-padding-x 0; | |||
| // background-image: url(@/assets/img/home/header-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: left top; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__title { | |||
| margin-top: 1.25rem; | |||
| margin-bottom: 1.125rem; | |||
| color: #ffffff; | |||
| font-weight: 400; | |||
| font-size: 2.375rem; | |||
| font-family: WenYiHei; | |||
| } | |||
| @@ -28,15 +44,15 @@ | |||
| &__button { | |||
| margin-bottom: 4.375rem; | |||
| padding: 0.625rem 2.375rem; | |||
| padding: 0.75rem 2.375rem; | |||
| color: #ffffff; | |||
| font-size: 1rem; | |||
| text-align: center; | |||
| background: linear-gradient( | |||
| 108.54deg, | |||
| #5eb4ff 3.72%, | |||
| rgba(42, 92, 255, 0.01) 49.49%, | |||
| rgba(232, 239, 255, 0.33) 98.01% | |||
| 136.87deg, | |||
| rgba(57, 217, 255, 0.51) 0%, | |||
| rgba(255, 255, 255, 0.01) 48.54%, | |||
| rgba(255, 149, 247, 0.33) 100% | |||
| ); | |||
| border: 1px solid rgba(255, 255, 255, 0.38); | |||
| border-radius: 0.5rem; | |||
| @@ -1,23 +1,81 @@ | |||
| // import NavBar from '../NavBar'; | |||
| import miniHeaderImage from '@/assets/img/home/header-bg-mini.png'; | |||
| import headerImage from '@/assets/img/home/header-bg.png'; | |||
| import { convertRemToPx } from '@/utils'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| motion, | |||
| useMotionTemplate, | |||
| useMotionValueEvent, | |||
| useScroll, | |||
| useSpring, | |||
| useTransform, | |||
| } from 'motion/react'; | |||
| import { useState } from 'react'; | |||
| import NavBar from '../NavBar'; | |||
| import StatisticsBlock from '../Statistics'; | |||
| import styles from './index.less'; | |||
| function IntroBlock() { | |||
| const [backgroundImage1, setBackgroundImage1] = useState(undefined); | |||
| const [backgroundImage2, setBackgroundImage2] = useState(headerImage); | |||
| const navigate = useNavigate(); | |||
| const { scrollY } = useScroll(); | |||
| const springValue = useSpring(scrollY, { | |||
| stiffness: 100, | |||
| damping: 30, | |||
| restDelta: 0.001, | |||
| }); | |||
| const initialHeight = convertRemToPx(35); | |||
| const minHeight = convertRemToPx(4.7); | |||
| const height = useTransform(() => `max(calc(35rem - ${springValue.get()}px), 4.7rem)`); | |||
| const left = useTransform(springValue, [0, initialHeight], [0.0, 16.25]); | |||
| const leftRem = useMotionTemplate`${left}rem`; | |||
| const radius = useTransform(springValue, [0, initialHeight], [0.0, 2.5]); | |||
| const radiusRem = useMotionTemplate`${radius}rem`; | |||
| const top = useTransform(springValue, [0, initialHeight], [0, 10]); | |||
| const paddingX = useTransform(springValue, [0, initialHeight], [16.25, 10]); | |||
| const paddingXRem = useMotionTemplate`1.25rem ${paddingX}rem 0`; | |||
| useMotionValueEvent(scrollY, 'change', (value) => { | |||
| setBackgroundImage1(value > initialHeight - minHeight ? miniHeaderImage : undefined); | |||
| setBackgroundImage2(value > initialHeight - minHeight ? undefined : headerImage); | |||
| }); | |||
| return ( | |||
| <div className={styles.intro}> | |||
| {/* <NavBar></NavBar> */} | |||
| <div className={styles['intro__title']}>智能材料科研平台</div> | |||
| <div className={styles['intro__desc']}> | |||
| 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, | |||
| 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 | |||
| </div> | |||
| <div className={styles['intro__button']} onClick={() => navigate('/workspace')}> | |||
| 开始使用 | |||
| </div> | |||
| <StatisticsBlock></StatisticsBlock> | |||
| </div> | |||
| <motion.div | |||
| className={styles.intro} | |||
| style={{ | |||
| height: height, | |||
| left: leftRem, | |||
| right: leftRem, | |||
| borderRadius: radiusRem, | |||
| top: top, | |||
| backgroundImage: backgroundImage1 ? `url(${backgroundImage1})` : undefined, | |||
| }} | |||
| transition={{ | |||
| type: 'spring', | |||
| duration: 0.3, | |||
| }} | |||
| > | |||
| <motion.div | |||
| className={styles['intro__content']} | |||
| style={{ | |||
| padding: paddingXRem, | |||
| backgroundImage: backgroundImage2 ? `url(${backgroundImage2})` : undefined, | |||
| }} | |||
| > | |||
| <NavBar></NavBar> | |||
| <div className={styles['intro__title']}>智能材料科研平台</div> | |||
| <div className={styles['intro__desc']}> | |||
| 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, | |||
| 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 | |||
| </div> | |||
| <div className={styles['intro__button']} onClick={() => navigate('/workspace')}> | |||
| 开始使用 | |||
| </div> | |||
| <StatisticsBlock></StatisticsBlock> | |||
| </motion.div> | |||
| </motion.div> | |||
| ); | |||
| } | |||
| @@ -1,67 +1,110 @@ | |||
| .model { | |||
| .mirror { | |||
| position: relative; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 0 16.25rem 9.125rem; | |||
| .backgroundFullImage(url(@/assets/img/home/model-bg.png)); | |||
| padding: 0 @home-padding-x 11.125rem; | |||
| &__item { | |||
| position: relative; | |||
| width: 27.875rem; | |||
| padding: 1.875rem 1.25rem; | |||
| color: #8284a4; | |||
| width: calc((100% - 2 * 1.25rem) / 4); | |||
| padding: 1.625rem; | |||
| color: @home-text-color-tertiary; | |||
| font-size: 0.8125rem; | |||
| background-color: transparent; | |||
| border-radius: 11px; | |||
| box-shadow: 0rem 0.0625rem 0.75rem rgba(33, 73, 212, 0.09); | |||
| border-radius: 1.125rem; | |||
| box-shadow: 0px 0.1875rem 0.75rem rgba(41, 50, 225, 0.09); | |||
| cursor: pointer; | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-bg.png)); | |||
| .backgroundFullImage(url(@/assets/img/home/dataset-item-bg.png)); | |||
| &:hover { | |||
| color: white; | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-bg-hover.png)); | |||
| } | |||
| &:nth-child(3n + 2) { | |||
| top: -3.75rem; | |||
| &:nth-child(2), | |||
| &:nth-child(4) { | |||
| width: calc((100% - 2 * 1.25rem) / 2); | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 2.75rem; | |||
| height: 2.75rem; | |||
| margin-right: 0.875rem; | |||
| &:hover { | |||
| outline: 2px solid @primary-color; | |||
| box-shadow: 0px 0.1875rem 0.75rem rgba(41, 50, 225, 0.09); | |||
| } | |||
| &__title { | |||
| margin-bottom: 0.625rem; | |||
| color: #020814; | |||
| flex: 1; | |||
| margin-right: 0.5rem; | |||
| color: @home-text-color; | |||
| font-size: 1rem; | |||
| .singleLine(); | |||
| } | |||
| &:hover &__title { | |||
| color: white; | |||
| color: @primary-color; | |||
| } | |||
| &__arrow { | |||
| display: none; | |||
| width: 0.75rem; | |||
| height: 0.75rem; | |||
| } | |||
| &:hover &__arrow { | |||
| display: block; | |||
| } | |||
| &__desc { | |||
| height: 2.75rem; | |||
| margin-bottom: 0.875rem; | |||
| color: @home-text-color-secondary; | |||
| font-size: 0.875rem; | |||
| line-height: 1.375rem; | |||
| .multiLine(2); | |||
| } | |||
| &__version { | |||
| width: fit-content; | |||
| margin-bottom: 1.625rem; | |||
| padding: 0.375rem 0.625rem; | |||
| color: @primary-color; | |||
| font-size: 0.875rem; | |||
| background: linear-gradient( | |||
| 90deg, | |||
| .addAlpha(@primary-color, 0.1) [] 0%, | |||
| .addAlpha(#c7daff, 0.1) [] 100% | |||
| ); | |||
| border-radius: 0.25rem; | |||
| &__img { | |||
| width: 0.875rem; | |||
| height: 0.875rem; | |||
| margin-right: 0.375rem; | |||
| } | |||
| } | |||
| &__user-avatar { | |||
| flex: none; | |||
| width: 1.5rem; | |||
| height: 1.5rem; | |||
| margin-right: 0.875rem; | |||
| } | |||
| &__user { | |||
| .singleLine(); | |||
| } | |||
| &__user-divider { | |||
| flex: none; | |||
| height: 0.625rem; | |||
| margin-right: 0.75rem; | |||
| margin-left: 0.75rem; | |||
| background-color: #8284a4; | |||
| background-color: @home-divider-color; | |||
| } | |||
| &:hover &__user-divider { | |||
| background-color: white; | |||
| &__timestamp-icon { | |||
| flex: none; | |||
| width: 1rem; | |||
| height: 1rem; | |||
| margin-right: 0.375rem; | |||
| } | |||
| &__timestamp { | |||
| flex: none; | |||
| } | |||
| } | |||
| } | |||
| @@ -2,14 +2,13 @@ import { MirrorData } from '@/pages/Mirror/List'; | |||
| import { getPublicImagesReq } from '@/services/home'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { gotoPageIfLogin } from '@/utils/ui'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| function MirrorBlock() { | |||
| const navigate = useNavigate(); | |||
| const [mirrorData, setMirrirData] = useState<MirrorData[]>([]); | |||
| useEffect(() => { | |||
| @@ -25,40 +24,54 @@ function MirrorBlock() { | |||
| }, []); | |||
| return ( | |||
| <div className={styles.model}> | |||
| <div className={styles.mirror}> | |||
| <BlockTitle | |||
| title="热门镜像" | |||
| style={{ marginBottom: '8.125rem' }} | |||
| onClick={() => navigate('/dataset/mirror')} | |||
| style={{ marginBottom: '5.25rem' }} | |||
| onClick={() => gotoPageIfLogin('/dataset/mirror')} | |||
| ></BlockTitle> | |||
| <Flex align="center" justify="space-between" style={{ width: '100%' }} wrap gap="3.125rem 0"> | |||
| <Flex align="center" style={{ width: '100%' }} wrap gap="1.625rem 1.25rem"> | |||
| {mirrorData.map((item) => { | |||
| return ( | |||
| <Flex | |||
| className={styles['model__item']} | |||
| <div | |||
| className={styles['mirror__item']} | |||
| key={item.id} | |||
| onClick={() => { | |||
| navigate(`/dataset/mirror/info/${item.id}`); | |||
| gotoPageIfLogin(`/dataset/mirror/info/${item.id}`); | |||
| }} | |||
| > | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar-big.png')} | |||
| className={styles['model__item__user-avatar']} | |||
| ></img> | |||
| <div style={{ flex: 1, minWidth: 0 }}> | |||
| <div className={styles['model__item__title']}>{item.name}</div> | |||
| <div className={styles['model__item__desc']}>{item.description}</div> | |||
| <Flex align="center"> | |||
| <div>{item.create_by}</div> | |||
| <Divider | |||
| type="vertical" | |||
| className={styles['model__item__user-divider']} | |||
| ></Divider> | |||
| <div>{formatDate(item.create_time)}</div> | |||
| <div></div> | |||
| </Flex> | |||
| </div> | |||
| </Flex> | |||
| <Flex align="center" style={{ marginBottom: '1rem' }}> | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar.png')} | |||
| className={styles['mirror__item__user-avatar']} | |||
| ></img> | |||
| <div className={styles['mirror__item__title']}>{item.name}</div> | |||
| <img | |||
| className={styles['mirror__item__arrow']} | |||
| src={require('@/assets/img/home/mirror-arrow.png')} | |||
| ></img> | |||
| </Flex> | |||
| <div className={styles['mirror__item__desc']}>{item.description}</div> | |||
| <Flex align="center" className={styles['mirror__item__version']}> | |||
| <img | |||
| src={require('@/assets/img/home/mirror-version.png')} | |||
| className={styles['mirror__item__version__img']} | |||
| ></img> | |||
| <span>{`版本数:${item.version_count}`}</span> | |||
| </Flex> | |||
| <Flex align="center"> | |||
| <div className={styles['mirror__item__user']}>{item.create_by}</div> | |||
| <Divider type="vertical" className={styles['mirror__item__user-divider']}></Divider> | |||
| <img | |||
| src={require('@/assets/img/home/timestamp.png')} | |||
| className={styles['mirror__item__timestamp-icon']} | |||
| ></img> | |||
| <div className={styles['mirror__item__timestamp']}> | |||
| {formatDate(item.create_time)} | |||
| </div> | |||
| <div></div> | |||
| </Flex> | |||
| </div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| @@ -3,18 +3,28 @@ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| padding: 5.375rem 16.25rem 9.125rem; | |||
| padding: 4.125rem @home-padding-x 14.75rem; | |||
| .backgroundFullImage(url(@/assets/img/home/model-bg.png)); | |||
| &__content { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 1.625rem 1.25rem; | |||
| align-items: center; | |||
| width: 100%; | |||
| } | |||
| &__item { | |||
| position: relative; | |||
| width: 27.875rem; | |||
| display: flex; | |||
| align-items: center; | |||
| width: calc((100% - 2 * 1.25rem) / 3); | |||
| padding: 1.875rem 1.25rem; | |||
| color: #8284a4; | |||
| color: @home-text-color-tertiary; | |||
| font-size: 0.8125rem; | |||
| background-color: transparent; | |||
| border-radius: 11px; | |||
| box-shadow: 0rem 0.0625rem 0.75rem rgba(33, 73, 212, 0.09); | |||
| border-radius: 1rem; | |||
| box-shadow: 0px 0.0625rem 0.75rem rgba(33, 73, 212, 0.09); | |||
| cursor: pointer; | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-bg.png)); | |||
| @@ -23,8 +33,17 @@ | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-bg-hover.png)); | |||
| } | |||
| &:nth-child(3n + 2) { | |||
| top: -3.75rem; | |||
| &__hot { | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-hot.png)); | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| width: 4.625rem; | |||
| height: 2rem; | |||
| } | |||
| &:hover &__hot { | |||
| .backgroundFullImage(url(@/assets/img/home/model-item-hot-hover.png)); | |||
| } | |||
| &__user-avatar { | |||
| @@ -36,7 +55,7 @@ | |||
| &__title { | |||
| margin-bottom: 0.625rem; | |||
| color: #020814; | |||
| color: @home-text-color; | |||
| font-size: 1rem; | |||
| .singleLine(); | |||
| } | |||
| @@ -53,15 +72,42 @@ | |||
| .multiLine(2); | |||
| } | |||
| &__user { | |||
| .singleLine(); | |||
| } | |||
| &__user-divider { | |||
| flex: none; | |||
| height: 0.625rem; | |||
| margin-right: 0.75rem; | |||
| margin-left: 0.75rem; | |||
| background-color: #8284a4; | |||
| background-color: @home-divider-color; | |||
| } | |||
| &__timestamp { | |||
| flex: none; | |||
| } | |||
| &:hover &__user-divider { | |||
| background-color: white; | |||
| } | |||
| // &__category { | |||
| // padding: 0.25rem 0.625rem; | |||
| // color: @primary-color; | |||
| // font-size: 0.8125rem; | |||
| // background-color: .addAlpha(@primary-color, 0.07) []; | |||
| // border-radius: 0.25rem; | |||
| // &:nth-child(2) { | |||
| // color: rgba(28, 153, 7, 1); | |||
| // background-color: rgba(28, 153, 7, 0.07); | |||
| // } | |||
| // } | |||
| // &:hover &__category { | |||
| // color: @home-text-color; | |||
| // background-color: white; | |||
| // } | |||
| } | |||
| } | |||
| @@ -1,14 +1,35 @@ | |||
| import { ModelData } from '@/pages/Dataset/config'; | |||
| import { getPublicModelsReq } from '@/services/home'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { gotoPageIfLogin } from '@/utils/ui'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import { motion, type Variants } from 'motion/react'; | |||
| import { useEffect, useState } from 'react'; | |||
| import BlockTitle from '../BlockTitle'; | |||
| import styles from './index.less'; | |||
| const modelVariants: Variants = { | |||
| offscreen: (index: number) => ({ | |||
| y: 0, | |||
| opacity: 1, | |||
| transition: { | |||
| ease: 'linear', | |||
| duration: 0.1, | |||
| }, | |||
| }), | |||
| onscreen: { | |||
| y: [0, 200, 0], | |||
| opacity: [0, 0, 1], | |||
| transition: { | |||
| ease: 'easeOut', | |||
| duration: 0.3, | |||
| times: [0, 0, 1], | |||
| delay: 0.5, | |||
| }, | |||
| }, | |||
| }; | |||
| function ModelBlock() { | |||
| const navigate = useNavigate(); | |||
| const [modelData, setModelData] = useState<ModelData[]>([]); | |||
| useEffect(() => { | |||
| @@ -27,42 +48,51 @@ function ModelBlock() { | |||
| <div className={styles.model}> | |||
| <BlockTitle | |||
| title="热门模型" | |||
| style={{ marginBottom: '8.125rem' }} | |||
| onClick={() => navigate('/dataset/model')} | |||
| style={{ marginBottom: '5.25rem' }} | |||
| onClick={() => gotoPageIfLogin('/dataset/model')} | |||
| ></BlockTitle> | |||
| <Flex align="center" justify="space-between" style={{ width: '100%' }} wrap gap="3.125rem 0"> | |||
| {modelData.map((item) => { | |||
| <div className={styles['model__content']}> | |||
| {modelData.map((item, index) => { | |||
| return ( | |||
| <Flex | |||
| <motion.div | |||
| variants={modelVariants} | |||
| initial={'offscreen'} | |||
| whileInView={'onscreen'} | |||
| custom={index} | |||
| className={styles['model__item']} | |||
| key={item.id} | |||
| onClick={() => { | |||
| navigate( | |||
| gotoPageIfLogin( | |||
| `/dataset/model/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`, | |||
| ); | |||
| }} | |||
| > | |||
| <img | |||
| src={require('@/assets/img/home/user-avatar-big.png')} | |||
| src={require('@/assets/img/home/user-avatar.png')} | |||
| className={styles['model__item__user-avatar']} | |||
| ></img> | |||
| {index < 3 && <div className={styles['model__item__hot']}></div>} | |||
| <div style={{ flex: 1, minWidth: 0 }}> | |||
| <div className={styles['model__item__title']}>{item.name}</div> | |||
| <div className={styles['model__item__desc']}>{item.description}</div> | |||
| <Flex align="center"> | |||
| <div>{item.create_by}</div> | |||
| <div className={styles['model__item__user']}>{item.create_by}</div> | |||
| <Divider | |||
| type="vertical" | |||
| className={styles['model__item__user-divider']} | |||
| ></Divider> | |||
| <div>{item.time_ago}更新</div> | |||
| <div className={styles['model__item__timestamp']}>{item.time_ago}更新</div> | |||
| <div></div> | |||
| </Flex> | |||
| {/* <Flex align="center" style={{ marginTop: '1.25rem' }} gap={'0 0.625rem'}> | |||
| <div className={styles['model__item__category']}>电池开发</div> | |||
| <div className={styles['model__item__category']}>材料研发</div> | |||
| </Flex> */} | |||
| </div> | |||
| </Flex> | |||
| </motion.div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -1,7 +1,52 @@ | |||
| .nav-bar { | |||
| display: flex; | |||
| align-items: center; | |||
| padding: 1.25rem 15.625rem; | |||
| justify-content: space-between; | |||
| width: 100%; | |||
| margin-bottom: 4.375rem; | |||
| color: white; | |||
| font-size: 0.9375rem; | |||
| } | |||
| &__app-logo { | |||
| width: 1.75rem; | |||
| margin-right: 0.625rem; | |||
| } | |||
| &__app-name { | |||
| margin-right: 6.625rem; | |||
| font-size: 1.375rem; | |||
| font-family: WenYiHei; | |||
| line-height: 2.2rem; | |||
| } | |||
| &__menu-item { | |||
| margin-right: 3.125rem; | |||
| padding: 0.25rem 0.5rem; | |||
| border-radius: 0.375rem; | |||
| cursor: pointer; | |||
| &:hover { | |||
| background-color: rgba(0, 0, 0, 0.06); | |||
| } | |||
| &:last-of-type { | |||
| margin-right: 0; | |||
| } | |||
| } | |||
| :global { | |||
| .ant-dropdown-trigger { | |||
| height: 2.15rem; | |||
| .ant-avatar { | |||
| width: 1.875rem; | |||
| height: 1.875rem; | |||
| margin-right: 0 !important; | |||
| } | |||
| } | |||
| .ant-dropdown-trigger > .anticon { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,10 +1,87 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import Avatar from '@/components/RightContent/AvatarDropdown'; | |||
| import { gotoPageIfLogin } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import styles from './index.less'; | |||
| function NavBar() { | |||
| const navigate = useNavigate(); | |||
| const token = getAccessToken(); | |||
| const gotoPage = (page: string) => { | |||
| if (page === 'login') { | |||
| navigate('/user/login'); | |||
| return; | |||
| } | |||
| let pathname = ''; | |||
| switch (page) { | |||
| case 'service': | |||
| pathname = '/dataset/modelDeployment'; | |||
| break; | |||
| case 'model': | |||
| pathname = '/dataset/model'; | |||
| break; | |||
| case 'dataset': | |||
| pathname = '/dataset/dataset'; | |||
| break; | |||
| case 'mirror': | |||
| pathname = '/dataset/mirror'; | |||
| break; | |||
| case 'codeConfig': | |||
| pathname = '/dataset/codeConfig'; | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| if (pathname) { | |||
| gotoPageIfLogin(pathname); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['nav-bar']}> | |||
| <div>智能材料科研平台</div> | |||
| <div>首页</div> | |||
| <Flex align="center"> | |||
| <img | |||
| src={require('@/assets/img/home/app-logo.png')} | |||
| className={styles['nav-bar__app-logo']} | |||
| ></img> | |||
| <span className={styles['nav-bar__app-name']}>智能材料科研平台</span> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('service')}> | |||
| 服务 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('model')}> | |||
| 模型 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('dataset')}> | |||
| 数据集 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('mirror')}> | |||
| 镜像 | |||
| </div> | |||
| <div className={styles['nav-bar__menu-item']} onClick={() => gotoPage('codeConfig')}> | |||
| 代码配置 | |||
| </div> | |||
| </Flex> | |||
| {token ? ( | |||
| <Avatar menu isHome /> | |||
| ) : ( | |||
| <div | |||
| className={classNames(styles['nav-bar__menu-item'], styles['nav-bar__login'])} | |||
| onClick={() => gotoPage('login')} | |||
| > | |||
| 登录 | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| import { motion, useInView, Variants } from 'motion/react'; | |||
| import { ReactNode, useRef } from 'react'; | |||
| type ScrollRevealProps = { | |||
| children: ReactNode; | |||
| variants: Variants; | |||
| }; | |||
| function ScrollReveal({ children, variants }: ScrollRevealProps) { | |||
| const ref = useRef<HTMLDivElement>(null); | |||
| const isInView = useInView(ref, { amount: 'all' }); | |||
| return ( | |||
| <motion.div | |||
| variants={variants} | |||
| ref={ref} | |||
| initial="offscreen" | |||
| animate={isInView ? 'onscreen' : 'offscreen'} | |||
| > | |||
| {children} | |||
| </motion.div> | |||
| ); | |||
| } | |||
| export default ScrollReveal; | |||