feat: clean markdown content

main
jialin 10 months ago
parent 830ee45161
commit a6825f7ee3

@ -38,6 +38,7 @@
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
"dompurify": "^3.2.6",
"driver.js": "^1.3.1",
"echarts": "^5.5.1",
"epubjs": "^0.3.93",

@ -75,6 +75,9 @@ dependencies:
dayjs:
specifier: ^1.11.11
version: 1.11.11
dompurify:
specifier: ^3.2.6
version: 3.2.6
driver.js:
specifier: ^1.3.1
version: 1.3.1
@ -5377,6 +5380,12 @@ packages:
/@types/stylis@4.2.6:
resolution: {integrity: sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw==, tarball: https://registry.npmjs.org/@types/stylis/-/stylis-4.2.6.tgz}
/@types/trusted-types@2.0.7:
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz}
requiresBuild: true
dev: false
optional: true
/@types/unist@2.0.10:
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==, tarball: https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz}
dev: false
@ -9395,6 +9404,12 @@ packages:
domelementtype: 2.3.0
dev: false
/dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==, tarball: https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz}
optionalDependencies:
'@types/trusted-types': 2.0.7
dev: false
/domutils@1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==, tarball: https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz}
dependencies:

@ -1,6 +1,7 @@
import { EyeOutlined } from '@ant-design/icons';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { Checkbox, Image, Typography } from 'antd';
import DOMPurify from 'dompurify';
import { unescape } from 'lodash';
import { TokensList, marked } from 'marked';
import React, { Fragment, useCallback, useEffect } from 'react';
@ -16,6 +17,15 @@ interface MarkdownViewerProps {
generateImgLink?: (src: string) => string;
}
const dompurifyOptions = {
FORBID_TAGS: ['meta', 'style', 'script', 'iframe'],
FORBID_ATTR: ['onerror', 'onclick', 'onload', 'style']
};
const cleanHtml = (html: string): string => {
return DOMPurify.sanitize(html, dompurifyOptions);
};
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
content,
generateImgLink,
@ -68,11 +78,14 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
const renderItem = useCallback(
(token: any, render: any) => {
if (token.type === 'script' || token.type === 'style') {
return null;
}
if (!reDefineTypes.includes(token.type)) {
return (
<span
dangerouslySetInnerHTML={{
__html: marked.parser([token], { renderer })
__html: cleanHtml(marked.parser([token], { renderer }))
}}
></span>
);
@ -89,7 +102,13 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
}
if (token.type === 'html') {
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
htmlstr = (
<div
dangerouslySetInnerHTML={{
__html: cleanHtml(token.text)
}}
/>
);
}
if (token.type === 'list') {
htmlstr = token.order ? (
@ -158,7 +177,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
if (token.type === 'link') {
htmlstr = (
<Link
href={token.href}
href={sanitizeUrl(token.href || '')}
title={token.title || ''}
target="_blank"
rel="noopener noreferrer"

@ -242,13 +242,6 @@ const AddModal: FC<AddModalProps> = (props) => {
);
const handleSelectModelFile = async (item: any, requestModelId: number) => {
console.log(
'handleSelectModelFile:',
item,
requestModelId,
getRequestId(),
evaluateStateRef.current
);
if (requestModelId !== getRequestId()) {
return;
}
@ -261,8 +254,6 @@ const AddModal: FC<AddModalProps> = (props) => {
categories: getCategory(item)
});
console.log('handleSelectModelFile>>>>>>>>>>>>', item);
// evaluate the form data when select a model file
if (item.fakeName) {
onSelectFile(item, modelInfo);
@ -273,7 +264,19 @@ const AddModal: FC<AddModalProps> = (props) => {
cancelEvaluate();
modelFileRef.current?.cancelRequest();
};
const handleOnSelectModel = async (item: any) => {
const generateNameValue = (
item: any,
modelName: string,
manual?: boolean
) => {
if (item.name === currentSelectedModel.current.name) {
return manual ? modelName : form.current?.getFieldValue?.('name');
}
return modelName;
};
const handleOnSelectModel = async (item: any, manual?: boolean) => {
// If the item is empty or the same as the selected model, do nothing
handleCancelFiles();
@ -324,7 +327,7 @@ const AddModal: FC<AddModalProps> = (props) => {
);
};
const handleOnSelectModelAfterEvaluate = (item: any) => {
const handleOnSelectModelAfterEvaluate = (item: any, manual?: boolean) => {
console.log(
'handleOnSelectModelAfterEvaluate:',
item.name,
@ -352,9 +355,8 @@ const AddModal: FC<AddModalProps> = (props) => {
handleShowCompatibleAlert(item.evaluateResult);
form.current?.setFieldsValue?.({
...getDefaultSpec(item),
...(item.name === currentSelectedModel.current.name
? _.omit(modelInfo, ['name'])
: modelInfo),
...modelInfo,
name: generateNameValue(item, modelInfo.name, manual),
categories: getCategory(item)
});
}
@ -521,7 +523,6 @@ const AddModal: FC<AddModalProps> = (props) => {
handleOnSelectModelAfterEvaluate
}
displayEvaluateStatus={displayEvaluateStatus}
unlockWarningStatus={unlockWarningStatus}
gpuOptions={props.gpuOptions}
></SearchModel>
</ColumnWrapper>

@ -9,7 +9,6 @@ import {
RightOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { useBoolean } from 'ahooks';
import { Button, Empty, Spin, Tooltip } from 'antd';
import { some } from 'lodash';
import 'overlayscrollbars/overlayscrollbars.css';
@ -73,7 +72,6 @@ const ModelCard: React.FC<{
loadingModel?: boolean;
modelSource: string;
}> = (props) => {
const [hideMd, { toggle }] = useBoolean();
const { onCollapse, setIsGGUF, collapsed, modelSource } = props;
const intl = useIntl();
const requestSource = useRequestToken();
@ -324,7 +322,7 @@ const ModelCard: React.FC<{
<div className="card-wrapper">
{modelData ? (
<div className="model-card-wrap">
<div className="flex-center">
<div className="flex-center flex-wrap gap-8">
{modelType && (
<ThemeTag className="tag-item" color="gold" opacity={0.65}>
<span className="m-r-5">
@ -399,20 +397,9 @@ const ModelCard: React.FC<{
{readmeText && (
<>
<TitleWrapper>
<div className="flex-center gap-8">
<span className="title">README.md</span>
{/* <Button
onClick={toggle}
size="small"
type="text"
icon={hideMd ? <EyeOutlined /> : <EyeInvisibleOutlined />}
></Button> */}
</div>
<span className="title">README.md</span>
</TitleWrapper>
<div
className="card-wrapper"
style={{ width: hideMd ? 0 : 'auto', overflow: 'hidden' }}
>
<div className="card-wrapper">
<MarkdownViewer
generateImgLink={generateModeScopeImgLink}
content={readmeText}
@ -428,4 +415,4 @@ const ModelCard: React.FC<{
);
};
export default React.memo(ModelCard);
export default ModelCard;

@ -47,9 +47,8 @@ interface SearchInputProps {
gpuOptions?: any[];
setLoadingModel?: (flag: boolean) => void;
onSourceChange?: (source: string) => void;
onSelectModel: (model: any) => void;
onSelectModelAfterEvaluate: (model: any) => void;
unlockWarningStatus?: () => void;
onSelectModel: (model: any, manul?: boolean) => void;
onSelectModelAfterEvaluate: (model: any, manual?: boolean) => void;
displayEvaluateStatus?: (
data: MessageStatus,
options?: WarningStausOptions
@ -66,8 +65,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
setLoadingModel,
onSelectModel,
onSelectModelAfterEvaluate,
displayEvaluateStatus,
unlockWarningStatus
displayEvaluateStatus
} = props;
const [dataSource, setDataSource] = useState<{
@ -142,12 +140,12 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
return isGGUF || isGGUFFromMs;
};
const handleOnSelectModel = (model: any) => {
const handleOnSelectModel = (model: any, manual?: boolean) => {
const item = model || {};
if (item.evaluated && !item.isGGUF) {
onSelectModelAfterEvaluate(item);
onSelectModelAfterEvaluate(item, manual);
} else {
onSelectModel(item);
onSelectModel(item, manual);
}
setCurrent(item.id);
currentRef.current = item.id;
@ -497,6 +495,10 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}
};
const handleSelectModelManually = (model: any) => {
handleOnSelectModel(model, true);
};
const renderGGUFTips = useMemo(() => {
return (
<Tooltip
@ -596,7 +598,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
current={current}
source={modelSource}
isEvaluating={isEvaluating}
onSelect={handleOnSelectModel}
onSelect={handleSelectModelManually}
></SearchResult>
</div>
);

@ -18,6 +18,7 @@
border-radius: 4px;
font-size: 12px;
height: 20px;
margin-right: 0;
}
.btn {

Loading…
Cancel
Save