search result styling changes

This commit is contained in:
Daniel Avila 2023-03-19 11:25:12 -04:00
parent 0b47218cd5
commit 4ce60537ca
14 changed files with 608 additions and 337 deletions

View file

@ -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) {

View file

@ -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;

View 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;

View file

@ -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;

View file

@ -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;

View file

@ -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}
/>
</>
);
}

View file

@ -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) {

View 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;