feat: clean markdown content
This commit is contained in:
@@ -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",
|
||||
|
||||
Generated
+15
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user