import IconFont from '@/components/icon-font'; import MarkdownViewer from '@/components/markdown-viewer'; import SimpleOverlay from '@/components/simple-overlay'; import ThemeTag from '@/components/tags-wrapper/theme-tag'; import useRequestToken from '@/hooks/use-request-token'; import { DownOutlined, FileTextOutlined, RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Empty, Spin, Tooltip } from 'antd'; import { some } from 'lodash'; import 'overlayscrollbars/overlayscrollbars.css'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import 'simplebar-react/dist/simplebar.min.css'; import styled from 'styled-components'; import { downloadModelFile, queryHuggingfaceModelDetail, queryModelScopeModelDetail } from '../apis'; import { modelSourceMap } from '../config'; import '../style/model-card.less'; import TitleWrapper from './title-wrapper'; const MkdTitle = styled.span` cursor: pointer; background-color: var(--ant-color-fill-tertiary); display: flex; justify-content: space-between; align-items: center; padding: 10px; height: 36px; `; const MarkDownTitle: React.FC<{ collapsed: boolean; loading: boolean; onCollapse: () => void; }> = ({ collapsed, loading, onCollapse }) => { const intl = useIntl(); return ( README.md {collapsed ? ( ) : loading ? ( ) : ( )} ); }; const ModelCard: React.FC<{ onCollapse: (flag: boolean) => void; setIsGGUF: (flag: boolean) => void; selectedModel: any; collapsed: boolean; loadingModel?: boolean; modelSource: string; }> = (props) => { const { onCollapse, setIsGGUF, collapsed, modelSource } = props; const intl = useIntl(); const requestSource = useRequestToken(); const [modelData, setModelData] = useState(null); const [readmeText, setReadmeText] = useState(null); const requestToken = useRef(null); const axiosTokenRef = useRef(null); const loadConfigTokenRef = useRef(null); const loadConfigJsonTokenRef = useRef(null); const [isGGUFModel, setIsGGUFModel] = useState(false); const [loading, setLoading] = useState(false); const modelTags = useMemo(() => { if (modelSource === modelSourceMap.huggingface_value) { return modelData?.pipeline_tag ? [modelData?.pipeline_tag] : []; } if (modelSource === modelSourceMap.modelscope_value) { const tasks = modelData?.Tasks || []; return tasks.map((task: any) => task?.Name)?.filter((val: string) => val); } return []; }, [modelSource, modelData]); const modelType = useMemo(() => { if (modelSource === modelSourceMap.huggingface_value) { return modelData?.config?.model_type || modelData?.ModelType?.[0]; } if (modelSource === modelSourceMap.modelscope_value) { return modelData?.ModelType?.[0]; } }, [modelData, modelSource]); const loadFile = useCallback(async (repo: string, sha: string) => { try { axiosTokenRef.current?.abort?.(); axiosTokenRef.current = new AbortController(); const res = await downloadModelFile( { repo, revision: sha, path: 'README.md' }, { signal: axiosTokenRef.current.signal } ); return res || ''; } catch (error) { return ''; } }, []); const handleOnCollapse = (readmeText: any) => { if (!readmeText) { onCollapse(false); } }; const removeMetadata = useCallback((str: string) => { let indexes = []; let index = str.indexOf('---'); while (index !== -1) { indexes.push(index); if (indexes.length >= 2) { break; } index = str.indexOf('---', index + 1); } if (indexes.length >= 2) { return str.slice(indexes[1] + 3); } return str; }, []); // huggingface model card data const getHuggingfaceModelDetail = async () => { try { const [modelcard, readme] = await Promise.all([ queryHuggingfaceModelDetail( { repo: props.selectedModel.name }, { token: requestToken.current.token } ), loadFile(props.selectedModel.name, 'main') ]); setModelData(modelcard); // remove the meta data from readme const newReadme = removeMetadata(readme); setReadmeText(newReadme); handleOnCollapse(newReadme); const isGGUF = modelcard.tags?.includes('gguf'); setIsGGUF(isGGUF); setIsGGUFModel(isGGUF); } catch (error) { setModelData(null); setReadmeText(null); handleOnCollapse(null); setIsGGUF(false); setIsGGUFModel(false); } }; const getModelScopeModelDetail = async () => { try { const data = await queryModelScopeModelDetail( { name: props.selectedModel.name }, { token: requestToken.current.token } ); setModelData({ ...data?.Data, name: `${data.Data?.Path}/${data.Data?.Name}` }); setReadmeText(data?.Data?.ReadMeContent); handleOnCollapse(data?.Data?.ReadMeContent); const isGGUF = some( data?.Data?.Tags, (tag: string) => tag?.indexOf('gguf') > -1 ); setIsGGUF(isGGUF); setIsGGUFModel(isGGUF); } catch (error) { setModelData(null); setReadmeText(null); handleOnCollapse(null); setIsGGUF(false); setIsGGUFModel(false); } }; const getModelCardData = async () => { if (!props.selectedModel?.name) { setModelData(null); setReadmeText(null); handleOnCollapse(null); return; } requestToken.current?.cancel?.(); requestToken.current = requestSource(); setLoading(true); if (modelSource === modelSourceMap.huggingface_value) { await getHuggingfaceModelDetail(); } else if (modelSource === modelSourceMap.modelscope_value) { await getModelScopeModelDetail(); } setLoading(false); }; const handleCollapse = useCallback(() => { onCollapse(!collapsed); }, [collapsed]); const generateModelLink = () => { const name = modelData?.id || modelData?.name; if (!name) { return null; } if (modelSource === modelSourceMap.huggingface_value) { return ( ); } if (modelSource === modelSourceMap.modelscope_value) { return ( ); } return null; }; const generateModeScopeImgLink = useCallback( (imgSrc: string) => { if (!imgSrc) { return ''; } if (modelSource === modelSourceMap.modelscope_value) { return `https://modelscope.cn/api/v1/models/${modelData?.name}/repo?Revision=${modelData?.Revision}&View=true&FilePath=${imgSrc}`; } if (modelSource === modelSourceMap.huggingface_value) { return `https://huggingface.co/${modelData?.id}/resolve/main/${imgSrc}`; } return ''; }, [modelSource, modelData?.name, modelData?.id, modelData?.Revision] ); useEffect(() => { console.log('ModelCard selectedModel changed', props.selectedModel); if (!props.selectedModel.name) return; getModelCardData(); setIsGGUFModel(props.selectedModel.isGGUF); setModelData({ id: props.selectedModel.name, name: props.selectedModel.name, isGGUF: props.selectedModel.isGGUF }); }, [props.selectedModel?.name, props.selectedModel?.isGGUF]); useEffect(() => { return () => { requestToken.current?.cancel?.(); axiosTokenRef.current?.abort?.(); loadConfigTokenRef.current?.abort?.(); loadConfigJsonTokenRef.current?.abort?.(); }; }, []); return ( <> {modelData?.id || modelData?.name} {generateModelLink()} {modelData ? ( {modelType && ( {intl.formatMessage({ id: 'models.architecture' })}: {modelType} )} {isGGUFModel && ( GGUF )} {!!modelTags.length && modelTags.map((tag: string, index: number) => { return ( {tag} ); })} {readmeText && isGGUFModel && ( )} ) : ( <> {!loading && ( )} > )} {!isGGUFModel && ( {readmeText && ( <> README.md > )} )} > ); }; export default React.memo(ModelCard);