feat: clean markdown content
This commit is contained in:
@@ -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