ui: export as pdf, copy text contents of a message

This commit is contained in:
nick-delirium 2025-05-06 15:22:23 +02:00
parent ffa4763343
commit 20cab5b104
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
3 changed files with 75 additions and 9 deletions

View file

@ -1,10 +1,12 @@
import React from 'react';
import { Icon } from 'UI';
import { Icon, CopyButton } from 'UI';
import cn from 'classnames';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm'
import { Loader, ThumbsUp, ThumbsDown, ListRestart } from 'lucide-react';
import { Loader, ThumbsUp, ThumbsDown, ListRestart, FileDown } from 'lucide-react';
import { Button, Tooltip } from 'antd';
import { kaiStore } from '../KaiStore';
import { toast } from 'react-toastify';
export function ChatMsg({
text,
@ -19,12 +21,39 @@ export function ChatMsg({
userName?: string;
isLast?: boolean;
}) {
const [isProcessing, setIsProcessing] = React.useState(false);
const bodyRef = React.useRef<HTMLDivElement>(null);
const onRetry = () => {
kaiStore.editMessage(text)
}
const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => {
kaiStore.sendMsgFeedback(feedback, messageId);
};
const onExport = () => {
setIsProcessing(true);
import('jsPDF').then(({ jsPDF }) => {
const doc = new jsPDF();
doc.html(bodyRef.current, {
callback: function(doc) {
doc.save('document.pdf');
},
margin: [10, 10, 10, 10],
x: 0,
y: 0,
width: 190, // Target width
windowWidth: 675 // Window width for rendering
});
})
.catch(e => {
console.error('Error exporting message:', e);
toast.error('Failed to export message');
})
.finally(() => {
setIsProcessing(false);
});
}
return (
<div
className={cn(
@ -50,7 +79,7 @@ export function ChatMsg({
</div>
)}
<div className={'mt-1'}>
<div className='markdown-body'>
<div className='markdown-body' ref={bodyRef}>
<Markdown remarkPlugins={[remarkGfm]}>{text}</Markdown>
</div>
{isUser ? (
@ -67,12 +96,16 @@ export function ChatMsg({
) : null
) : (
<div className={'flex items-center gap-2'}>
<IconButton onClick={() => onFeedback('like', messageId)}>
<IconButton tooltip="Like this answer" onClick={() => onFeedback('like', messageId)}>
<ThumbsUp size={16} />
</IconButton>
<IconButton onClick={() => onFeedback('dislike', messageId)}>
<IconButton tooltip="Dislike this answer" onClick={() => onFeedback('dislike', messageId)}>
<ThumbsDown size={16} />
</IconButton>
<CopyButton content={text} isIcon />
<IconButton processing={isProcessing} tooltip="Export as PDF" onClick={onExport}>
<FileDown size={16} />
</IconButton>
</div>
)}
</div>
@ -83,14 +116,18 @@ export function ChatMsg({
function IconButton({
children,
onClick,
tooltip,
processing,
}: {
children: React.ReactNode;
onClick?: () => void;
tooltip?: string;
processing?: boolean;
}) {
return (
<div className={'cursor-pointer hover:text-main'} onClick={onClick}>
{children}
</div>
<Tooltip title={tooltip}>
<Button onClick={onClick} type="text" icon={children} size='small' loading={processing} />
</Tooltip>
);
}

View file

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import copy from 'copy-to-clipboard';
import { Button } from 'antd';
import { Button, Tooltip } from 'antd';
import { ClipboardCopy, ClipboardCheck } from 'lucide-react';
function CopyButton({
content,
@ -8,6 +9,7 @@ function CopyButton({
className = 'capitalize mt-2 font-medium text-neutral-400',
btnText = 'copy',
size = 'small',
isIcon = false,
}) {
const [copied, setCopied] = useState(false);
@ -19,6 +21,20 @@ function CopyButton({
}, 1000);
};
if (isIcon) {
return (
<Tooltip title={copied ? 'Copied!' : 'Copy'}>
<Button
type="text"
onClick={copyHandler}
size={size}
icon={
copied ? <ClipboardCheck size={16} /> : <ClipboardCopy size={16} />
}
/>
</Tooltip>
);
}
return (
<Button
type={variant}

View file

@ -291,6 +291,9 @@ svg {
.markdown-body table {
table-layout: auto;
width: 100%;
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
.markdown-body dl dt {
@ -386,3 +389,13 @@ svg {
margin-top: 4px!important;
margin-bottom: 4px!important;
}
.markdown-body ul {
list-style-type: none; /* Remove default bullets */
}
.markdown-body ul li:before {
content: "•"; /* Use standard bullet character */
display: inline-block;
width: 1em;
margin-left: -1em;
}