diff --git a/react-ui/mock/skills.ts b/react-ui/mock/skills.ts index 05e0b989..5c820d0b 100644 --- a/react-ui/mock/skills.ts +++ b/react-ui/mock/skills.ts @@ -47,330 +47,20 @@ Skill 是模块化的能力包,用于扩展 Claude 的功能。每个 Skill `; export default defineMock({ - 'GET /api/skill/types/': { + 'GET /api/mmp/skills/types/': { msg: '操作成功', code: 200, data: [ { id: 1, name: '营销', + icon: 'marketing', }, { id: 2, name: '编程与开发', + icon: 'development', }, ], }, - - 'GET /api/skill/': { - 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, - model_type: '高强度,高韧性', - }, - { - 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, - model_type: '高强度,高韧性', - model_tag: '半导体,导体', - }, - { - 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, - model_type: '高强度', - model_tag: '半导体', - }, - { - 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, - }, - }, - - 'GET /api/skill/:id': { - msg: '操作成功', - code: 200, - data: { - id: 109, - name: 'Claude Code Skill', - create_by: 'ceshi', - description: `Claude Skill 是模块化能力包,通过三级加载机制(元数据→指令→资源)高效扩展 Claude 功能。创建流程包含 7 步:理解需求、规划资源、初始化目录、编写 SKILL.md、添加资源、验证打包、迭代改进。核心要素包括:精准的 description 触发条件、精简的 SKILL.md(<5k tokens)、合理的资源组织(scripts/、references/、assets/)、具体使用示例。关键原则:使用祈使句而非第二人称、保持信息渐进式披露、脚本独立可执行、避免内容重复。遵循规范可创建专业、高效、易维护的 Skill。`, - 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: 3, - type: '编程', - content: md, - }, - }, }); diff --git a/react-ui/src/pages/Skill/Info/index.less b/react-ui/src/pages/Skill/Info/index.less index f2156a4e..08f2f8bf 100644 --- a/react-ui/src/pages/Skill/Info/index.less +++ b/react-ui/src/pages/Skill/Info/index.less @@ -95,10 +95,8 @@ :global { .wmde-markdown { - border-radius: 8px; + border-radius: 8px !important; } } } - - } \ No newline at end of file diff --git a/react-ui/src/pages/Skill/Info/index.tsx b/react-ui/src/pages/Skill/Info/index.tsx index 352c29c6..c9867476 100644 --- a/react-ui/src/pages/Skill/Info/index.tsx +++ b/react-ui/src/pages/Skill/Info/index.tsx @@ -1,10 +1,20 @@ import KFIcon from '@/components/KFIcon'; import KFMdEditor from '@/components/KFMdEditor'; +import DownloadSkillModal from '@/pages/Skill/components/DownloadModal'; import { SkillData } from '@/pages/Skill/types'; -import { getSkillInfoReq } from '@/services/skill'; +import { + cancelPraiseSkillReq, + downloadSkillReq, + getSkillInfoReq, + praiseSkillReq, + publishSkillReq, +} from '@/services/skill'; +import { parseJsonText } from '@/utils'; +import { downloadFileWithUrl } from '@/utils/downloadfile'; +import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; -import { useParams } from '@umijs/max'; -import { Button, Flex } from 'antd'; +import { useModel, useParams } from '@umijs/max'; +import { Button, Flex, message } from 'antd'; import classNames from 'classnames'; import { useCallback, useEffect, useState } from 'react'; import styles from './index.less'; @@ -12,6 +22,9 @@ import styles from './index.less'; function SkillInfo() { const { id } = useParams(); const [info, setInfo] = useState(); + const { initialState } = useModel('@@initialState'); + const { currentUser } = initialState || {}; + const isMine = currentUser?.userName === info?.create_by; const getSkillInfo = useCallback(async () => { const [res] = await to(getSkillInfoReq(id as string)); @@ -24,6 +37,61 @@ function SkillInfo() { getSkillInfo(); }, [getSkillInfo]); + // 发布技能 + const handlePublish = useCallback(async () => { + const [res] = await to(publishSkillReq(id as string)); + if (res && res.data) { + if (res.data.is_public) { + message.success('发布成功'); + setInfo((prev) => ({ + ...prev!, + is_public: true, + })); + } else { + message.success('操作成功,等待管理员审核'); + } + } + }, [id]); + + // 处理点赞 + const handlePraise = useCallback(async () => { + const request = info!.praised === true ? cancelPraiseSkillReq : praiseSkillReq; + const [res] = await to(request(id as string)); + if (res && res.data) { + message.success('操作成功'); + setInfo(res.data); + } + }, [id, info]); + + // 处理下载 + const handleDownload = useCallback(async () => { + if (info!.file_tree_json) { + const treeData = parseJsonText(info!.file_tree_json); + const { close } = openAntdModal(DownloadSkillModal, { + treeData, + onOk: () => { + close(); + downloadSkill(info!.file_path); + }, + }); + } else { + downloadSkill(info!.file_path); + } + }, [info]); + + const downloadSkill = async (url: string) => { + if (url) { + const [res] = await to(downloadSkillReq(url)); + if (res && res.data) { + downloadFileWithUrl(res.data); + } + } + }; + + if (!info) { + return null; + } + return (
@@ -37,38 +105,36 @@ function SkillInfo() {
{info?.name || ''}
{info?.type || ''}
- - + )} +
- {info?.prraises_count || 0} + {info?.praises_count || 0}
@@ -76,7 +142,7 @@ function SkillInfo() {
{ function AddSkillModal({ skillTypes, onOk, ...rest }: AddVersionModalProps) { const [uuid] = useState(Date.now()); const [form] = Form.useForm(); - const [sliptUploadRequest, cancelUpload] = useSplitUpload(); + // const [sliptUploadRequest, cancelUpload] = useSplitUpload(); // 上传组件参数 const uploadProps: UploadProps = { @@ -36,10 +35,14 @@ function AddSkillModal({ skillTypes, onOk, ...rest }: AddVersionModalProps) { Authorization: getAccessToken() || '', }, defaultFileList: [], - // beforeUpload: config.beforeUpload, - // accept: config.uploadAccept, - customRequest: sliptUploadRequest, - onRemove: cancelUpload, + action: SkillUploadUrl, + beforeUpload: limitUploadFileType({ + extensions: '.md,.zip', + size: 100, + unit: 'MB', + }), + accept: '.md,.zip', + maxCount: 1, }; // 上传请求 @@ -55,17 +58,10 @@ function AddSkillModal({ skillTypes, onOk, ...rest }: AddVersionModalProps) { const onFinish = (formData: any) => { const fileList: UploadFile[] = formData['fileList'] ?? []; if (validateUploadFiles(fileList)) { - const files = fileList.map((item) => { - const data = item.response?.data ?? {}; - return { - file_name: data.filename, - file_size: data.totalSize, - url: data.location, - }; - }); + const data = fileList[0]?.response?.data ?? {}; const params = { ...omit(formData, 'fileList'), - files, + ...data, }; createDatasetVersion(params); } @@ -108,7 +104,7 @@ function AddSkillModal({ skillTypes, onOk, ...rest }: AddVersionModalProps) { @@ -170,7 +166,7 @@ function AddSkillModal({ skillTypes, onOk, ...rest }: AddVersionModalProps) { ]} > - + diff --git a/react-ui/src/pages/Skill/components/CategoryList/index.less b/react-ui/src/pages/Skill/components/CategoryList/index.less index 71f683f5..ea7ce213 100644 --- a/react-ui/src/pages/Skill/components/CategoryList/index.less +++ b/react-ui/src/pages/Skill/components/CategoryList/index.less @@ -1,15 +1,15 @@ .category-list { display: flex; flex-direction: column; - width: 350px; + width: 280px; height: 100%; padding-bottom: 72px; - @media screen and (max-width: 1600px) { - & { - width: 240px; - } - } + // @media screen and (max-width: 1600px) { + // & { + // width: 260px; + // } + // } &__search { width: 100%; diff --git a/react-ui/src/pages/Skill/components/DownloadModal/index.less b/react-ui/src/pages/Skill/components/DownloadModal/index.less new file mode 100644 index 00000000..0e1142cc --- /dev/null +++ b/react-ui/src/pages/Skill/components/DownloadModal/index.less @@ -0,0 +1,7 @@ +.download-modal { + :global { + .ant-tree-directory { + background-color: transparent; + } + } +} \ No newline at end of file diff --git a/react-ui/src/pages/Skill/components/DownloadModal/index.tsx b/react-ui/src/pages/Skill/components/DownloadModal/index.tsx new file mode 100644 index 00000000..bc7b961c --- /dev/null +++ b/react-ui/src/pages/Skill/components/DownloadModal/index.tsx @@ -0,0 +1,39 @@ +import KFModal from '@/components/KFModal'; +import { Tree, type ModalProps, type TreeDataNode } from 'antd'; +import styles from './index.less'; + +const { DirectoryTree } = Tree; + +interface DownloadTreeDataNode extends TreeDataNode { + name: string; + fileSize?: string; +} + +interface AddVersionModalProps extends Omit { + treeData: DownloadTreeDataNode[]; + onOk: () => void; +} + +function DownloadSkillModal({ treeData, onOk, ...rest }: AddVersionModalProps) { + return ( + + { + const label = record.name + (record.fileSize ? `(${record.fileSize})` : ''); + return {label}; + }} + /> + + ); +} + +export default DownloadSkillModal; diff --git a/react-ui/src/pages/Skill/components/ResourceItem/index.tsx b/react-ui/src/pages/Skill/components/ResourceItem/index.tsx index c663f37c..9810a194 100644 --- a/react-ui/src/pages/Skill/components/ResourceItem/index.tsx +++ b/react-ui/src/pages/Skill/components/ResourceItem/index.tsx @@ -12,7 +12,7 @@ type ResourceItemProps = { }; function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) { - const timeAgo = `${ago(item.full_last_update_time)}更新`; + const timeAgo = `${ago(item.update_time)}更新`; const create_by = item.create_by ?? ''; return ( diff --git a/react-ui/src/pages/Skill/types.ts b/react-ui/src/pages/Skill/types.ts index f9a2d639..55019183 100644 --- a/react-ui/src/pages/Skill/types.ts +++ b/react-ui/src/pages/Skill/types.ts @@ -9,11 +9,15 @@ export type SkillData = { max_version: string; preview_pic: string; create_by: string; - full_last_update_time: string; + update_time: string; praises_count: number; description: string; type: string; - content: string; + md_content: string; prraised: boolean; prraises_count: number; + is_public: boolean; + praised: boolean; + file_tree_json: string; + file_path: string; }; diff --git a/react-ui/src/services/skill/index.ts b/react-ui/src/services/skill/index.ts index 7bf3d090..d8d05d06 100644 --- a/react-ui/src/services/skill/index.ts +++ b/react-ui/src/services/skill/index.ts @@ -7,7 +7,7 @@ import { request } from '@umijs/max'; // 分页查询技能列表 export function getSkillListReq(params: any) { - return request(`/api/skill/`, { + return request(`/api/mmp/skills/`, { method: 'GET', params, }); @@ -15,7 +15,7 @@ export function getSkillListReq(params: any) { // 分页查询技能类型列表 export function getSkillTypesReq(params: any) { - return request(`/api/skill/types/`, { + return request(`/api/mmp/skills/types/`, { method: 'GET', params, }); @@ -23,14 +23,14 @@ export function getSkillTypesReq(params: any) { // 查询技能详情 export function getSkillInfoReq(id: number | string) { - return request(`/api/skill/${id}`, { + return request(`/api/mmp/skills/${id}`, { method: 'GET', }); } // 创建技能 export function createSkillReq(data: any) { - return request(`/api/skill/`, { + return request(`/api/mmp/skills/`, { method: 'POST', data, }); @@ -38,7 +38,7 @@ export function createSkillReq(data: any) { // 更新技能 export function updateSkillReq(data: any) { - return request(`/api/skill/`, { + return request(`/api/mmp/skills/`, { method: 'PUT', data, }); @@ -46,7 +46,41 @@ export function updateSkillReq(data: any) { // 删除技能 export function deleteSkillReq(id: number) { - return request(`/api/skill/${id}`, { + return request(`/api/mmp/skills/${id}`, { method: 'DELETE', }); } + +// 发布技能 +export function publishSkillReq(id: number | string) { + return request(`/api/mmp/skills/publish/${id}`, { + method: 'PUT', + }); +} + +// 点赞技能 +export function praiseSkillReq(id: number | string) { + return request(`/api/mmp/skills/praise/${id}`, { + method: 'PUT', + }); +} + +// 取消点赞技能 +export function cancelPraiseSkillReq(id: number | string) { + return request(`/api/mmp/skills/unpraise/${id}`, { + method: 'PUT', + }); +} + +// 下载技能 +export function downloadSkillReq(url: string) { + return request(`/api/mmp/skills/download/`, { + method: 'GET', + params: { + url, + }, + }); +} + +// 上传地址 +export const SkillUploadUrl = '/api/mmp/skills/upload';