Skip to content

Commit 1dc99ef

Browse files
committed
fix code block cannot be selected while generating
1 parent d9959cb commit 1dc99ef

File tree

4 files changed

+84
-41
lines changed

4 files changed

+84
-41
lines changed
56 Bytes
Binary file not shown.

examples/server/webui/src/components/ChatMessage.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
22
import { useAppContext } from '../utils/app.context';
33
import { Message, PendingMessage } from '../utils/types';
44
import { classNames } from '../utils/misc';
5-
import MarkdownDisplay from './MarkdownDisplay';
5+
import MarkdownDisplay, { CopyButton } from './MarkdownDisplay';
66

77
interface SplitMessage {
88
content: PendingMessage['content'];
@@ -207,20 +207,19 @@ export default function ChatMessage({
207207
{/* assistant message */}
208208
{msg.role === 'assistant' && (
209209
<>
210-
<button
211-
className="badge btn-mini show-on-hover mr-2"
212-
onClick={regenerate}
213-
disabled={msg.content === null}
214-
>
215-
🔄 Regenerate
216-
</button>
217-
<button
210+
{!isPending && (
211+
<button
212+
className="badge btn-mini show-on-hover mr-2"
213+
onClick={regenerate}
214+
disabled={msg.content === null}
215+
>
216+
🔄 Regenerate
217+
</button>
218+
)}
219+
<CopyButton
218220
className="badge btn-mini show-on-hover mr-2"
219-
onClick={() => navigator.clipboard.writeText(msg.content || '')}
220-
disabled={msg.content === null}
221-
>
222-
📋 Copy
223-
</button>
221+
content={msg.content}
222+
/>
224223
</>
225224
)}
226225
</div>

examples/server/webui/src/components/ChatScreen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function ChatScreen() {
2727
msgListElem.scrollHeight -
2828
msgListElem.scrollTop -
2929
msgListElem.clientHeight;
30-
if (!requiresNearBottom || spaceToBottom < 100) {
30+
if (!requiresNearBottom || spaceToBottom < 50) {
3131
setTimeout(
3232
() => msgListElem.scrollTo({ top: msgListElem.scrollHeight }),
3333
1

examples/server/webui/src/components/MarkdownDisplay.tsx

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import rehypeKatex from 'rehype-katex';
66
import remarkMath from 'remark-math';
77
import remarkBreaks from 'remark-breaks';
88
import 'katex/dist/katex.min.css';
9-
import { copyStr } from '../utils/misc';
9+
import { classNames, copyStr } from '../utils/misc';
10+
import { ElementContent, Root } from 'hast';
11+
import { visit } from 'unist-util-visit';
1012

1113
export default function MarkdownDisplay({ content }: { content: string }) {
1214
const preprocessedContent = useMemo(
@@ -16,25 +18,26 @@ export default function MarkdownDisplay({ content }: { content: string }) {
1618
return (
1719
<Markdown
1820
remarkPlugins={[remarkGfm, remarkMath, remarkBreaks]}
19-
rehypePlugins={[rehypeHightlight, rehypeKatex]}
21+
rehypePlugins={[rehypeHightlight, rehypeKatex, rehypeCustomCopyButton]}
2022
components={{
21-
pre: (props) => <Pre {...props} origContent={preprocessedContent} />,
23+
button: (props) => (
24+
<CopyCodeButton {...props} origContent={preprocessedContent} />
25+
),
2226
}}
2327
>
2428
{preprocessedContent}
2529
</Markdown>
2630
);
2731
}
2832

29-
const Pre: React.ElementType<
30-
React.ClassAttributes<HTMLPreElement> &
31-
React.HTMLAttributes<HTMLPreElement> &
33+
const CopyCodeButton: React.ElementType<
34+
React.ClassAttributes<HTMLButtonElement> &
35+
React.HTMLAttributes<HTMLButtonElement> &
3236
ExtraProps & { origContent: string }
33-
> = ({ node, origContent, ...props }) => {
37+
> = ({ node, origContent }) => {
3438
const startOffset = node?.position?.start.offset ?? 0;
3539
const endOffset = node?.position?.end.offset ?? 0;
3640

37-
const [copied, setCopied] = useState(false);
3841
const copiedContent = useMemo(
3942
() =>
4043
origContent
@@ -44,29 +47,70 @@ const Pre: React.ElementType<
4447
[origContent, startOffset, endOffset]
4548
);
4649

47-
if (!node?.position) {
48-
return <pre {...props} />;
49-
}
50-
5150
return (
52-
<div className="relative my-4">
53-
<div
54-
className="text-right sticky top-4 mb-2 mr-2 h-0"
55-
onClick={() => {
56-
copyStr(copiedContent);
57-
setCopied(true);
58-
}}
59-
onMouseLeave={() => setCopied(false)}
60-
>
61-
<button className="badge btn-mini">
62-
{copied ? 'Copied!' : '📋 Copy'}
63-
</button>
64-
</div>
65-
<pre {...props} />
51+
<div
52+
className={classNames({
53+
'text-right sticky top-4 mb-2 mr-2 h-0': true,
54+
'display-none': !node?.position,
55+
})}
56+
>
57+
<CopyButton className="badge btn-mini" content={copiedContent} />
6658
</div>
6759
);
6860
};
6961

62+
export const CopyButton = ({
63+
content,
64+
className,
65+
}: {
66+
content: string;
67+
className?: string;
68+
}) => {
69+
const [copied, setCopied] = useState(false);
70+
return (
71+
<button
72+
className={className}
73+
onClick={() => {
74+
copyStr(content);
75+
setCopied(true);
76+
}}
77+
onMouseLeave={() => setCopied(false)}
78+
>
79+
{copied ? 'Copied!' : '📋 Copy'}
80+
</button>
81+
);
82+
};
83+
84+
/**
85+
* This injects the "button" element before each "pre" element.
86+
* The actual button will be replaced with a react component in the MarkdownDisplay.
87+
* We don't replace "pre" node directly because it will cause the node to re-render, which causes this bug: https://github.com/ggerganov/llama.cpp/issues/9608
88+
*/
89+
function rehypeCustomCopyButton() {
90+
return function (tree: Root) {
91+
visit(tree, 'element', function (node) {
92+
if (node.tagName === 'pre' && !node.properties.visited) {
93+
const preNode = { ...node };
94+
// replace current node
95+
preNode.properties.visited = 'true';
96+
node.tagName = 'div';
97+
node.properties = {
98+
className: 'relative my-4',
99+
};
100+
// add node for button
101+
const btnNode: ElementContent = {
102+
type: 'element',
103+
tagName: 'button',
104+
properties: {},
105+
children: [],
106+
position: node.position,
107+
};
108+
node.children = [btnNode, preNode];
109+
}
110+
});
111+
};
112+
}
113+
70114
/**
71115
* The part below is copied and adapted from:
72116
* https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts

0 commit comments

Comments
 (0)