Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
7dd1000a
authored
Aug 10, 2026
by
Seefs
Committed by
GitHub
Aug 10, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf(web): debounce server and large-list searches (#6727)
parent
5d3423be
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
45 additions
and
18 deletions
+45
-18
web/src/features/keys/components/api-keys-table.tsx
+1
-0
web/src/features/models/components/deployments-table.tsx
+1
-0
web/src/features/models/components/dialogs/upstream-conflict-dialog.tsx
+5
-3
web/src/features/models/components/models-table.tsx
+1
-0
web/src/features/pricing/hooks/use-filters.ts
+5
-2
web/src/features/redemption-codes/components/redemptions-table.tsx
+1
-0
web/src/features/system-settings/models/channel-selector-dialog.tsx
+5
-3
web/src/features/system-settings/models/model-ratio-visual-editor.tsx
+1
-0
web/src/features/system-settings/models/upstream-ratio-sync-table.tsx
+5
-3
web/src/features/wallet/hooks/use-billing-history.ts
+20
-7
No files found.
web/src/features/keys/components/api-keys-table.tsx
View file @
7dd1000a
...
@@ -307,6 +307,7 @@ export function ApiKeysTable() {
...
@@ -307,6 +307,7 @@ export function ApiKeysTable() {
applyHeaderSize
applyHeaderSize
toolbarProps=
{
{
toolbarProps=
{
{
searchPlaceholder
:
t
(
'Filter by name...'
),
searchPlaceholder
:
t
(
'Filter by name...'
),
searchDebounceMs
:
500
,
additionalSearch
:
(
additionalSearch
:
(
<
Input
<
Input
placeholder=
{
t
(
'Filter by API key...'
)
}
placeholder=
{
t
(
'Filter by API key...'
)
}
...
...
web/src/features/models/components/deployments-table.tsx
View file @
7dd1000a
...
@@ -230,6 +230,7 @@ export function DeploymentsTable() {
...
@@ -230,6 +230,7 @@ export function DeploymentsTable() {
applyHeaderSize
applyHeaderSize
toolbarProps=
{
{
toolbarProps=
{
{
searchPlaceholder
:
t
(
'Search deployments...'
),
searchPlaceholder
:
t
(
'Search deployments...'
),
searchDebounceMs
:
500
,
filters
:
[
filters
:
[
{
{
columnId
:
'status'
,
columnId
:
'status'
,
...
...
web/src/features/models/components/dialogs/upstream-conflict-dialog.tsx
View file @
7dd1000a
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
{
useQueryClient
}
from
'@tanstack/react-query'
import
{
useQueryClient
}
from
'@tanstack/react-query'
import
{
type
ColumnDef
,
type
RowSelectionState
}
from
'@tanstack/react-table'
import
type
{
ColumnDef
,
RowSelectionState
}
from
'@tanstack/react-table'
import
{
import
{
Search
,
Search
,
Info
,
Info
,
...
@@ -48,6 +48,7 @@ import {
...
@@ -48,6 +48,7 @@ import {
SelectTrigger
,
SelectTrigger
,
SelectValue
,
SelectValue
,
}
from
'@/components/ui/select'
}
from
'@/components/ui/select'
import
{
useDebounce
}
from
'@/hooks/use-debounce'
import
{
useIsMobile
}
from
'@/hooks/use-mobile'
import
{
useIsMobile
}
from
'@/hooks/use-mobile'
import
{
applyUpstreamOverwrite
}
from
'../../api'
import
{
applyUpstreamOverwrite
}
from
'../../api'
...
@@ -117,6 +118,7 @@ export function UpstreamConflictDialog({
...
@@ -117,6 +118,7 @@ export function UpstreamConflictDialog({
}
=
useModels
()
}
=
useModels
()
const
isMobile
=
useIsMobile
()
const
isMobile
=
useIsMobile
()
const
[
search
,
setSearch
]
=
useState
(
''
)
const
[
search
,
setSearch
]
=
useState
(
''
)
const
debouncedSearch
=
useDebounce
(
search
,
200
)
const
[
isSubmitting
,
setIsSubmitting
]
=
useState
(
false
)
const
[
isSubmitting
,
setIsSubmitting
]
=
useState
(
false
)
const
[
rowSelection
,
setRowSelection
]
=
useState
<
RowSelectionState
>
({})
const
[
rowSelection
,
setRowSelection
]
=
useState
<
RowSelectionState
>
({})
const
[
pageSize
,
setPageSize
]
=
useState
(
10
)
const
[
pageSize
,
setPageSize
]
=
useState
(
10
)
...
@@ -149,7 +151,7 @@ export function UpstreamConflictDialog({
...
@@ -149,7 +151,7 @@ export function UpstreamConflictDialog({
const
totalModels
=
upstreamConflicts
.
length
const
totalModels
=
upstreamConflicts
.
length
const
totalFields
=
conflictRows
.
length
const
totalFields
=
conflictRows
.
length
const
normalizedSearch
=
s
earch
.
trim
().
toLowerCase
()
const
normalizedSearch
=
debouncedS
earch
.
trim
().
toLowerCase
()
const
{
matchingModelNames
,
visibleRowIds
}
=
useMemo
(()
=>
{
const
{
matchingModelNames
,
visibleRowIds
}
=
useMemo
(()
=>
{
if
(
!
normalizedSearch
)
{
if
(
!
normalizedSearch
)
{
...
@@ -396,7 +398,7 @@ export function UpstreamConflictDialog({
...
@@ -396,7 +398,7 @@ export function UpstreamConflictDialog({
const
payload
:
SyncOverwritePayload
[]
=
Object
.
entries
(
groupedSelections
)
const
payload
:
SyncOverwritePayload
[]
=
Object
.
entries
(
groupedSelections
)
.
map
(([
modelName
,
fields
])
=>
({
.
map
(([
modelName
,
fields
])
=>
({
model_name
:
modelName
,
model_name
:
modelName
,
fields
:
Array
.
from
(
fields
)
,
fields
:
[...
fields
]
,
}))
}))
.
filter
((
item
)
=>
item
.
fields
.
length
>
0
)
.
filter
((
item
)
=>
item
.
fields
.
length
>
0
)
...
...
web/src/features/models/components/models-table.tsx
View file @
7dd1000a
...
@@ -202,6 +202,7 @@ export function ModelsTable() {
...
@@ -202,6 +202,7 @@ export function ModelsTable() {
applyHeaderSize
applyHeaderSize
toolbarProps={{
toolbarProps={{
searchPlaceholder: t('Filter by model name...'),
searchPlaceholder: t('Filter by model name...'),
searchDebounceMs: 500,
filters: [
filters: [
{
{
columnId: 'status',
columnId: 'status',
...
...
web/src/features/pricing/hooks/use-filters.ts
View file @
7dd1000a
...
@@ -19,6 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -19,6 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
import
{
useSearch
}
from
'@tanstack/react-router'
import
{
useSearch
}
from
'@tanstack/react-router'
import
{
useMemo
,
useCallback
,
useState
}
from
'react'
import
{
useMemo
,
useCallback
,
useState
}
from
'react'
import
{
useDebounce
}
from
'@/hooks/use-debounce'
import
{
import
{
FILTER_ALL
,
FILTER_ALL
,
SORT_OPTIONS
,
SORT_OPTIONS
,
...
@@ -67,6 +69,7 @@ export function useFilters(models: PricingModel[]) {
...
@@ -67,6 +69,7 @@ export function useFilters(models: PricingModel[]) {
}))
}))
const
searchInput
=
filterState
.
search
||
''
const
searchInput
=
filterState
.
search
||
''
const
debouncedSearchInput
=
useDebounce
(
searchInput
,
200
)
const
sortBy
=
filterState
.
sort
||
SORT_OPTIONS
.
NAME
const
sortBy
=
filterState
.
sort
||
SORT_OPTIONS
.
NAME
const
vendorFilter
=
filterState
.
vendor
||
FILTER_ALL
const
vendorFilter
=
filterState
.
vendor
||
FILTER_ALL
const
groupFilter
=
filterState
.
group
||
FILTER_ALL
const
groupFilter
=
filterState
.
group
||
FILTER_ALL
...
@@ -147,7 +150,7 @@ export function useFilters(models: PricingModel[]) {
...
@@ -147,7 +150,7 @@ export function useFilters(models: PricingModel[]) {
if
(
!
models
||
models
.
length
===
0
)
return
[]
if
(
!
models
||
models
.
length
===
0
)
return
[]
return
filterAndSortModels
(
models
,
{
return
filterAndSortModels
(
models
,
{
search
:
s
earchInput
,
search
:
debouncedS
earchInput
,
vendor
:
vendorFilter
,
vendor
:
vendorFilter
,
group
:
groupFilter
,
group
:
groupFilter
,
quotaType
:
quotaTypeFilter
,
quotaType
:
quotaTypeFilter
,
...
@@ -157,7 +160,7 @@ export function useFilters(models: PricingModel[]) {
...
@@ -157,7 +160,7 @@ export function useFilters(models: PricingModel[]) {
})
})
},
[
},
[
models
,
models
,
s
earchInput
,
debouncedS
earchInput
,
vendorFilter
,
vendorFilter
,
groupFilter
,
groupFilter
,
quotaTypeFilter
,
quotaTypeFilter
,
...
...
web/src/features/redemption-codes/components/redemptions-table.tsx
View file @
7dd1000a
...
@@ -171,6 +171,7 @@ export function RedemptionsTable() {
...
@@ -171,6 +171,7 @@ export function RedemptionsTable() {
applyHeaderSize
applyHeaderSize
toolbarProps=
{
{
toolbarProps=
{
{
searchPlaceholder
:
t
(
'Filter by name or ID...'
),
searchPlaceholder
:
t
(
'Filter by name or ID...'
),
searchDebounceMs
:
500
,
filters
:
[
filters
:
[
{
{
columnId
:
'status'
,
columnId
:
'status'
,
...
...
web/src/features/system-settings/models/channel-selector-dialog.tsx
View file @
7dd1000a
...
@@ -39,6 +39,7 @@ import {
...
@@ -39,6 +39,7 @@ import {
SelectTrigger
,
SelectTrigger
,
SelectValue
,
SelectValue
,
}
from
'@/components/ui/select'
}
from
'@/components/ui/select'
import
{
useDebounce
}
from
'@/hooks/use-debounce'
import
type
{
UpstreamChannel
}
from
'../types'
import
type
{
UpstreamChannel
}
from
'../types'
import
{
import
{
...
@@ -80,6 +81,7 @@ export function ChannelSelectorDialog({
...
@@ -80,6 +81,7 @@ export function ChannelSelectorDialog({
}:
ChannelSelectorDialogProps
)
{
}:
ChannelSelectorDialogProps
)
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
[
search
,
setSearch
]
=
useState
(
''
)
const
[
search
,
setSearch
]
=
useState
(
''
)
const
debouncedSearch
=
useDebounce
(
search
,
200
)
const
[
rowSelection
,
setRowSelection
]
=
useState
<
RowSelectionState
>
({})
const
[
rowSelection
,
setRowSelection
]
=
useState
<
RowSelectionState
>
({})
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -273,15 +275,15 @@ export function ChannelSelectorDialog({
...
@@ -273,15 +275,15 @@ export function ChannelSelectorDialog({
)
)
const
filteredChannels
=
useMemo
(()
=>
{
const
filteredChannels
=
useMemo
(()
=>
{
if
(
!
s
earch
.
trim
())
return
channels
if
(
!
debouncedS
earch
.
trim
())
return
channels
const
searchLower
=
s
earch
.
toLowerCase
()
const
searchLower
=
debouncedS
earch
.
toLowerCase
()
return
channels
.
filter
(
return
channels
.
filter
(
(
ch
)
=>
(
ch
)
=>
ch
.
name
.
toLowerCase
().
includes
(
searchLower
)
||
ch
.
name
.
toLowerCase
().
includes
(
searchLower
)
||
ch
.
base_url
.
toLowerCase
().
includes
(
searchLower
)
ch
.
base_url
.
toLowerCase
().
includes
(
searchLower
)
)
)
},
[
channels
,
s
earch
])
},
[
channels
,
debouncedS
earch
])
const
sortedChannels
=
useMemo
(()
=>
{
const
sortedChannels
=
useMemo
(()
=>
{
return
[...
filteredChannels
].
sort
((
a
,
b
)
=>
{
return
[...
filteredChannels
].
sort
((
a
,
b
)
=>
{
...
...
web/src/features/system-settings/models/model-ratio-visual-editor.tsx
View file @
7dd1000a
...
@@ -682,6 +682,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
...
@@ -682,6 +682,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
<
DataTableToolbar
<
DataTableToolbar
table=
{
table
}
table=
{
table
}
searchPlaceholder=
{
t
(
'Search models...'
)
}
searchPlaceholder=
{
t
(
'Search models...'
)
}
searchDebounceMs=
{
250
}
filters=
{
[
filters=
{
[
{
{
columnId
:
'billingMode'
,
columnId
:
'billingMode'
,
...
...
web/src/features/system-settings/models/upstream-ratio-sync-table.tsx
View file @
7dd1000a
...
@@ -34,6 +34,7 @@ import {
...
@@ -34,6 +34,7 @@ import {
SelectTrigger
,
SelectTrigger
,
SelectValue
,
SelectValue
,
}
from
'@/components/ui/select'
}
from
'@/components/ui/select'
import
{
useDebounce
}
from
'@/hooks/use-debounce'
import
type
{
DifferencesMap
,
RatioType
}
from
'../types'
import
type
{
DifferencesMap
,
RatioType
}
from
'../types'
import
{
RATIO_TYPE_OPTIONS
}
from
'./constants'
import
{
RATIO_TYPE_OPTIONS
}
from
'./constants'
...
@@ -88,6 +89,7 @@ export function UpstreamRatioSyncTable({
...
@@ -88,6 +89,7 @@ export function UpstreamRatioSyncTable({
}:
UpstreamRatioSyncTableProps
)
{
}:
UpstreamRatioSyncTableProps
)
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
[
search
,
setSearch
]
=
useState
(
''
)
const
[
search
,
setSearch
]
=
useState
(
''
)
const
debouncedSearch
=
useDebounce
(
search
,
250
)
const
[
ratioTypeFilter
,
setRatioTypeFilter
]
=
useState
<
string
>
(
''
)
const
[
ratioTypeFilter
,
setRatioTypeFilter
]
=
useState
<
string
>
(
''
)
const
dataSource
=
useMemo
<
ModelRow
[]
>
(()
=>
{
const
dataSource
=
useMemo
<
ModelRow
[]
>
(()
=>
{
...
@@ -106,8 +108,8 @@ export function UpstreamRatioSyncTable({
...
@@ -106,8 +108,8 @@ export function UpstreamRatioSyncTable({
const
filteredData
=
useMemo
(()
=>
{
const
filteredData
=
useMemo
(()
=>
{
let
data
=
dataSource
let
data
=
dataSource
if
(
s
earch
.
trim
())
{
if
(
debouncedS
earch
.
trim
())
{
const
lower
=
s
earch
.
toLowerCase
()
const
lower
=
debouncedS
earch
.
toLowerCase
()
data
=
data
.
filter
((
row
)
=>
row
.
model
.
toLowerCase
().
includes
(
lower
))
data
=
data
.
filter
((
row
)
=>
row
.
model
.
toLowerCase
().
includes
(
lower
))
}
}
...
@@ -116,7 +118,7 @@ export function UpstreamRatioSyncTable({
...
@@ -116,7 +118,7 @@ export function UpstreamRatioSyncTable({
}
}
return
data
return
data
},
[
dataSource
,
s
earch
,
ratioTypeFilter
])
},
[
dataSource
,
debouncedS
earch
,
ratioTypeFilter
])
const
upstreamNames
=
useMemo
(()
=>
{
const
upstreamNames
=
useMemo
(()
=>
{
const
set
=
new
Set
<
string
>
()
const
set
=
new
Set
<
string
>
()
...
...
web/src/features/wallet/hooks/use-billing-history.ts
View file @
7dd1000a
...
@@ -17,10 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,10 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
i18next
from
'i18next'
import
i18next
from
'i18next'
import
{
useState
,
useEffect
,
useCallback
}
from
'react'
import
{
useState
,
useEffect
,
useCallback
,
useRef
}
from
'react'
import
{
toast
}
from
'sonner'
import
{
toast
}
from
'sonner'
import
{
useIsAdmin
}
from
'@/hooks/use-admin'
import
{
useIsAdmin
}
from
'@/hooks/use-admin'
import
{
useDebounce
}
from
'@/hooks/use-debounce'
import
{
import
{
getUserBillingHistory
,
getUserBillingHistory
,
...
@@ -50,6 +51,8 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
...
@@ -50,6 +51,8 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
const
[
page
,
setPage
]
=
useState
(
initialPage
)
const
[
page
,
setPage
]
=
useState
(
initialPage
)
const
[
pageSize
,
setPageSize
]
=
useState
(
initialPageSize
)
const
[
pageSize
,
setPageSize
]
=
useState
(
initialPageSize
)
const
[
keyword
,
setKeyword
]
=
useState
(
''
)
const
[
keyword
,
setKeyword
]
=
useState
(
''
)
const
debouncedKeyword
=
useDebounce
(
keyword
)
const
requestIdRef
=
useRef
(
0
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
[
completing
,
setCompleting
]
=
useState
(
false
)
const
[
completing
,
setCompleting
]
=
useState
(
false
)
...
@@ -57,11 +60,14 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
...
@@ -57,11 +60,14 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
* Fetch billing history
* Fetch billing history
*/
*/
const
fetchBillingHistory
=
useCallback
(
async
()
=>
{
const
fetchBillingHistory
=
useCallback
(
async
()
=>
{
const
requestId
=
++
requestIdRef
.
current
setLoading
(
true
)
setLoading
(
true
)
try
{
try
{
const
response
=
isAdmin
const
response
=
isAdmin
?
await
getAllBillingHistory
(
page
,
pageSize
,
keyword
)
?
await
getAllBillingHistory
(
page
,
pageSize
,
debouncedKeyword
)
:
await
getUserBillingHistory
(
page
,
pageSize
,
keyword
)
:
await
getUserBillingHistory
(
page
,
pageSize
,
debouncedKeyword
)
if
(
requestId
!==
requestIdRef
.
current
)
return
if
(
isApiSuccess
(
response
)
&&
response
.
data
)
{
if
(
isApiSuccess
(
response
)
&&
response
.
data
)
{
setRecords
(
response
.
data
.
items
||
[])
setRecords
(
response
.
data
.
items
||
[])
...
@@ -74,15 +80,19 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
...
@@ -74,15 +80,19 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
setTotal
(
0
)
setTotal
(
0
)
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
if
(
requestId
!==
requestIdRef
.
current
)
return
// eslint-disable-next-line no-console
// eslint-disable-next-line no-console
console
.
error
(
'Failed to fetch billing history:'
,
error
)
console
.
error
(
'Failed to fetch billing history:'
,
error
)
toast
.
error
(
i18next
.
t
(
'Failed to load billing history'
))
toast
.
error
(
i18next
.
t
(
'Failed to load billing history'
))
setRecords
([])
setRecords
([])
setTotal
(
0
)
setTotal
(
0
)
}
finally
{
}
finally
{
setLoading
(
false
)
if
(
requestId
===
requestIdRef
.
current
)
{
setLoading
(
false
)
}
}
}
},
[
isAdmin
,
page
,
pageSize
,
keyword
])
},
[
debouncedKeyword
,
isAdmin
,
page
,
pageSize
])
/**
/**
* Complete a pending order (admin only)
* Complete a pending order (admin only)
...
@@ -137,14 +147,17 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
...
@@ -137,14 +147,17 @@ export function useBillingHistory(options: UseBillingHistoryOptions = {}) {
* Search by keyword
* Search by keyword
*/
*/
const
handleSearch
=
useCallback
((
newKeyword
:
string
)
=>
{
const
handleSearch
=
useCallback
((
newKeyword
:
string
)
=>
{
requestIdRef
.
current
+=
1
setKeyword
(
newKeyword
)
setKeyword
(
newKeyword
)
setPage
(
1
)
// Reset to first page when searching
setPage
(
1
)
// Reset to first page when searching
},
[])
},
[])
// Fetch data
when dependencies change
// Fetch data
after the search draft has settled.
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
keyword
!==
debouncedKeyword
)
return
fetchBillingHistory
()
fetchBillingHistory
()
},
[
fetchBillingHistory
])
},
[
debouncedKeyword
,
fetchBillingHistory
,
keyword
])
return
{
return
{
records
,
records
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment