Copy button — on all messages, Copied! feedback, checkmark icon
This commit is contained in:
+50
-34
@@ -1,9 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useStore } from '@/lib/store';
|
||||
import { renderMarkdown } from '@/lib/markdown';
|
||||
|
||||
function CopyRow({ content, sources }: { content: string; sources: string[] }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
function copy() {
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center gap-3 mt-1.5">
|
||||
<button
|
||||
onPointerUp={copy}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '2px 6px',
|
||||
borderRadius: '4px',
|
||||
color: copied ? 'var(--accent)' : 'var(--text3)',
|
||||
fontSize: '11px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
title="Copy"
|
||||
>
|
||||
{copied ? (
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
)}
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
{sources && sources.length > 0 && (
|
||||
<div className="text-xs italic" style={{ color: 'var(--text3)' }}>
|
||||
Sources: {[...new Set(sources)].join(', ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MessageList() {
|
||||
const { messages, isLoading } = useStore();
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
@@ -60,39 +108,7 @@ export default function MessageList() {
|
||||
<span style={{ whiteSpace: 'pre-wrap' }}>{m.content}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-1.5">
|
||||
{m.role === 'assistant' && (
|
||||
<button
|
||||
onPointerUp={() => {
|
||||
navigator.clipboard.writeText(m.content);
|
||||
}}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '2px 6px',
|
||||
borderRadius: '4px',
|
||||
color: 'var(--text3)',
|
||||
fontSize: '11px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
title="Copy response"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
Copy
|
||||
</button>
|
||||
)}
|
||||
{m.sources && m.sources.length > 0 && (
|
||||
<div className="text-xs italic" style={{ color: 'var(--text3)' }}>
|
||||
Sources: {[...new Set(m.sources)].join(', ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CopyRow content={m.content} sources={m.sources} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user