Merge pull request #6346 from menloresearch/fix/btn-copy-codeblock

chore: fix id code-block for avoid duplicate same state
This commit is contained in:
Faisal Amir 2025-09-02 15:28:05 +07:00 committed by GitHub
commit 8c4a88a182
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 105 deletions

View File

@ -375,11 +375,8 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
} }
const handlePaste = async (e: React.ClipboardEvent) => { const handlePaste = async (e: React.ClipboardEvent) => {
// Only allow paste if model supports mmproj // Only process images if model supports mmproj
if (!hasMmproj) { if (hasMmproj) {
return
}
const clipboardItems = e.clipboardData?.items const clipboardItems = e.clipboardData?.items
let hasProcessedImage = false let hasProcessedImage = false
@ -427,9 +424,11 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
} }
// Modern Clipboard API fallback (for Linux, images copied from web, etc.) // Modern Clipboard API fallback (for Linux, images copied from web, etc.)
if (navigator.clipboard && 'read' in navigator.clipboard) { if (
e.preventDefault() navigator.clipboard &&
'read' in navigator.clipboard &&
!hasProcessedImage
) {
try { try {
const clipboardContents = await navigator.clipboard.read() const clipboardContents = await navigator.clipboard.read()
const files: File[] = [] const files: File[] = []
@ -457,6 +456,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
} }
if (files.length > 0) { if (files.length > 0) {
e.preventDefault()
const syntheticEvent = { const syntheticEvent = {
target: { target: {
files: files, files: files,
@ -471,10 +471,12 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
} }
} }
// If we reach here, no image was found or processed // If we reach here, no image was found - allow normal text pasting to continue
if (!hasProcessedImage) { console.log(
console.log('No image data found in clipboard or clipboard access failed') 'No image data found in clipboard, allowing normal text paste'
)
} }
// If hasMmproj is false or no images found, allow normal text pasting to continue
} }
return ( return (
@ -569,7 +571,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
// When Shift+Enter is pressed, a new line is added (default behavior) // When Shift+Enter is pressed, a new line is added (default behavior)
} }
}} }}
onPaste={hasMmproj ? handlePaste : undefined} onPaste={handlePaste}
placeholder={t('common:placeholder.chatInput')} placeholder={t('common:placeholder.chatInput')}
autoFocus autoFocus
spellCheck={spellCheckChatInput} spellCheck={spellCheckChatInput}

View File

@ -6,7 +6,7 @@ import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex' import rehypeKatex from 'rehype-katex'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import * as prismStyles from 'react-syntax-highlighter/dist/cjs/styles/prism' import * as prismStyles from 'react-syntax-highlighter/dist/cjs/styles/prism'
import { memo, useState, useMemo } from 'react' import { memo, useState, useMemo, useRef, useEffect } from 'react'
import { getReadableLanguageName } from '@/lib/utils' import { getReadableLanguageName } from '@/lib/utils'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useCodeblock } from '@/hooks/useCodeblock' import { useCodeblock } from '@/hooks/useCodeblock'
@ -37,6 +37,13 @@ function RenderMarkdownComponent({
// State for tracking which code block has been copied // State for tracking which code block has been copied
const [copiedId, setCopiedId] = useState<string | null>(null) const [copiedId, setCopiedId] = useState<string | null>(null)
// Map to store unique IDs for code blocks based on content and position
const codeBlockIds = useRef(new Map<string, string>())
// Clear ID map when content changes
useEffect(() => {
codeBlockIds.current.clear()
}, [content])
// Function to handle copying code to clipboard // Function to handle copying code to clipboard
const handleCopy = (code: string, id: string) => { const handleCopy = (code: string, id: string) => {
@ -49,17 +56,6 @@ function RenderMarkdownComponent({
}, 2000) }, 2000)
} }
// Simple hash function for strings
const hashString = (str: string): string => {
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32bit integer
}
return Math.abs(hash).toString(36)
}
// Default components for syntax highlighting and emoji rendering // Default components for syntax highlighting and emoji rendering
const defaultComponents: Components = useMemo( const defaultComponents: Components = useMemo(
() => ({ () => ({
@ -70,8 +66,13 @@ function RenderMarkdownComponent({
const code = String(children).replace(/\n$/, '') const code = String(children).replace(/\n$/, '')
// Generate a stable ID based on code content and language // Generate a unique ID based on content and language
const codeId = `code-${hashString(code.substring(0, 40) + language)}` const contentKey = `${code}-${language}`
let codeId = codeBlockIds.current.get(contentKey)
if (!codeId) {
codeId = `code-${codeBlockIds.current.size}`
codeBlockIds.current.set(contentKey, codeId)
}
return !isInline && !isUser ? ( return !isInline && !isUser ? (
<div className="relative overflow-hidden border rounded-md border-main-view-fg/2"> <div className="relative overflow-hidden border rounded-md border-main-view-fg/2">
@ -155,7 +156,7 @@ function RenderMarkdownComponent({
) )
}, },
}), }),
[codeBlockStyle, showLineNumbers, copiedId, handleCopy, hashString] [codeBlockStyle, showLineNumbers, copiedId]
) )
// Memoize the remarkPlugins to prevent unnecessary re-renders // Memoize the remarkPlugins to prevent unnecessary re-renders