diff --git a/package.json b/package.json index 7a4626c5..5c4f2af7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b66e754e..da16460d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/components/markdown-viewer/index.tsx b/src/components/markdown-viewer/index.tsx index 0dbfdd41..aa38c166 100644 --- a/src/components/markdown-viewer/index.tsx +++ b/src/components/markdown-viewer/index.tsx @@ -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 = ({ content, generateImgLink, @@ -68,11 +78,14 @@ const MarkdownViewer: React.FC = ({ const renderItem = useCallback( (token: any, render: any) => { + if (token.type === 'script' || token.type === 'style') { + return null; + } if (!reDefineTypes.includes(token.type)) { return ( ); @@ -89,7 +102,13 @@ const MarkdownViewer: React.FC = ({ } if (token.type === 'html') { - htmlstr =
; + htmlstr = ( +
+ ); } if (token.type === 'list') { htmlstr = token.order ? ( @@ -158,7 +177,7 @@ const MarkdownViewer: React.FC = ({ if (token.type === 'link') { htmlstr = ( = (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 = (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 = (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 = (props) => { ); }; - const handleOnSelectModelAfterEvaluate = (item: any) => { + const handleOnSelectModelAfterEvaluate = (item: any, manual?: boolean) => { console.log( 'handleOnSelectModelAfterEvaluate:', item.name, @@ -352,9 +355,8 @@ const AddModal: FC = (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 = (props) => { handleOnSelectModelAfterEvaluate } displayEvaluateStatus={displayEvaluateStatus} - unlockWarningStatus={unlockWarningStatus} gpuOptions={props.gpuOptions} > diff --git a/src/pages/llmodels/components/model-card.tsx b/src/pages/llmodels/components/model-card.tsx index fc0d1fac..46104ded 100644 --- a/src/pages/llmodels/components/model-card.tsx +++ b/src/pages/llmodels/components/model-card.tsx @@ -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<{
{modelData ? (
-
+
{modelType && ( @@ -399,20 +397,9 @@ const ModelCard: React.FC<{ {readmeText && ( <> -
- README.md - {/* */} -
+ README.md
-
+
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 = (props) => { setLoadingModel, onSelectModel, onSelectModelAfterEvaluate, - displayEvaluateStatus, - unlockWarningStatus + displayEvaluateStatus } = props; const [dataSource, setDataSource] = useState<{ @@ -142,12 +140,12 @@ const SearchModel: React.FC = (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 = (props) => { } }; + const handleSelectModelManually = (model: any) => { + handleOnSelectModel(model, true); + }; + const renderGGUFTips = useMemo(() => { return ( = (props) => { current={current} source={modelSource} isEvaluating={isEvaluating} - onSelect={handleOnSelectModel} + onSelect={handleSelectModelManually} >
); diff --git a/src/pages/llmodels/style/model-card.less b/src/pages/llmodels/style/model-card.less index b94e813b..d3ce2999 100644 --- a/src/pages/llmodels/style/model-card.less +++ b/src/pages/llmodels/style/model-card.less @@ -18,6 +18,7 @@ border-radius: 4px; font-size: 12px; height: 20px; + margin-right: 0; } .btn {