feat: clean markdown content
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"driver.js": "^1.3.1",
|
"driver.js": "^1.3.1",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"epubjs": "^0.3.93",
|
"epubjs": "^0.3.93",
|
||||||
|
|||||||
Generated
+15
@@ -75,6 +75,9 @@ dependencies:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.11
|
specifier: ^1.11.11
|
||||||
version: 1.11.11
|
version: 1.11.11
|
||||||
|
dompurify:
|
||||||
|
specifier: ^3.2.6
|
||||||
|
version: 3.2.6
|
||||||
driver.js:
|
driver.js:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1
|
version: 1.3.1
|
||||||
@@ -5377,6 +5380,12 @@ packages:
|
|||||||
/@types/stylis@4.2.6:
|
/@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}
|
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:
|
/@types/unist@2.0.10:
|
||||||
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==, tarball: https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz}
|
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==, tarball: https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -9395,6 +9404,12 @@ packages:
|
|||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
dev: false
|
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:
|
/domutils@1.7.0:
|
||||||
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==, tarball: https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz}
|
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==, tarball: https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EyeOutlined } from '@ant-design/icons';
|
import { EyeOutlined } from '@ant-design/icons';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
import { Checkbox, Image, Typography } from 'antd';
|
import { Checkbox, Image, Typography } from 'antd';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
import { unescape } from 'lodash';
|
import { unescape } from 'lodash';
|
||||||
import { TokensList, marked } from 'marked';
|
import { TokensList, marked } from 'marked';
|
||||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
@@ -16,6 +17,15 @@ interface MarkdownViewerProps {
|
|||||||
generateImgLink?: (src: string) => string;
|
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> = ({
|
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
||||||
content,
|
content,
|
||||||
generateImgLink,
|
generateImgLink,
|
||||||
@@ -68,11 +78,14 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(token: any, render: any) => {
|
(token: any, render: any) => {
|
||||||
|
if (token.type === 'script' || token.type === 'style') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (!reDefineTypes.includes(token.type)) {
|
if (!reDefineTypes.includes(token.type)) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: marked.parser([token], { renderer })
|
__html: cleanHtml(marked.parser([token], { renderer }))
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
);
|
);
|
||||||
@@ -89,7 +102,13 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.type === 'html') {
|
if (token.type === 'html') {
|
||||||
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
|
htmlstr = (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: cleanHtml(token.text)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (token.type === 'list') {
|
if (token.type === 'list') {
|
||||||
htmlstr = token.order ? (
|
htmlstr = token.order ? (
|
||||||
@@ -158,7 +177,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
if (token.type === 'link') {
|
if (token.type === 'link') {
|
||||||
htmlstr = (
|
htmlstr = (
|
||||||
<Link
|
<Link
|
||||||
href={token.href}
|
href={sanitizeUrl(token.href || '')}
|
||||||
title={token.title || ''}
|
title={token.title || ''}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
|||||||
@@ -242,13 +242,6 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectModelFile = async (item: any, requestModelId: number) => {
|
const handleSelectModelFile = async (item: any, requestModelId: number) => {
|
||||||
console.log(
|
|
||||||
'handleSelectModelFile:',
|
|
||||||
item,
|
|
||||||
requestModelId,
|
|
||||||
getRequestId(),
|
|
||||||
evaluateStateRef.current
|
|
||||||
);
|
|
||||||
if (requestModelId !== getRequestId()) {
|
if (requestModelId !== getRequestId()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -261,8 +254,6 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
categories: getCategory(item)
|
categories: getCategory(item)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('handleSelectModelFile>>>>>>>>>>>>', item);
|
|
||||||
|
|
||||||
// evaluate the form data when select a model file
|
// evaluate the form data when select a model file
|
||||||
if (item.fakeName) {
|
if (item.fakeName) {
|
||||||
onSelectFile(item, modelInfo);
|
onSelectFile(item, modelInfo);
|
||||||
@@ -273,7 +264,19 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
cancelEvaluate();
|
cancelEvaluate();
|
||||||
modelFileRef.current?.cancelRequest();
|
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
|
// If the item is empty or the same as the selected model, do nothing
|
||||||
|
|
||||||
handleCancelFiles();
|
handleCancelFiles();
|
||||||
@@ -324,7 +327,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSelectModelAfterEvaluate = (item: any) => {
|
const handleOnSelectModelAfterEvaluate = (item: any, manual?: boolean) => {
|
||||||
console.log(
|
console.log(
|
||||||
'handleOnSelectModelAfterEvaluate:',
|
'handleOnSelectModelAfterEvaluate:',
|
||||||
item.name,
|
item.name,
|
||||||
@@ -352,9 +355,8 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
handleShowCompatibleAlert(item.evaluateResult);
|
handleShowCompatibleAlert(item.evaluateResult);
|
||||||
form.current?.setFieldsValue?.({
|
form.current?.setFieldsValue?.({
|
||||||
...getDefaultSpec(item),
|
...getDefaultSpec(item),
|
||||||
...(item.name === currentSelectedModel.current.name
|
...modelInfo,
|
||||||
? _.omit(modelInfo, ['name'])
|
name: generateNameValue(item, modelInfo.name, manual),
|
||||||
: modelInfo),
|
|
||||||
categories: getCategory(item)
|
categories: getCategory(item)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -521,7 +523,6 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
handleOnSelectModelAfterEvaluate
|
handleOnSelectModelAfterEvaluate
|
||||||
}
|
}
|
||||||
displayEvaluateStatus={displayEvaluateStatus}
|
displayEvaluateStatus={displayEvaluateStatus}
|
||||||
unlockWarningStatus={unlockWarningStatus}
|
|
||||||
gpuOptions={props.gpuOptions}
|
gpuOptions={props.gpuOptions}
|
||||||
></SearchModel>
|
></SearchModel>
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
RightOutlined
|
RightOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useBoolean } from 'ahooks';
|
|
||||||
import { Button, Empty, Spin, Tooltip } from 'antd';
|
import { Button, Empty, Spin, Tooltip } from 'antd';
|
||||||
import { some } from 'lodash';
|
import { some } from 'lodash';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
@@ -73,7 +72,6 @@ const ModelCard: React.FC<{
|
|||||||
loadingModel?: boolean;
|
loadingModel?: boolean;
|
||||||
modelSource: string;
|
modelSource: string;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [hideMd, { toggle }] = useBoolean();
|
|
||||||
const { onCollapse, setIsGGUF, collapsed, modelSource } = props;
|
const { onCollapse, setIsGGUF, collapsed, modelSource } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const requestSource = useRequestToken();
|
const requestSource = useRequestToken();
|
||||||
@@ -324,7 +322,7 @@ const ModelCard: React.FC<{
|
|||||||
<div className="card-wrapper">
|
<div className="card-wrapper">
|
||||||
{modelData ? (
|
{modelData ? (
|
||||||
<div className="model-card-wrap">
|
<div className="model-card-wrap">
|
||||||
<div className="flex-center">
|
<div className="flex-center flex-wrap gap-8">
|
||||||
{modelType && (
|
{modelType && (
|
||||||
<ThemeTag className="tag-item" color="gold" opacity={0.65}>
|
<ThemeTag className="tag-item" color="gold" opacity={0.65}>
|
||||||
<span className="m-r-5">
|
<span className="m-r-5">
|
||||||
@@ -399,20 +397,9 @@ const ModelCard: React.FC<{
|
|||||||
{readmeText && (
|
{readmeText && (
|
||||||
<>
|
<>
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<div className="flex-center gap-8">
|
<span className="title">README.md</span>
|
||||||
<span className="title">README.md</span>
|
|
||||||
{/* <Button
|
|
||||||
onClick={toggle}
|
|
||||||
size="small"
|
|
||||||
type="text"
|
|
||||||
icon={hideMd ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
|
||||||
></Button> */}
|
|
||||||
</div>
|
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<div
|
<div className="card-wrapper">
|
||||||
className="card-wrapper"
|
|
||||||
style={{ width: hideMd ? 0 : 'auto', overflow: 'hidden' }}
|
|
||||||
>
|
|
||||||
<MarkdownViewer
|
<MarkdownViewer
|
||||||
generateImgLink={generateModeScopeImgLink}
|
generateImgLink={generateModeScopeImgLink}
|
||||||
content={readmeText}
|
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[];
|
gpuOptions?: any[];
|
||||||
setLoadingModel?: (flag: boolean) => void;
|
setLoadingModel?: (flag: boolean) => void;
|
||||||
onSourceChange?: (source: string) => void;
|
onSourceChange?: (source: string) => void;
|
||||||
onSelectModel: (model: any) => void;
|
onSelectModel: (model: any, manul?: boolean) => void;
|
||||||
onSelectModelAfterEvaluate: (model: any) => void;
|
onSelectModelAfterEvaluate: (model: any, manual?: boolean) => void;
|
||||||
unlockWarningStatus?: () => void;
|
|
||||||
displayEvaluateStatus?: (
|
displayEvaluateStatus?: (
|
||||||
data: MessageStatus,
|
data: MessageStatus,
|
||||||
options?: WarningStausOptions
|
options?: WarningStausOptions
|
||||||
@@ -66,8 +65,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
setLoadingModel,
|
setLoadingModel,
|
||||||
onSelectModel,
|
onSelectModel,
|
||||||
onSelectModelAfterEvaluate,
|
onSelectModelAfterEvaluate,
|
||||||
displayEvaluateStatus,
|
displayEvaluateStatus
|
||||||
unlockWarningStatus
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [dataSource, setDataSource] = useState<{
|
const [dataSource, setDataSource] = useState<{
|
||||||
@@ -142,12 +140,12 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
return isGGUF || isGGUFFromMs;
|
return isGGUF || isGGUFFromMs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSelectModel = (model: any) => {
|
const handleOnSelectModel = (model: any, manual?: boolean) => {
|
||||||
const item = model || {};
|
const item = model || {};
|
||||||
if (item.evaluated && !item.isGGUF) {
|
if (item.evaluated && !item.isGGUF) {
|
||||||
onSelectModelAfterEvaluate(item);
|
onSelectModelAfterEvaluate(item, manual);
|
||||||
} else {
|
} else {
|
||||||
onSelectModel(item);
|
onSelectModel(item, manual);
|
||||||
}
|
}
|
||||||
setCurrent(item.id);
|
setCurrent(item.id);
|
||||||
currentRef.current = 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(() => {
|
const renderGGUFTips = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -596,7 +598,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
current={current}
|
current={current}
|
||||||
source={modelSource}
|
source={modelSource}
|
||||||
isEvaluating={isEvaluating}
|
isEvaluating={isEvaluating}
|
||||||
onSelect={handleOnSelectModel}
|
onSelect={handleSelectModelManually}
|
||||||
></SearchResult>
|
></SearchResult>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
|||||||
Reference in New Issue
Block a user