perf: http recognition and input textarea

This commit is contained in:
archer 2023-05-15 13:43:17 +08:00
parent 32a8d68c6c
commit e7d3a8e2e1
No known key found for this signature in database
GPG Key ID: 569A5660D2379E28
6 changed files with 238 additions and 24 deletions

View File

@ -51,6 +51,7 @@
"react-syntax-highlighter": "^15.5.0",
"redis": "^4.6.5",
"rehype-katex": "^6.0.2",
"rehype-raw": "^6.1.1",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sass": "^1.58.3",

View File

@ -59,6 +59,7 @@ specifiers:
react-syntax-highlighter: ^15.5.0
redis: ^4.6.5
rehype-katex: ^6.0.2
rehype-raw: ^6.1.1
remark-gfm: ^3.0.1
remark-math: ^5.1.1
sass: ^1.58.3
@ -109,6 +110,7 @@ dependencies:
react-syntax-highlighter: registry.npmmirror.com/react-syntax-highlighter/15.5.0_react@18.2.0
redis: registry.npmmirror.com/redis/4.6.5
rehype-katex: registry.npmmirror.com/rehype-katex/6.0.2
rehype-raw: 6.1.1
remark-gfm: registry.npmmirror.com/remark-gfm/3.0.1
remark-math: registry.npmmirror.com/remark-math/5.1.1
sass: registry.npmmirror.com/sass/1.58.3
@ -294,11 +296,37 @@ packages:
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
dev: true
/@types/hast/2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/parse5/6.0.3:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: false
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/bail/2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
dev: false
/comma-separated-tokens/2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
dev: false
/cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: false
/extend/3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -309,9 +337,95 @@ packages:
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
requiresBuild: true
dev: false
optional: true
/hast-util-from-parse5/7.1.2:
resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==}
dependencies:
'@types/hast': 2.3.4
'@types/unist': 2.0.6
hastscript: 7.2.0
property-information: 6.2.0
vfile: 5.3.7
vfile-location: 4.1.0
web-namespaces: 2.0.1
dev: false
/hast-util-parse-selector/3.1.1:
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
dependencies:
'@types/hast': 2.3.4
dev: false
/hast-util-raw/7.2.3:
resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==}
dependencies:
'@types/hast': 2.3.4
'@types/parse5': 6.0.3
hast-util-from-parse5: 7.1.2
hast-util-to-parse5: 7.1.0
html-void-elements: 2.0.1
parse5: 6.0.1
unist-util-position: 4.0.4
unist-util-visit: 4.1.2
vfile: 5.3.7
web-namespaces: 2.0.1
zwitch: 2.0.4
dev: false
/hast-util-to-parse5/7.1.0:
resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==}
dependencies:
'@types/hast': 2.3.4
comma-separated-tokens: 2.0.3
property-information: 6.2.0
space-separated-tokens: 2.0.2
web-namespaces: 2.0.1
zwitch: 2.0.4
dev: false
/hastscript/7.2.0:
resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
dependencies:
'@types/hast': 2.3.4
comma-separated-tokens: 2.0.3
hast-util-parse-selector: 3.1.1
property-information: 6.2.0
space-separated-tokens: 2.0.2
dev: false
/html-void-elements/2.0.1:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
dev: false
/is-buffer/2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
dev: false
/is-plain-obj/4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
dev: false
/parse5/6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
dev: false
/property-information/6.2.0:
resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==}
dev: false
/rehype-raw/6.1.1:
resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==}
dependencies:
'@types/hast': 2.3.4
hast-util-raw: 7.2.3
unified: 10.1.2
dev: false
/saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
engines: {node: '>=6'}
@ -324,9 +438,92 @@ packages:
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dev: false
optional: true
/space-separated-tokens/2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
dev: false
/trough/2.1.0:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: false
/unified/10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
'@types/unist': 2.0.6
bail: 2.0.2
extend: 3.0.2
is-buffer: 2.0.5
is-plain-obj: 4.1.0
trough: 2.1.0
vfile: 5.3.7
dev: false
/unist-util-is/5.2.0:
resolution: {integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==}
dev: false
/unist-util-position/4.0.4:
resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-stringify-position/3.0.3:
resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/5.1.3:
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.0
dev: false
/unist-util-visit/4.1.2:
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.0
unist-util-visit-parents: 5.1.3
dev: false
/vfile-location/4.1.0:
resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==}
dependencies:
'@types/unist': 2.0.6
vfile: 5.3.7
dev: false
/vfile-message/3.1.4:
resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.3
dev: false
/vfile/5.3.7:
resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==}
dependencies:
'@types/unist': 2.0.6
is-buffer: 2.0.5
unist-util-stringify-position: 3.0.3
vfile-message: 3.1.4
dev: false
/web-namespaces/2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
dev: false
/zwitch/2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
registry.npmmirror.com/@alicloud/credentials/2.2.6:
resolution: {integrity: sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz}
name: '@alicloud/credentials'
@ -7742,7 +7939,7 @@ packages:
name: hast-util-from-parse5
version: 7.1.2
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
hastscript: registry.npmmirror.com/hastscript/7.2.0
property-information: registry.npmmirror.com/property-information/6.2.0
@ -7756,7 +7953,7 @@ packages:
name: hast-util-is-element
version: 2.1.3
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
dev: false
@ -7771,7 +7968,7 @@ packages:
name: hast-util-parse-selector
version: 3.1.1
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
dev: false
registry.npmmirror.com/hast-util-to-text/3.1.2:
@ -7796,7 +7993,7 @@ packages:
name: hastscript
version: 6.0.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
comma-separated-tokens: registry.npmmirror.com/comma-separated-tokens/1.0.8
hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/2.2.5
property-information: registry.npmmirror.com/property-information/5.6.0
@ -7808,7 +8005,7 @@ packages:
name: hastscript
version: 7.2.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
comma-separated-tokens: registry.npmmirror.com/comma-separated-tokens/2.0.3
hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/3.1.1
property-information: registry.npmmirror.com/property-information/6.2.0
@ -8762,7 +8959,7 @@ packages:
version: 5.1.2
dependencies:
'@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
unist-util-visit: registry.npmmirror.com/unist-util-visit/4.1.2
dev: false
@ -8890,7 +9087,7 @@ packages:
name: mdast-util-to-hast
version: 12.3.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10
mdast-util-definitions: registry.npmmirror.com/mdast-util-definitions/5.1.2
micromark-util-sanitize-uri: registry.npmmirror.com/micromark-util-sanitize-uri/1.1.0
@ -11624,7 +11821,7 @@ packages:
name: unist-util-position
version: 4.0.4
dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
dev: false
registry.npmmirror.com/unist-util-remove-position/4.0.2:
@ -11817,7 +12014,7 @@ packages:
name: vfile-location
version: 4.1.0
dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
vfile: registry.npmmirror.com/vfile/5.3.7
dev: false

View File

@ -1,12 +1,13 @@
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import { useCopyData, formatLinkTextToHtml } from '@/utils/tools';
import Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
@ -15,13 +16,17 @@ import { codeLight } from './codeLight';
const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
const { copyData } = useCopyData();
const formatSource = useMemo(() => {
return formatLinkTextToHtml(source);
}, [source]);
return (
<ReactMarkdown
className={`markdown ${styles.markdown} ${
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
}`}
remarkPlugins={[remarkMath]}
rehypePlugins={[remarkGfm, rehypeKatex]}
rehypePlugins={[rehypeRaw, remarkGfm, rehypeKatex]}
components={{
pre: 'div',
code({ node, inline, className, children, ...props }) {
@ -63,7 +68,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
}}
linkTarget="_blank"
>
{source}
{formatSource}
</ReactMarkdown>
);
};

View File

@ -171,6 +171,9 @@ export const theme = extendTheme({
fontWeight: 400,
height: '100%',
overflow: 'hidden'
},
a: {
color: 'myBlue.700'
}
}
},

View File

@ -36,7 +36,7 @@ import { useToast } from '@/hooks/useToast';
import { useScreen } from '@/hooks/useScreen';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useCopyData, voiceBroadcast } from '@/utils/tools';
import { useCopyData, voiceBroadcast, formatLinkTextToHtml } from '@/utils/tools';
import { streamFetch } from '@/api/fetch';
import MyIcon from '@/components/Icon';
import { throttle } from 'lodash';
@ -90,7 +90,6 @@ const Chat = ({
const controller = useRef(new AbortController());
const isLeavePage = useRef(false);
const [inputVal, setInputVal] = useState(''); // user input prompt
const [showSystemPrompt, setShowSystemPrompt] = useState('');
const [messageContextMenuData, setMessageContextMenuData] = useState<{
// message messageContextMenuData
@ -168,7 +167,8 @@ const Chat = ({
// 重置输入内容
const resetInputVal = useCallback((val: string) => {
setInputVal(val);
if (!TextareaDom.current) return;
TextareaDom.current.value = val;
setTimeout(() => {
/* 回到最小高度 */
if (TextareaDom.current) {
@ -289,6 +289,7 @@ const Chat = ({
*
*/
const sendPrompt = useCallback(async () => {
// get value
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
@ -296,9 +297,10 @@ const Chat = ({
});
return;
}
const storeInput = inputVal;
// 去除空行
const val = inputVal.trim().replace(/\n\s*/g, '\n');
// get input value
const value = TextareaDom.current?.value || '';
const val = value.trim().replace(/\n\s*/g, '\n');
if (!val) {
toast({
@ -346,7 +348,7 @@ const Chat = ({
isClosable: true
});
resetInputVal(storeInput);
resetInputVal(value);
setChatData((state) => ({
...state,
@ -355,7 +357,6 @@ const Chat = ({
}
}, [
isChatting,
inputVal,
chatData.history,
setChatData,
resetInputVal,
@ -855,7 +856,10 @@ const Chat = ({
bg={'myBlue.300'}
onContextMenu={(e) => onclickContextMenu(e, item)}
>
<Box as={'p'}>{item.value}</Box>
<Box
as={'p'}
dangerouslySetInnerHTML={{ __html: formatLinkTextToHtml(item.value) }}
/>
</Card>
</Box>
)}
@ -888,7 +892,6 @@ const Chat = ({
}}
placeholder="提问"
resize={'none'}
value={inputVal}
rows={1}
height={'22px'}
lineHeight={'22px'}
@ -901,7 +904,6 @@ const Chat = ({
color={useColorModeValue('blackAlpha.700', 'white')}
onChange={(e) => {
const textarea = e.target;
setInputVal(textarea.value);
textarea.style.height = textareaMinH;
textarea.style.height = `${textarea.scrollHeight}px`;
}}

View File

@ -113,3 +113,9 @@ export const voiceBroadcast = ({ text }: { text: string }) => {
cancel: () => window.speechSynthesis?.cancel()
};
};
export const formatLinkTextToHtml = (text: string) => {
const httpReg =
/(?<!\[.*\]\()((http|https|ftp):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)(?![\)])/gi;
return text.replace(httpReg, '<a href="$&" target="_blank">$&</a>');
};