fix(style): model readme img render

main
jialin 1 year ago
parent 5f77a0082c
commit ea25291d75

@ -40,20 +40,24 @@
line-height: 2;
}
a * {
color: inherit;
}
table {
width: 100%;
th {
text-align: left;
font-weight: var(--font-weight-bold);
line-height: 2;
line-height: 1.5;
padding-inline: 6px;
border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
}
td {
line-height: 2;
line-height: 1.5;
padding-inline: 6px;
border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
@ -70,6 +74,23 @@
}
}
img {
max-width: 100%;
height: auto;
}
video {
max-width: 100%;
height: auto;
border-radius: var(--border-radius-base);
}
audio {
max-width: 100%;
height: auto;
border-radius: var(--border-radius-base);
}
.item-token {
color: inherit;
font-size: inherit;

@ -2,7 +2,7 @@ import { EyeOutlined } from '@ant-design/icons';
import { Image, Typography } from 'antd';
import { unescape } from 'lodash';
import { TokensList, marked } from 'marked';
import React, { Fragment, useCallback } from 'react';
import React, { Fragment, useCallback, useEffect } from 'react';
import HighlightCode from '../highlight-code';
import './index.less';
@ -12,10 +12,12 @@ interface MarkdownViewerProps {
content: string;
height?: string;
theme?: 'light' | 'dark';
generateImgLink?: (src: string) => string;
}
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
content,
generateImgLink,
height = 'auto',
theme = 'light'
}) => {
@ -39,109 +41,138 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
'escape'
];
const renderItem = useCallback((token: any, render: any) => {
if (!reDefineTypes.includes(token.type)) {
console.log('token======66==', token.type, token);
return (
<span
dangerouslySetInnerHTML={{
__html: marked.parser([token], { renderer })
}}
></span>
);
}
let htmlstr: any = null;
let child: any = null;
if (token.tokens?.length) {
child = render?.(token.tokens as TokensList, render);
}
const text = child ? child : unescape(token.text);
renderer.link = ({ href, title, text }) => {
return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
};
if (token.type === 'escape') {
htmlstr = text;
}
const isValidURL = useCallback((url: string) => {
const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/;
if (token.type === 'html') {
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
}
if (token.type === 'list') {
htmlstr = token.order ? (
<ol>{render?.(token.items, render)}</ol>
) : (
<ul>{render?.(token.items, render)}</ul>
);
}
return pattern.test(url);
}, []);
if (token.type === 'list_item') {
htmlstr = <li>{text}</li>;
}
const renderItem = useCallback(
(token: any, render: any) => {
if (!reDefineTypes.includes(token.type)) {
return (
<span
dangerouslySetInnerHTML={{
__html: marked.parser([token], { renderer })
}}
></span>
);
}
let htmlstr: any = null;
let child: any = null;
if (token.tokens?.length) {
child = render?.(token.tokens as TokensList, render);
}
const text = child ? child : unescape(token.text);
if (token.type === 'br') {
htmlstr = <br />;
}
if (token.type === 'escape') {
htmlstr = text;
}
if (token.type === 'em') {
htmlstr = <em>{text}</em>;
}
if (token.type === 'html') {
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
}
if (token.type === 'list') {
htmlstr = token.order ? (
<ol>{render?.(token.items, render)}</ol>
) : (
<ul>{render?.(token.items, render)}</ul>
);
}
if (token.type === 'image') {
htmlstr = (
<Image
src={token.href}
alt={token.text}
preview={{
mask: <EyeOutlined />
}}
/>
);
}
if (token.type === 'text') {
htmlstr = text;
}
if (token.type === 'codespan') {
htmlstr = <Text code>{text}</Text>;
}
if (token.type === 'strong') {
htmlstr = <Text strong>{text}</Text>;
}
if (token.type === 'heading') {
htmlstr = <Typography.Title level={4}>{text}</Typography.Title>;
}
if (token.type === 'paragraph') {
htmlstr = <Paragraph> {text}</Paragraph>;
}
if (token.type === 'code') {
htmlstr = (
<HighlightCode theme={theme} code={token.text} lang={token.lang} />
);
}
if (token.type === 'link') {
htmlstr = (
<Link
href={token.href}
title={token.title || ''}
target="_blank"
rel="noopener noreferrer"
>
{text}
</Link>
);
}
if (token.type === 'hr') {
htmlstr = <hr className="hr" />;
}
if (token.type === 'list_item') {
htmlstr = <li>{text}</li>;
}
return htmlstr;
}, []);
if (token.type === 'br') {
htmlstr = <br />;
}
if (token.type === 'em') {
htmlstr = <em>{text}</em>;
}
if (token.type === 'image') {
let href = token.href;
if (!isValidURL(token.href)) {
href = generateImgLink ? generateImgLink(token.href) : token.href;
}
htmlstr = (
<Image
src={href}
alt={token.text}
preview={{
mask: <EyeOutlined />
}}
/>
);
}
if (token.type === 'text') {
htmlstr = text;
}
if (token.type === 'codespan') {
htmlstr = <Text code>{text}</Text>;
}
if (token.type === 'strong') {
htmlstr = <Text strong>{text}</Text>;
}
if (token.type === 'heading') {
htmlstr = <Typography.Title level={4}>{text}</Typography.Title>;
}
if (token.type === 'paragraph') {
htmlstr = <Paragraph> {text}</Paragraph>;
}
if (token.type === 'code') {
htmlstr = (
<HighlightCode theme={theme} code={token.text} lang={token.lang} />
);
}
if (token.type === 'link') {
htmlstr = (
<Link
href={token.href}
title={token.title || ''}
target="_blank"
rel="noopener noreferrer"
>
{text}
</Link>
);
}
if (token.type === 'hr') {
htmlstr = <hr className="hr" />;
}
return htmlstr;
},
[generateImgLink]
);
const renderTokens = (tokens: TokensList): any => {
return tokens?.map((token: any, index: number) => {
return <Fragment key={index}>{renderItem(token, renderTokens)}</Fragment>;
});
};
useEffect(() => {
if (!content) {
return;
}
const imgs = document.querySelectorAll('.markdown-viewer img');
imgs.forEach((img) => {
const src = img.getAttribute('src');
if (src && !isValidURL(src)) {
img.setAttribute('src', generateImgLink ? generateImgLink(src) : src);
}
});
}, [content, generateImgLink]);
return (
<div
style={{ height, overflow: 'auto' }}
style={{ height }}
className="markdown-viewer custom-scrollbar-horizontal"
>
{renderTokens(tokens)}

@ -548,6 +548,10 @@ body {
color: var(--ant-color-error) !important;
}
.ant-image .ant-image-mask {
border-radius: var(--border-radius-base);
}
.ant-message-notice-wrapper {
.ant-message-notice-error {
.ant-message-notice-content {

@ -197,12 +197,12 @@ export async function queryModelScopeModelDetail(
}
export async function queryModelScopeModelFiles(
params: { name: string },
params: { name: string; revision: string },
options?: any
) {
const res = await fetch(
`${MODE_SCOPE_MODEL_FIELS_API}${params.name}/repo/files?${qs.stringify({
Revision: 'master',
Revision: params.revision,
Recursive: true,
Root: ''
})}`,

@ -1,3 +1,4 @@
import AutoTooltip from '@/components/auto-tooltip';
import { convertFileSize } from '@/utils';
import { useIntl } from '@umijs/max';
import _ from 'lodash';
@ -12,12 +13,12 @@ const GPUCard: React.FC<{
const intl = useIntl();
return (
<div className="gpu-card">
<div className="header">
<div className="header" style={{ width: '100%' }}>
{header ?? (
<>
<AutoTooltip ghost>
{data.label}({data.worker_name})[
{intl.formatMessage({ id: 'resources.table.index' })}:{data.index}]
</>
</AutoTooltip>
)}
</div>
<div className="info">

@ -153,7 +153,8 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
try {
const data = await queryModelScopeModelFiles(
{
name: props.selectedModel.name || ''
name: props.selectedModel.name || '',
revision: props.selectedModel.revision || 'master'
},
{
signal: axiosTokenRef.current.signal

@ -150,7 +150,10 @@ const ModelCard: React.FC<{
target="_blank"
href={`https://huggingface.co/${modelData?.id}`}
>
<IconFont type="icon-external-link"></IconFont>
<IconFont
type="icon-external-link"
className="font-size-14"
></IconFont>
</Button>
</Tooltip>
);
@ -165,7 +168,10 @@ const ModelCard: React.FC<{
target="_blank"
href={`https://modelscope.cn/models/${modelData?.name}`}
>
<IconFont type="icon-external-link"></IconFont>
<IconFont
type="icon-external-link"
className="font-size-14"
></IconFont>
</Button>
</Tooltip>
);
@ -173,6 +179,16 @@ const ModelCard: React.FC<{
return null;
};
const generateModeScopeImgLink = useCallback(
(imgSrc: string) => {
if (!imgSrc) {
return '';
}
return `https://modelscope.cn/api/v1/models/${modelData?.name}/repo?Revision=${modelData?.Revision}&View=true&FilePath=${imgSrc}`;
},
[modelData]
);
useEffect(() => {
getModelCardData();
}, [props.selectedModel.name]);
@ -240,6 +256,11 @@ const ModelCard: React.FC<{
}}
>
<MarkdownViewer
generateImgLink={
modelSource === modelSourceMap.modelscope_value
? generateModeScopeImgLink
: undefined
}
content={readmeText}
theme="light"
></MarkdownViewer>
@ -262,6 +283,11 @@ const ModelCard: React.FC<{
<div className="card-wrapper">
<Spin spinning={loading}>
<MarkdownViewer
generateImgLink={
modelSource === modelSourceMap.modelscope_value
? generateModeScopeImgLink
: undefined
}
content={readmeText}
theme="light"
></MarkdownViewer>

@ -118,6 +118,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
likes: item.Stars,
value: item.Name,
label: item.Name,
revision: item.Revision,
task: item.Tasks?.map((sItem: any) => sItem.Name).join(',')
};
});

@ -321,7 +321,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
});
}
return list;
}, [modelList]);
}, [modelList, intl]);
useEffect(() => {
console.log('globalParams:', globalParams.model, globalParams);

@ -177,11 +177,7 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
}
]}
>
<SealSelect
showSearch
options={modelList}
label={intl.formatMessage({ id: 'playground.model' })}
></SealSelect>
<SealSelect showSearch options={modelList}></SealSelect>
</Form.Item>
</>
)}

Loading…
Cancel
Save