diff --git a/src/ai/component.jsx b/src/ai/component.jsx index a3444112..f9a27531 100644 --- a/src/ai/component.jsx +++ b/src/ai/component.jsx @@ -1,15 +1,20 @@ import { message, Modal } from "antd" import { deleteProjectInZoneById, deleteSourceById, takeDownSourceById } from "../forge/Information/api"; +import { useState } from "react"; export const DeleteModal = ({type, id, cancel, roleByZone, reload}) =>{ - return { + const [loading, setLoading] = useState(false) + return { const deleteFn = type === "codes" ? deleteProjectInZoneById : deleteSourceById; + setLoading(true); deleteFn(id, roleByZone).then(res=>{ if(res && res.data && res.data.code === 200){ message.success(`操作成功`); cancel(); reload(Math.random()); } + }).finally(()=>{ + setLoading(false); }) }}>
@@ -21,13 +26,17 @@ export const DeleteModal = ({type, id, cancel, roleByZone, reload}) =>{ } export const TakeDownModal = ({id, cancel, roleByZone, reload}) =>{ - return { + const [loading, setLoading] = useState(false) + return { + setLoading(true); takeDownSourceById(id, roleByZone).then(res=>{ if(res && res.data && res.data.code === 200){ message.success(`操作成功`); cancel(); reload(Math.random()); } + }).finally(()=>{ + setLoading(false); }) }}>
diff --git a/src/ai/create/index.jsx b/src/ai/create/index.jsx index 39b4bf64..e7adddd2 100644 --- a/src/ai/create/index.jsx +++ b/src/ai/create/index.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, Fragment } from "react"; +import React, { useState, useEffect, Fragment, useRef } from "react"; import { Input, Select, Upload, message, Form, Checkbox, Tree, Spin } from "antd"; import './index.scss'; import { aiCreateSteps, aiResouceType, aiResouceTypeKey, convertToTreeData } from "../static"; @@ -6,7 +6,8 @@ import { Link } from "react-router-dom"; import { AlignCenter } from "../../forge/Component/layout"; import DorpChooseMenus from "../../forge/Component/licenseModal/dorpChooseMenus"; import axios from "axios"; -import { addProjectInZone, addSkill, editSkill, getProjectsTypeLists, getSkillById, getSourceZoneList, parseFileBySkill } from "../../forge/Information/api"; +import { addDataset, addModel, addProjectInZone, addSkill, editDataset, editModel, editSkill, getProjectsTypeLists, getAISourceById, getSourceZoneList, parseFileBySkill } from "../../forge/Information/api"; +import ChunkedUpload from "../../components/ChunkedUpload"; const { Option } = Select; function CreateResource(props) { @@ -18,7 +19,6 @@ function CreateResource(props) { const { id: aiZoneId } = aiZoneInfo || {} const [loading, setLoading] = useState(false); - const [resourceType, setResourceType] = useState("models"); const [currentStep, setCurrentStep] = useState(0); const [categoryList, setCategoryList] = useState(); const [languageList, setLanguageList] = useState(); @@ -27,6 +27,8 @@ function CreateResource(props) { const [ownerList, setOwnerList] = useState(); const [cate, setCate] = useState(); const [info, setInfo] = useState(); + const [uploadedFile, setUploadedFile] = useState(null); + const uploadRef = useRef(null); const typeDetail = type && aiResouceType.find(i => i.key === type); const { ignoreFlag, licenseFlag, categoreFlag, languageFlag, file } = getFieldsValue(); @@ -100,23 +102,31 @@ function CreateResource(props) { useEffect(() => { if (sourceId) { - getSkillById(sourceId).then(res => { + getAISourceById(sourceId).then(res => { if (res && res.data && res.data.code === 200) { - const { fileIds, extendData, name, summary, domainId } = res.data.data || {} - setFieldsValue({ - file: { - file: { - response: { - fileId: fileIds, - skillMdContent: extendData.skill.content, - fileTreeJson: JSON.stringify(extendData.skill.fileTree) - } - } - }, + const { fileIds, extendData, name, summary, domainId, fileList } = res.data.data || {} + const fileInfo = { + fileId: fileList[0]?.fileId, + fileOriginName: fileList[0]?.fileOriginName, + fileSize: fileList[0]?.fileSizeInfo, + url: fileList[0]?.downloadUrl + } + setUploadedFile(fileInfo) + const fileValue = { name, summary, domainId - }) + } + type === "skills" ? fileValue.file = { + file: { + response: { + fileId: fileIds, + skillMdContent: extendData.skill.content, + fileTreeJson: JSON.stringify(extendData.skill.fileTree) + } + } + } : fileValue.fileId = fileIds + setFieldsValue(fileValue) } }) } @@ -135,7 +145,6 @@ function CreateResource(props) { const compelete = (response) => { if (response && response.data && response.data.code === 200) { message.success("操作成功"); - // ${type === "codes" ? '' : "/" + sourceId || response.data.data.id} history.push(`/ai/${type}`); } } @@ -171,6 +180,18 @@ function CreateResource(props) { submitFn(param, roleByZone).then(response => { compelete(response) }) break; default: + // models/datasets 类型:使用分片上传的 fileId + const { fileId: formFileId } = getFieldsValue() || {}; + const submitParam = { + ...values, + fileId: formFileId, + zoneId: aiZoneId + } + if (sourceId) { + submitParam.id = sourceId; + } + const submitFn1 = type === "models" ? sourceId ? editModel : addModel : sourceId ? editDataset : addDataset; + submitFn1(submitParam).then(response => { compelete(response) }) break; } } @@ -229,6 +250,10 @@ function CreateResource(props) { e.preventDefault(); e.stopPropagation(); } + form.resetFields(); + if (uploadRef.current) { + uploadRef.current.cancelUpload(); + } }} > @@ -277,7 +302,6 @@ function CreateResource(props) { {getFieldDecorator('user_id', { rules: [{ @@ -307,7 +331,6 @@ function CreateResource(props) { / {getFieldDecorator('name', { rules: [{ @@ -372,7 +395,7 @@ function CreateResource(props) { {codeCheckBox("categoreFlag", "项目类别", categoreFlag, "project_category", categoryList)} {codeCheckBox("languageFlag", "项目语言", languageFlag, "project_language", languageList)} : - + {type === "skills" && {getFieldDecorator('file', { rules: [{ required: true, message: "请上传文件" }], })( @@ -420,14 +443,14 @@ function CreateResource(props) {
)} - + } {getFieldDecorator('name', { rules: [{ - required: true, message: '请填写资源名称' + required: true, message: '请填写名称' }], })( - + )} @@ -443,10 +466,10 @@ function CreateResource(props) { {getFieldDecorator('domainId', { rules: [{ - required: true, message: '请选择模型类别' + required: true, message: '请选择类别' }], })( - {cate && cate.length > 0 && cate.map((i, k) => { return ( @@ -456,8 +479,44 @@ function CreateResource(props) { } )} - + {/* models/datasets 类型使用分片上传 */} + {type !== "skills" && + {getFieldDecorator('fileId', { + rules: [{ + required: true, message: `请上传${typeDetail.name}文件` + }], + })( +
+ { + // 设置表单字段,保存 fileId + setFieldsValue({ + fileId: fileInfo.fileId + }); + }} + onCancel={() => { + setUploadedFile(null); + setFieldsValue({ + fileId: undefined + }); + }} + > + + + +
请上传{typeDetail.name}压缩包(支持 .zip 格式)
+
请确保压缩包内包含{typeDetail.name}文件及 README.md,系统将自动解析说明文档
+
单文件最大支持 50GB,系统支持断点续传
+
+
+ )} +
} } {/* 提交按钮 */} diff --git a/src/ai/create/index.scss b/src/ai/create/index.scss index 787e254a..c825c883 100644 --- a/src/ai/create/index.scss +++ b/src/ai/create/index.scss @@ -157,15 +157,14 @@ padding: 30px 0; border: 1px dashed rgba(22, 76, 228, 0.17); border-radius: 6px; - - .ai_create_upload_icon { - display: inline-block; - width: 45px; - height: 45px; - background: #f3f4f8; - border-radius: 50%; - line-height: 45px; - } + } + .ai_create_upload_icon { + display: inline-block; + width: 45px; + height: 45px; + background: #f3f4f8; + border-radius: 50%; + line-height: 45px; } .ai_tree_file{ border: 1px solid #D9D9D9; diff --git a/src/ai/detail/index.jsx b/src/ai/detail/index.jsx index 0914038f..1a4f64c1 100644 --- a/src/ai/detail/index.jsx +++ b/src/ai/detail/index.jsx @@ -3,8 +3,8 @@ import { Tabs, Tag, Spin, message, Divider, Tree, Tooltip } from "antd"; import axios from "axios"; import RenderHtml from "../../components/render-html"; import "./index.scss"; -import { getlikedSource, getSkillById, likeAISource } from "../../forge/Information/api"; -import { convertToTreeData } from "../static"; +import { getlikedSource, getAISourceById, likeAISource } from "../../forge/Information/api"; +import { aiResouceTypeKey, convertToTreeData } from "../static"; import skillIcon from '../image/icon7.png'; import CommentsList from "../../components/commentsByZone"; @@ -13,7 +13,7 @@ const { TabPane } = Tabs; function Detail(props) { // 路由参数 const { history, match: { params }, current_user: { login }, current_user, showLoginDialog, showNotification } = props; - const { id } = params || {}; + const { id, type } = params || {}; // 状态管理 const [reload, setReload] = useState(undefined); @@ -25,10 +25,11 @@ function Detail(props) { const [branch, setBranch] = useState("master"); const [liked, setLiked] = useState(false); - const { extendData: { skill: { content, fileTree } = {} } = {} } = detail; - - const skillTreeData = fileTree && convertToTreeData(fileTree.children) - const skillContent = String(content).replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, ''); + const { extendData={} } = detail; + const dataByType = extendData[aiResouceTypeKey[type]] || {} + const content = dataByType.content || dataByType.readmeContent + const introContent = String(content || '
暂无 README 内容
').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, ''); + const treeData = dataByType && dataByType.fileTree && convertToTreeData(dataByType.fileTree.children) // 组件挂载和 ID 变化时加载数据 useEffect(() => { @@ -40,7 +41,7 @@ function Detail(props) { const getDetail = () => { - id && getSkillById(id).then(response => { + id && getAISourceById(id).then(response => { if (response && response.data && response.data.code === 200) { const { name } = response.data.data document.title = `${name}-AI资源`; @@ -59,21 +60,6 @@ function Detail(props) { }) } - // 处理分支切换 - const handleBranchChange = (value) => { - setBranch(value); - // 重新获取文件列表 - axios.get(`/api/ai/models/${id}/files.json`, { params: { branch: value } }) - .then(res => { - if (res && res.data) { - setFiles(res.data); - } - }) - .catch(error => { - console.error(error); - }); - }; - // 格式化下载量 const formatDownloads = (num) => { if (num >= 10000) { @@ -82,17 +68,6 @@ function Detail(props) { return num; }; - // 格式化文件大小 - const formatFileSize = (size) => { - if (!size) return "0KB"; - const num = parseFloat(size); - if (isNaN(num)) return size; - if (num >= 1024) { - return (num / 1024).toFixed(2) + "MB"; - } - return Math.round(num) + "KB"; - }; - if (loading) { return (
@@ -116,7 +91,7 @@ function Detail(props) {
-
+
{detail.name}
@@ -137,16 +112,21 @@ function Detail(props) { {detail.domainName} )} + {dataByType && dataByType.fileSize && ( + + {dataByType.fileSize} + + )}
@@ -210,11 +190,7 @@ function Detail(props) { {activeTab === "introduction" && (
- {skillContent ? ( - - ) : ( -
暂无 README 内容
- )} +
)} @@ -222,78 +198,12 @@ function Detail(props) { {activeTab === "files" && (
- {/*
- - - {files?.lastCommit && ( -
- - {files.lastCommit.message || "暂无提交信息"} - - - {files.lastCommit.author || "unknown"} - - - {files.lastCommit.sha?.substring(0, 7) || "unknown"} - - - {files.lastCommit.time || "刚刚"} - -
- )} - -
- {files?.commitCount || 0} 次提交 -
-
- -
- {files?.files && files.files.length > 0 ? ( - files.files.map((file, index) => ( -
-
- - {file.name} - - {formatFileSize(file.size)} - -
-
- - {file.lastMessage || "暂无信息"} - - - {timeAgo(file.updateTime || new Date())} - -
- -
- )) - ) : ( -
暂无文件
- )} -
*/}
)} diff --git a/src/ai/detail/index.scss b/src/ai/detail/index.scss index c1b8735c..fc8d3125 100644 --- a/src/ai/detail/index.scss +++ b/src/ai/detail/index.scss @@ -4,6 +4,7 @@ background-size: 100% auto; padding-top: 45px; padding-bottom: 50px; + min-height: 89vh; } // 加载状态 @@ -345,4 +346,15 @@ --light-color-2: rgba(70,106,255,0.23); --light-color-3: rgba(70,106,255,0.55); } + + // 文件树节点样式 + .ai_detail_tree_node { + .ant-tree-title{ + width: calc(100% - 42px); + display: inline-flex; + justify-content: space-between; + font-size: 15px; + color:#27264d; + } + } } diff --git a/src/ai/list/index.jsx b/src/ai/list/index.jsx index 4125a9c2..e7d11d57 100644 --- a/src/ai/list/index.jsx +++ b/src/ai/list/index.jsx @@ -9,15 +9,16 @@ import icon6 from '../image/icon6.png'; import { Divider, Input, Pagination } from "antd"; import ActionItem from "../../factory/resource/components/ActionItem"; import { Link } from "react-router-dom"; -import { getProjectsLists, getProjectsTypeLists, getSkillList, getSourceZoneList } from "../../forge/Information/api"; +import { getAISourceList, getProjectsLists, getProjectsTypeLists, getSourceZoneList } from "../../forge/Information/api"; import moment from "moment"; +import Nodata from "../../forge/Nodata"; function List(props) { const { match: { params }, history, aiZoneInfo, current_user } = props || {}; const type = params.type || aiResouceType[0].key const [activeCategory, setActiveCategory] = useState(); const [page, setPage] = useState(1); - const [total, setTotal] = useState(20); + const [total, setTotal] = useState(0); const [cate, setCate] = useState([]); const [list, setList] = useState(); const [search, setSearch] = useState(); @@ -51,9 +52,10 @@ function List(props) { }, [type, aiZoneId]) useEffect(() => { + if(!aiZoneId) return switch (type) { case "codes": - aiZoneId && getProjectsLists(aiZoneId, { + getProjectsLists(aiZoneId, { pageNum: page, pageSize: 10, projectTypeId: activeCategory, @@ -72,20 +74,20 @@ function List(props) { date: projectProperties.timeAgo, forkedCount: projectProperties.forkedCount, likes: projectProperties.praisesCount, - topics: projectProperties.topics ? projectProperties.topics.split(",") : [] + language: projectProperties.language ? projectProperties.language.split(",") : [] } })) setTotal(res.data.total) } }) break; - case "skills": - getSkillList({ - auditStatus: 1, + default: + getAISourceList(aiZoneId, { domainId: activeCategory, pageNum: page, pageSize: 10, - name: search + name: search, + resourceCategory: aiResouceTypeKey[type] }).then(res => { if (res && res.data && res.data.rows) { setList(res.data.rows.map(row => { @@ -104,10 +106,9 @@ function List(props) { setTotal(res.data.total) } }) - default: break; } - }, [activeCategory, aiZoneId, page, search, type]) + }, [type, aiZoneId, activeCategory, page, search]) const aiCard = (model) => { return @@ -202,8 +203,8 @@ function List(props) {
{/* 模型列表 */} -
- {list && list.map((model) => ( + {list && !!list.length ?
+ {list.map((model) => (
@@ -224,23 +225,22 @@ function List(props) {
{aiCard(model)} {type === "codes" ?
- {model.topics && !!model.topics.length && + {model.language && !!model.language.length && - {model.topics.map((topic, index) => { + {model.language.map((topic, index) => { return - {topic} - {index < model.topics.length - 1 && } + {topic} + {index < model.language.length - 1 && } })} } - -
: 大语言模型} +
: {model.domainName}}
}
))} -
+
:
} {/* 分页 */} { setPage(p) }} pageSize={10} total={total} showQuickJumper hideOnSinglePage /> diff --git a/src/ai/my-resources/index.jsx b/src/ai/my-resources/index.jsx index 4651536d..5ffd51dc 100644 --- a/src/ai/my-resources/index.jsx +++ b/src/ai/my-resources/index.jsx @@ -3,7 +3,7 @@ import { Link } from "react-router-dom"; import { Table, Pagination, Badge, message } from "antd"; import './index.scss'; import { aiResouceStatus, aiResouceType, aiResouceTypeKey } from "../static"; -import { drawSourceById, getMyCodeList, getMyResourceList, takeUpSourceById } from "../../forge/Information/api"; +import { drawSourceById, getAIProjectStatistics, getAIResourceStatistics, getMyCodeList, getMyResourceList, takeUpSourceById } from "../../forge/Information/api"; import { DeleteModal, TakeDownModal } from "../component"; import moment from "moment"; @@ -18,11 +18,43 @@ function MyResources(props) { const [list, setList] = useState([]); const [deleteId, setDeleteId] = useState(); const [downId, setDownId] = useState(); + const [stats, setStats] = useState([]); useEffect(() => { document.title = `我的资源池` }, []) + useEffect(() => { + if (aiZoneId && type) { + if(type === "codes") { + getAIProjectStatistics().then(res => { + if (res && res.data && res.data.code === 200) { + const { likeCount, publishedCount, pendingCount } = res.data.data || {}; + setStats([ + { key: 'likes', label: '累计获得点赞', value: likeCount || 0 }, + { key: 'published', label: '已发布', value: publishedCount || 0 }, + { key: 'reviewing', label: '审核中', value: pendingCount || 0 }, + ]); + } + }) + } + getAIResourceStatistics({ + isMine: true, + resourceCategory: aiResouceTypeKey[type], + zoneId: aiZoneId, + }).then(res => { + if (res && res.data && res.data.code === 200) { + const { likeCount, publishedCount, pendingCount } = res.data.data || {}; + setStats([ + { key: 'likes', label: '累计获得点赞', value: likeCount || 0 }, + { key: 'published', label: '已发布', value: publishedCount || 0 }, + { key: 'reviewing', label: '审核中', value: pendingCount || 0 }, + ]); + } + }) + } + }, [aiZoneId, type]) + useEffect(() => { if(type === "codes" && aiZoneId) { // 获取聚合的代码库列表 @@ -50,13 +82,7 @@ function MyResources(props) { setTotal(res.data.total) } }) - }, [type, aiZoneId, current, reload]) - - const stats = [ - { key: 'likes', label: '累计获得点赞', value: '123' }, - { key: 'published', label: '已发布', value: '17' }, - { key: 'reviewing', label: '审核中', value: '25' }, - ]; + }, [type, aiZoneId, current, reload, roleByZone]) const columns = [ { diff --git a/src/ai/static.js b/src/ai/static.js index 698e3cff..ef04e9cf 100644 --- a/src/ai/static.js +++ b/src/ai/static.js @@ -1,3 +1,5 @@ +import { Fragment } from "react"; + export const aiResouceType = [ { key: "models", name: "模型库", icon: "moxingku", intro: "行业算法模型", desc: "收录多模态原生大模型矩阵,开箱即用灵活调度,全方位支撑企业 AI 业务研发与智能化升级", createIntro: "上传您的模型文件,审核通过后将同步展示在AI广场" }, { key: "datasets", name: "数据集", icon: "shujuji", intro: "高精训练数据", desc: "精心策划的多模态数据集,覆盖 NLP、CV、语音、多模态等核心方向,助力模型快速迭代。", createIntro: "上传您的数据集文件,审核通过后将同步展示在AI广场" }, @@ -36,13 +38,37 @@ export const aiCreateSteps = [ } ]; +// 格式化文件大小 +const formatFileSize = (size) => { + if (!size || size === 0) return ""; + const num = parseFloat(size); + if (isNaN(num)) return ""; + const units = ["B", "KB", "MB", "GB", "TB"]; + let unitIndex = 0; + let fileSize = num; + while (fileSize >= 1024 && unitIndex < units.length - 1) { + fileSize /= 1024; + unitIndex++; + } + const formatted = fileSize.toFixed(2); + // 如果两位小数都是0,则显示整数 + return formatted.endsWith('.00') ? `${parseInt(formatted, 10)}${units[unitIndex]}` : `${formatted}${units[unitIndex]}`; +}; + export const convertToTreeData = (data) => { return data.map(node => { + const isLeaf = !node.children || node.children.length === 0; + let title = node.name; + if (isLeaf && node.size) { + const sizeStr = formatFileSize(node.size); + title = {node.name}{sizeStr}; + } return { key: node.name, - title: node.name, - isLeaf: node.type === 'file', - children: node.children ? convertToTreeData(node.children) : undefined + title, + isLeaf, + size: isLeaf ? node.size : undefined, + children: node.children && node.children.length > 0 ? convertToTreeData(node.children) : undefined }; }); }; diff --git a/src/forge/Information/api.js b/src/forge/Information/api.js index 13045401..86c03339 100644 --- a/src/forge/Information/api.js +++ b/src/forge/Information/api.js @@ -513,7 +513,7 @@ export function takeUpSourceById(id, role="Member"){ // export function drawSourceById(id){ return fetch({ - url: `/zoneFront/skillResource/${id}/withdraw`, + url: `/zoneFront/resource/${id}/withdraw`, method:"PUT" }) } @@ -540,17 +540,17 @@ export function getMyCodeList(params, role="Member"){ }) } -export function getSkillList(params){ +export function getAISourceList(zoneId, params){ return fetch({ - url:`/zone/open/ai-square/skill/list`, + url:`/zone/open/${zoneId}/resource/list`, method: 'get', params }) } -export function getSkillById(id){ +export function getAISourceById(id){ return fetch({ - url:`/zone/open/ai-square/skill/${id}`, + url:`/zone/open/resource/detail/${id}`, method: 'get' }) } @@ -607,4 +607,48 @@ export function uploadFileBySourceJournal(data) { method: 'POST', data: data }) +} +export function addModel(data) { + return fetch({ + url: `/zone/zoneFront/resource/model`, + method: 'POST', + data: data + }) +} +export function editModel(data) { + return fetch({ + url: `/zone/zoneFront/resource/model`, + method: 'PUT', + data: data + }) +} +export function addDataset(data) { + return fetch({ + url: `/zone/zoneFront/resource/dataset`, + method: 'POST', + data: data + }) +} +export function editDataset(data) { + return fetch({ + url: `/zone/zoneFront/resource/dataset`, + method: 'PUT', + data: data + }) +} + +export function getAIResourceStatistics(params){ + return fetch({ + url:`/zone/zoneFront/resourceStatistics`, + method: 'get', + params + }) +} + +export function getAIProjectStatistics(params){ + return fetch({ + url:`/zone/zoneFront/myProjectStatistics`, + method: 'get', + params + }) } \ No newline at end of file