feat: clean markdown content

This commit is contained in:
jialin
2025-06-25 14:35:00 +08:00
parent 830ee45161
commit a6825f7ee3
7 changed files with 70 additions and 44 deletions
+22 -3
View File
@@ -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"
+16 -15
View File
@@ -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>
+4 -17
View File
@@ -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;
+11 -9
View File
@@ -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>
);
+1
View File
@@ -18,6 +18,7 @@
border-radius: 4px;
font-size: 12px;
height: 20px;
margin-right: 0;
}
.btn {