markdown guide

This commit is contained in:
archer
2023-07-18 14:44:30 +08:00
parent 505aff3dbf
commit a510f96b83
8 changed files with 176 additions and 78 deletions

View File

@@ -89,8 +89,6 @@ export const streamFetch = ({
});
read();
} catch (err: any) {
console.log(111111111111111111);
if (err?.message === 'The user aborted a request.') {
return resolve({
responseText,

View File

@@ -17,6 +17,7 @@ import { useUserStore } from '@/store/user';
import { Types } from 'mongoose';
import { HUMAN_ICON, quoteLenKey, rawSearchKey } from '@/constants/chat';
import Markdown from '@/components/Markdown';
import { EventNameEnum } from '../Markdown/constant';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
@@ -236,7 +237,7 @@ const ChatBox = (
* user confirm send prompt
*/
const sendPrompt = useCallback(
async (data: Record<string, any> = {}) => {
async (data: Record<string, any> = {}, inputVal = '') => {
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
@@ -245,8 +246,7 @@ const ChatBox = (
return;
}
// get input value
const value = TextareaDom.current?.value || '';
const val = value.trim().replace(/\n\s*/g, '\n');
const val = inputVal.trim().replace(/\n\s*/g, '\n');
if (!val) {
toast({
@@ -320,7 +320,7 @@ const ChatBox = (
});
if (!err?.responseText) {
resetInputVal(value);
resetInputVal(inputVal);
setChatHistory(newChatList.slice(0, newChatList.length - 2));
}
@@ -403,11 +403,25 @@ const ChatBox = (
{/* avatar */}
<ChatAvatar src={appAvatar} order={1} mr={['6px', 2]} />
{/* message */}
<Flex order={2} pt={2} maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}>
<Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}>
{welcomeText}
</Card>
</Flex>
<Card
order={2}
mt={2}
px={4}
py={3}
bg={'white'}
maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}
borderRadius={'0 8px 8px 8px'}
>
<Markdown
source={`~~~guide \n${welcomeText}`}
isChatting={false}
onClick={(e) => {
const val = e?.data;
if (e?.event !== EventNameEnum.guideClick || !val) return;
handleSubmit((data) => sendPrompt(data, val))();
}}
/>
</Card>
</Flex>
)}
{/* variable input */}
@@ -416,54 +430,59 @@ const ChatBox = (
{/* avatar */}
<ChatAvatar src={appAvatar} order={1} mr={['6px', 2]} />
{/* message */}
<Flex order={2} pt={2} maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}>
<Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}>
<Box>
{variableModules.map((item) => (
<Box w={'min(100%,300px)'} key={item.id} mb={4}>
<VariableLabel required={item.required}>{item.label}</VariableLabel>
{item.type === VariableInputEnum.input && (
<Input
isDisabled={variableIsFinish}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.select && (
<MySelect
width={'100%'}
isDisabled={variableIsFinish}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
setRefresh(!refresh);
}}
/>
)}
</Box>
))}
{!variableIsFinish && (
<Button
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
<Card
order={2}
mt={2}
flex={1}
bg={'white'}
px={4}
py={3}
borderRadius={'0 8px 8px 8px'}
maxW={'min(100%,400px)'}
>
{variableModules.map((item) => (
<Box key={item.id} mb={4}>
<VariableLabel required={item.required}>{item.label}</VariableLabel>
{item.type === VariableInputEnum.input && (
<Input
isDisabled={variableIsFinish}
{...register(item.key, {
required: item.required
})}
>
{'开始对话'}
</Button>
/>
)}
{item.type === VariableInputEnum.select && (
<MySelect
width={'100%'}
isDisabled={variableIsFinish}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
setRefresh(!refresh);
}}
/>
)}
</Box>
</Card>
</Flex>
))}
{!variableIsFinish && (
<Button
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
})}
>
{'开始对话'}
</Button>
)}
</Card>
</Flex>
)}
@@ -650,7 +669,7 @@ const ChatBox = (
onKeyDown={(e) => {
// 触发快捷发送
if (e.keyCode === 13 && !e.shiftKey) {
handleSubmit(sendPrompt)();
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
e.preventDefault();
}
// 全选内容
@@ -685,7 +704,9 @@ const ChatBox = (
height={['18px', '20px']}
cursor={'pointer'}
color={'gray.500'}
onClick={handleSubmit(sendPrompt)}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
)}
</Flex>

View File

@@ -0,0 +1,49 @@
import React, { useMemo } from 'react';
import { Box } from '@chakra-ui/react';
import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm';
import RemarkMath from 'remark-math';
import RehypeKatex from 'rehype-katex';
import 'katex/dist/katex.min.css';
import styles from '../index.module.scss';
import { EventNameEnum } from '../constant';
const Guide = ({ text, onClick }: { text: string; onClick?: (e: any) => void }) => {
const formatText = useMemo(() => text.replace(/\[(.*?)\]/g, '[$1]()'), [text]);
return (
<ReactMarkdown
className={`markdown ${styles.markdown}`}
remarkPlugins={[RemarkGfm, RemarkMath]}
rehypePlugins={[RehypeKatex]}
components={{
a({ children }: any) {
return (
<Box as={'li'} py={1} m={0}>
<Box
as={'span'}
color={'blue.600'}
textDecoration={'underline'}
cursor={'pointer'}
onClick={() => {
if (!onClick) return;
onClick({
event: EventNameEnum.guideClick,
data: String(children)
});
}}
>
{String(children)}
</Box>
</Box>
);
}
}}
>
{formatText}
</ReactMarkdown>
);
};
export default React.memo(Guide);

View File

@@ -0,0 +1,3 @@
export enum EventNameEnum {
guideClick = 'guideClick'
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm';
import RemarkMath from 'remark-math';
@@ -7,19 +7,27 @@ import RemarkBreaks from 'remark-breaks';
import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
import dynamic from 'next/dynamic';
import Link from './Link';
import CodeLight from './CodeLight';
import MermaidCodeBlock from './img/MermaidCodeBlock';
import MdImage from './img/Image';
function Code({ inline, className, children }: any) {
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'));
const MdImage = dynamic(() => import('./img/Image'));
const ChatGuide = dynamic(() => import('./chat/Guide'));
function Code({ inline, className, children, onClick }: any) {
const match = /language-(\w+)/.exec(className || '');
const codeType = match?.[1];
if (match?.[1] === 'mermaid') {
if (codeType === 'mermaid') {
return <MermaidCodeBlock code={String(children)} />;
}
if (codeType === 'guide') {
return <ChatGuide text={String(children)} onClick={onClick} />;
}
return (
<CodeLight className={className} inline={inline} match={match}>
{children}
@@ -31,7 +39,23 @@ function Image({ src }: { src?: string }) {
return <MdImage src={src} />;
}
const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
const Markdown = ({
source,
isChatting = false,
onClick
}: {
source: string;
isChatting?: boolean;
onClick?: (e: any) => void;
}) => {
const components = useRef({
a: Link,
img: Image,
pre: 'div',
p: 'div',
code: (props: any) => <Code {...props} onClick={onClick} />
});
return (
<ReactMarkdown
className={`markdown ${styles.markdown}
@@ -39,13 +63,8 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
`}
remarkPlugins={[RemarkGfm, RemarkMath, RemarkBreaks]}
rehypePlugins={[RehypeKatex]}
components={{
a: Link,
img: Image,
pre: 'div',
p: 'div',
code: Code
}}
// @ts-ignore
components={components.current}
>
{source}
</ReactMarkdown>

View File

@@ -28,7 +28,7 @@ const Settings = ({ appId }: { appId: string }) => {
content: '确认删除该应用?'
});
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const [fullScreen, setFullScreen] = useState(true);
const [fullScreen, setFullScreen] = useState(false);
/* 点击删除 */
const handleDelModel = useCallback(async () => {

View File

@@ -12,7 +12,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyTooltip from '@/components/MyTooltip';
const welcomePlaceholder =
'每次对话开始前,发送一个初始内容。可使用的特殊标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }

View File

@@ -3,15 +3,23 @@ import { useChatBox } from '@/components/ChatBox';
import { ChatItemType } from '@/types/chat';
import { Menu, MenuButton, MenuList, MenuItem, Box } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useRouter } from 'next/router';
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
const { onExportChat } = useChatBox();
const router = useRouter();
const menuList = useRef([
// {
// icon: 'shareLight',
// label: '分享对话',
// onClick: () => {}
// },
{
icon: 'chatLight',
label: '新对话',
onClick: () => {
router.push({
query: {
appId: router.query?.appId
}
});
}
},
{
icon: 'apiLight',
label: 'HTML导出',