update chatview

This commit is contained in:
duanfuxiang
2025-06-20 13:50:19 +08:00
parent 35d1ddc979
commit c35f884764
13 changed files with 1983 additions and 38 deletions

75
src/utils/clipboard.ts Normal file
View File

@@ -0,0 +1,75 @@
import { useCallback, useEffect, useRef, useState } from "react"
/**
* Options for copying text to clipboard
*/
interface CopyOptions {
/** Duration in ms to show success feedback (default: 2000) */
feedbackDuration?: number
/** Optional callback when copy succeeds */
onSuccess?: () => void
/** Optional callback when copy fails */
onError?: (error: Error) => void
}
/**
* Copy text to clipboard with error handling
*/
export const copyToClipboard = async (text: string, options?: CopyOptions): Promise<boolean> => {
try {
await navigator.clipboard.writeText(text)
options?.onSuccess?.()
return true
} catch (error) {
const err = error instanceof Error ? error : new Error("Failed to copy to clipboard")
options?.onError?.(err)
console.error("Failed to copy to clipboard:", err)
return false
}
}
/**
* React hook for managing clipboard copy state with feedback
*/
export const useCopyToClipboard = (feedbackDuration = 2000) => {
const [showCopyFeedback, setShowCopyFeedback] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
const copyWithFeedback = useCallback(
async (text: string, e?: React.MouseEvent) => {
e?.stopPropagation()
// Clear any existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
const success = await copyToClipboard(text, {
onSuccess: () => {
setShowCopyFeedback(true)
timeoutRef.current = setTimeout(() => {
setShowCopyFeedback(false)
timeoutRef.current = null
}, feedbackDuration)
},
})
return success
},
[feedbackDuration],
)
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}
}, [])
return {
showCopyFeedback,
copyWithFeedback,
}
}

View File

@@ -0,0 +1,42 @@
import { useEffect, useRef } from "react"
type VoidFn = () => void
/**
* Runs `effectRef.current()` after `delay` ms whenever any of the `deps` change,
* but cancels/re-schedules if they change again before the delay.
*/
export function useDebounceEffect(effect: VoidFn, delay: number, deps: any[]) {
const callbackRef = useRef<VoidFn>(effect)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
// Keep callbackRef current
useEffect(() => {
callbackRef.current = effect
}, [effect])
useEffect(() => {
// Clear any queued call
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
// Schedule a new call
timeoutRef.current = setTimeout(() => {
// always call the *latest* version of effect
callbackRef.current()
}, delay)
// Cleanup on unmount or next effect
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}
// We want to reschedule if any item in `deps` changed,
// or if `delay` changed.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [delay, ...deps])
}