perf(web): add debounce channel search and skip during IME composition (#5393)

This commit is contained in:
Q.A.zh
2026-06-10 17:18:51 +08:00
committed by GitHub
parent d2576ddcd3
commit 30d3a3a5f7
2 changed files with 162 additions and 17 deletions
@@ -16,7 +16,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useState, useMemo, useEffect } from 'react'
import {
useState,
useMemo,
useEffect,
useRef,
type ChangeEvent,
type CompositionEvent,
} from 'react'
import { useQuery } from '@tanstack/react-query'
import { getRouteApi } from '@tanstack/react-router'
import {
@@ -124,17 +131,26 @@ export function ChannelsTable() {
(columnFilters.find((f) => f.id === 'model')?.value as string) || ''
// Local state for immediate input feedback
const isModelFilterComposingRef = useRef(false)
const [modelFilterInput, setModelFilterInput] = useState(modelFilterFromUrl)
const debouncedModelFilter = useDebounce(modelFilterInput, 500)
const [modelFilterPendingValue, setModelFilterPendingValue] =
useState(modelFilterFromUrl)
const debouncedModelFilter = useDebounce(modelFilterPendingValue, 500)
// Sync local input with URL when URL changes (e.g., from back/forward navigation)
useEffect(() => {
setModelFilterInput(modelFilterFromUrl)
if (!isModelFilterComposingRef.current) {
setModelFilterInput(modelFilterFromUrl)
}
setModelFilterPendingValue(modelFilterFromUrl)
}, [modelFilterFromUrl])
// Update URL when debounced value changes
useEffect(() => {
if (debouncedModelFilter !== modelFilterFromUrl) {
if (
debouncedModelFilter === modelFilterPendingValue &&
debouncedModelFilter !== modelFilterFromUrl
) {
onColumnFiltersChange((prev) => {
const filtered = prev.filter((f) => f.id !== 'model')
return debouncedModelFilter
@@ -142,7 +158,34 @@ export function ChannelsTable() {
: filtered
})
}
}, [debouncedModelFilter, modelFilterFromUrl, onColumnFiltersChange])
}, [
debouncedModelFilter,
modelFilterFromUrl,
modelFilterPendingValue,
onColumnFiltersChange,
])
const handleModelFilterChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value
setModelFilterInput(value)
if (!isModelFilterComposingRef.current) {
setModelFilterPendingValue(value)
}
}
const handleModelFilterCompositionStart = () => {
isModelFilterComposingRef.current = true
}
const handleModelFilterCompositionEnd = (
event: CompositionEvent<HTMLInputElement>
) => {
isModelFilterComposingRef.current = false
const value = event.currentTarget.value
setModelFilterInput(value)
setModelFilterPendingValue(value)
}
const modelFilter = modelFilterFromUrl
@@ -385,11 +428,19 @@ export function ChannelsTable() {
applyHeaderSize
toolbarProps={{
searchPlaceholder: t('Filter by name, ID, or key...'),
searchDebounceMs: 500,
onReset: () => {
isModelFilterComposingRef.current = false
setModelFilterInput('')
setModelFilterPendingValue('')
},
additionalSearch: (
<Input
placeholder={t('Filter by model...')}
value={modelFilterInput}
onChange={(e) => setModelFilterInput(e.target.value)}
onChange={handleModelFilterChange}
onCompositionStart={handleModelFilterCompositionStart}
onCompositionEnd={handleModelFilterCompositionEnd}
className='w-full sm:w-[150px] lg:w-[180px]'
/>
),