mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30:15 +01:00
search result styling changes
This commit is contained in:
parent
0b47218cd5
commit
4ce60537ca
14 changed files with 608 additions and 337 deletions
|
|
@ -17,7 +17,7 @@ import {
|
|||
refreshConversation
|
||||
} from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setSubmitState, setSubmission } from '~/store/submitSlice';
|
||||
import { setSubmitState, toggleCursor } from '~/store/submitSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import { useMessageHandler } from '../../utils/handleSubmit';
|
||||
|
||||
|
|
@ -238,6 +238,7 @@ export default function TextChat({ messages }) {
|
|||
|
||||
if (data.final) {
|
||||
convoHandler(data, currentState, currentMsg);
|
||||
dispatch(toggleCursor());
|
||||
console.log('final', data);
|
||||
}
|
||||
if (data.created) {
|
||||
|
|
@ -247,6 +248,7 @@ export default function TextChat({ messages }) {
|
|||
let text = data.text || data.response;
|
||||
if (data.initial) {
|
||||
console.log(data);
|
||||
dispatch(toggleCursor());
|
||||
}
|
||||
if (data.message) {
|
||||
latestResponseText = text;
|
||||
|
|
@ -268,6 +270,7 @@ export default function TextChat({ messages }) {
|
|||
events.onmessage = onMessage;
|
||||
|
||||
events.oncancel = (e) => {
|
||||
dispatch(toggleCursor(true));
|
||||
cancelHandler(latestResponseText, currentState, currentMsg);
|
||||
};
|
||||
|
||||
|
|
@ -276,7 +279,7 @@ export default function TextChat({ messages }) {
|
|||
events.close();
|
||||
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
dispatch(toggleCursor(true));
|
||||
errorHandler(data, currentState, currentMsg);
|
||||
};
|
||||
|
||||
|
|
@ -284,6 +287,7 @@ export default function TextChat({ messages }) {
|
|||
|
||||
return () => {
|
||||
events.removeEventListener('message', onMessage);
|
||||
dispatch(toggleCursor(true));
|
||||
const isCancelled = events.readyState <= 1;
|
||||
events.close();
|
||||
if (isCancelled) {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ const Content = React.memo(({ content }) => {
|
|||
components={{
|
||||
code,
|
||||
p,
|
||||
text: blinker,
|
||||
// li,
|
||||
// ul,
|
||||
// ol
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
|
|
@ -62,10 +58,28 @@ const p = React.memo((props) => {
|
|||
|
||||
const blinker = ({ node }) => {
|
||||
if (node.type === 'text' && node.value === '█') {
|
||||
return <span className="result-streaming">{node.value}</span>
|
||||
return <span className="result-streaming">{node.value}</span>;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const em = React.memo(({ node, ...props }) => {
|
||||
if (
|
||||
props.children[0] &&
|
||||
typeof props.children[0] === 'string' &&
|
||||
props.children[0].startsWith('^')
|
||||
) {
|
||||
return <sup>{props.children[0].substring(1)}</sup>;
|
||||
}
|
||||
if (
|
||||
props.children[0] &&
|
||||
typeof props.children[0] === 'string' &&
|
||||
props.children[0].startsWith('~')
|
||||
) {
|
||||
return <sub>{props.children[0].substring(1)}</sub>;
|
||||
}
|
||||
return <i {...props} />;
|
||||
});
|
||||
|
||||
export default Content;
|
||||
|
|
|
|||
153
client/src/components/Messages/ContentRewrite.jsx
Normal file
153
client/src/components/Messages/ContentRewrite.jsx
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import remarkMath from 'remark-math';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import TabLink from './TabLink';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import Highlight from './Highlight';
|
||||
import CodeBlock from './CodeBlock';
|
||||
import Embed from './Embed';
|
||||
// import { langSubset } from '~/utils/languages';
|
||||
|
||||
const mdOptions = {
|
||||
wrapper: React.Fragment,
|
||||
forceWrapper: true,
|
||||
overrides: {
|
||||
a: {
|
||||
component: TabLink
|
||||
// props: {
|
||||
// className: 'foo'
|
||||
// }
|
||||
},
|
||||
pre: code,
|
||||
// code: {
|
||||
// component: code
|
||||
// },
|
||||
// pre: {
|
||||
// component: PreBlock
|
||||
// },
|
||||
p: {
|
||||
component: p
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Content = ({ content }) => {
|
||||
return (
|
||||
<>
|
||||
{/* <ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, [remarkMath, { singleDollarTextMath: false }]]}
|
||||
rehypePlugins={[
|
||||
[rehypeKatex, { output: 'mathml' }],
|
||||
[
|
||||
rehypeHighlight,
|
||||
{
|
||||
detect: true,
|
||||
ignoreMissing: true,
|
||||
subset: langSubset
|
||||
}
|
||||
]
|
||||
]}
|
||||
linkTarget="_new"
|
||||
components={{
|
||||
code,
|
||||
p
|
||||
// li,
|
||||
// ul,
|
||||
// ol
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown> */}
|
||||
<Markdown
|
||||
options={mdOptions}
|
||||
>
|
||||
{content}
|
||||
</Markdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PreBlock = ({children, ...rest}) => {
|
||||
console.log('pre', children);
|
||||
if ('type' in children && children ['type'] === 'code') {
|
||||
return code(children['props']);
|
||||
}
|
||||
return <pre {...rest}>{children}</pre>;
|
||||
};
|
||||
|
||||
const code = (props) => {
|
||||
const { inline, className, children, ...rest } = props;
|
||||
|
||||
if ('type' in children && children ['type'] === 'code') {
|
||||
// return code(children['props']);
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const lang = match && match[1];
|
||||
console.log('code', lang, children);
|
||||
|
||||
if (inline) {
|
||||
return <code className={className}>{children}</code>;
|
||||
} else {
|
||||
return (
|
||||
<Embed
|
||||
language={lang}
|
||||
code={children}
|
||||
// matched={matched}
|
||||
>
|
||||
<Highlight
|
||||
language={lang}
|
||||
code={children}
|
||||
/>
|
||||
</Embed>
|
||||
);
|
||||
}
|
||||
}
|
||||
return <pre {...rest}>{children}</pre>;
|
||||
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const lang = match && match[1];
|
||||
console.log('code', lang, children);
|
||||
|
||||
if (inline) {
|
||||
return <code className={className}>{children}</code>;
|
||||
} else {
|
||||
return (
|
||||
<Embed
|
||||
language={lang}
|
||||
code={children}
|
||||
// matched={matched}
|
||||
>
|
||||
<Highlight
|
||||
language={lang}
|
||||
code={children}
|
||||
/>
|
||||
</Embed>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const p = (props) => {
|
||||
const regex = /^█$/;
|
||||
const match = regex.exec(props?.children || '');
|
||||
// if (match) {
|
||||
// return (
|
||||
// <p className="whitespace-pre-wrap ">
|
||||
// {props?.children.slice(0, -1)}
|
||||
// <span className="result-streaming">{'█'}</span>
|
||||
// </p>
|
||||
// );
|
||||
|
||||
if (match) {
|
||||
return (
|
||||
<p className="whitespace-pre-wrap ">
|
||||
<span className="result-streaming">{'█'}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <p className="whitespace-pre-wrap ">{props?.children}</p>;
|
||||
};
|
||||
|
||||
export default Content;
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||
import Clipboard from '../svg/Clipboard';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
export default function Embed({ children, lang = '', code, matched }) {
|
||||
const Embed = React.memo(({ children, lang = '', code, matched }) => {
|
||||
const [buttonText, setButtonText] = useState('Copy code');
|
||||
const isClicked = buttonText === 'Copy code';
|
||||
|
||||
|
|
@ -32,4 +32,6 @@ export default function Embed({ children, lang = '', code, matched }) {
|
|||
</div>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Embed;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Highlighter from 'react-highlight';
|
||||
import hljs from 'highlight.js';
|
||||
import { languages } from '~/utils/languages';
|
||||
|
||||
export default function Highlight({language, code}) {
|
||||
const Highlight = React.memo(({ language, code }) => {
|
||||
const [highlightedCode, setHighlightedCode] = useState(code);
|
||||
const lang = languages.has(language) ? language : 'shell';
|
||||
const lang = language ? language : 'javascript';
|
||||
|
||||
useEffect(() => {
|
||||
setHighlightedCode(hljs.highlight(code, { language: lang }).value);
|
||||
|
|
@ -12,7 +13,20 @@ export default function Highlight({language, code}) {
|
|||
|
||||
return (
|
||||
<pre>
|
||||
<code className={`language-${lang}`} dangerouslySetInnerHTML={{__html: highlightedCode}}/>
|
||||
{!highlightedCode ? (
|
||||
// <code className={`hljs !whitespace-pre language-${lang ? lang: 'javascript'}`}>
|
||||
<Highlighter className={`hljs !whitespace-pre language-${lang ? lang : 'javascript'}`}>
|
||||
{code}
|
||||
</Highlighter>
|
||||
) : (
|
||||
<code
|
||||
className={`hljs language-${lang}`}
|
||||
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
||||
/>
|
||||
)}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Highlight;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import TextWrapper from './TextWrapper';
|
||||
import Content from './Content';
|
||||
import Wrapper from './Wrapper';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import HoverButtons from './HoverButtons';
|
||||
import SiblingSwitch from './SiblingSwitch';
|
||||
import { setConversation, setLatestMessage } from '~/store/convoSlice';
|
||||
import { setModel, setCustomModel, setCustomGpt, setDisabled } from '~/store/submitSlice';
|
||||
import { setModel, setCustomModel, setCustomGpt, toggleCursor, setDisabled } from '~/store/submitSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { fetchById } from '~/utils/fetchers';
|
||||
import { getIconOfModel } from '~/utils';
|
||||
|
|
@ -22,7 +21,7 @@ export default function Message({
|
|||
siblingCount,
|
||||
setSiblingIdx
|
||||
}) {
|
||||
const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector(
|
||||
const { isSubmitting, model, chatGptLabel, cursor, promptPrefix } = useSelector(
|
||||
(state) => state.submit
|
||||
);
|
||||
const [abortScroll, setAbort] = useState(false);
|
||||
|
|
@ -42,8 +41,12 @@ export default function Message({
|
|||
return '';
|
||||
}
|
||||
|
||||
if (!cursor) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return <span className="result-streaming">█</span>;
|
||||
}, [blinker]);
|
||||
}, [blinker, cursor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (blinker && !abortScroll) {
|
||||
|
|
@ -109,7 +112,7 @@ export default function Message({
|
|||
|
||||
const clickSearchResult = async () => {
|
||||
if (!searchResult) return;
|
||||
dispatch(setMessages([]))
|
||||
dispatch(setMessages([]));
|
||||
const convoResponse = await fetchById('convos', message.conversationId);
|
||||
const convo = convoResponse.data;
|
||||
if (convo?.chatGptLabel) {
|
||||
|
|
@ -122,10 +125,10 @@ export default function Message({
|
|||
|
||||
dispatch(setCustomGpt(convo));
|
||||
dispatch(setConversation(convo));
|
||||
const {data} = await fetchById('messages', message.conversationId);
|
||||
dispatch(setMessages(data))
|
||||
const { data } = await fetchById('messages', message.conversationId);
|
||||
dispatch(setMessages(data));
|
||||
dispatch(setDisabled(false));
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -189,15 +192,12 @@ export default function Message({
|
|||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||
{/* {!isCreatedByUser && !searchResult ? (
|
||||
<TextWrapper
|
||||
text={text}
|
||||
generateCursor={generateCursor}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
)} */}
|
||||
<Content content={text} generateCursor={generateCursor}/>
|
||||
<Wrapper
|
||||
text={text}
|
||||
generateCursor={generateCursor}
|
||||
isCreatedByUser={isCreatedByUser}
|
||||
searchResult={searchResult}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -219,13 +219,13 @@ export default function Message({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MultiMessage
|
||||
messageList={message.children}
|
||||
messages={messages}
|
||||
scrollToBottom={scrollToBottom}
|
||||
currentEditId={currentEditId}
|
||||
setCurrentEditId={setCurrentEditId}
|
||||
/>
|
||||
<MultiMessage
|
||||
messageList={message.children}
|
||||
messages={messages}
|
||||
scrollToBottom={scrollToBottom}
|
||||
currentEditId={currentEditId}
|
||||
setCurrentEditId={setCurrentEditId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const inLineWrap = (parts) => {
|
|||
export default function TextWrapper({ text, generateCursor }) {
|
||||
let embedTest = false;
|
||||
let result = null;
|
||||
console.log('text wrapper', text)
|
||||
|
||||
// to match unenclosed code blocks
|
||||
if (text.match(/```/g)?.length === 1) {
|
||||
|
|
|
|||
25
client/src/components/Messages/Wrapper.jsx
Normal file
25
client/src/components/Messages/Wrapper.jsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import TextWrapper from './TextWrapper';
|
||||
import Content from './Content';
|
||||
|
||||
const Wrapper = React.memo(({ text, generateCursor, isCreatedByUser, searchResult }) => {
|
||||
if (!isCreatedByUser && searchResult) {
|
||||
return (
|
||||
<Content
|
||||
content={text}
|
||||
generateCursor={generateCursor}
|
||||
/>
|
||||
);
|
||||
} else if (!isCreatedByUser && !searchResult) {
|
||||
return (
|
||||
<TextWrapper
|
||||
text={text}
|
||||
generateCursor={generateCursor}
|
||||
/>
|
||||
);
|
||||
} else if (isCreatedByUser) {
|
||||
return <>{text}</>;
|
||||
}
|
||||
});
|
||||
|
||||
export default Wrapper;
|
||||
Loading…
Add table
Add a link
Reference in a new issue