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
a720064d
authored
May 12, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'main' of github.com:QuantumNous/new-api
parents
03d53732
fde2cac9
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
103 additions
and
33 deletions
+103
-33
.dockerignore
+4
-0
web/default/src/components/ui/dropdown-menu-events.ts
+25
-0
web/default/src/components/ui/dropdown-menu.test.tsx
+50
-0
web/default/src/components/ui/dropdown-menu.tsx
+16
-1
web/default/src/features/keys/components/api-keys-columns.tsx
+2
-30
web/default/src/features/playground/lib/storage.ts
+6
-2
No files found.
.dockerignore
View file @
a720064d
...
@@ -8,4 +8,8 @@ docs
...
@@ -8,4 +8,8 @@ docs
.eslintcache
.eslintcache
.gocache
.gocache
/web/node_modules
/web/node_modules
/web/default/node_modules
/web/default/dist
/web/classic/node_modules
/web/classic/dist
!THIRD-PARTY-LICENSES.md
!THIRD-PARTY-LICENSES.md
web/default/src/components/ui/dropdown-menu-events.ts
0 → 100644
View file @
a720064d
import
type
*
as
React
from
'react'
export
type
DropdownMenuItemSelectEvent
=
React
.
MouseEvent
<
HTMLElement
>
&
{
preventBaseUIHandler
?:
()
=>
void
}
export
type
DropdownMenuItemSelectHandler
=
(
event
:
DropdownMenuItemSelectEvent
)
=>
void
export
function
handleDropdownMenuItemSelect
(
event
:
DropdownMenuItemSelectEvent
,
onClick
?:
React
.
MouseEventHandler
<
HTMLElement
>
,
onSelect
?:
DropdownMenuItemSelectHandler
)
{
onClick
?.(
event
)
if
(
!
event
.
defaultPrevented
)
{
onSelect
?.(
event
)
}
if
(
event
.
defaultPrevented
)
{
event
.
preventBaseUIHandler
?.()
}
}
web/default/src/components/ui/dropdown-menu.test.tsx
0 → 100644
View file @
a720064d
import
assert
from
'node:assert/strict'
import
{
describe
,
test
}
from
'node:test'
import
{
handleDropdownMenuItemSelect
}
from
'./dropdown-menu-events'
function
createMenuEvent
()
{
let
defaultPrevented
=
false
let
baseUIHandlerPrevented
=
false
return
{
get
defaultPrevented
()
{
return
defaultPrevented
},
preventDefault
()
{
defaultPrevented
=
true
},
preventBaseUIHandler
()
{
baseUIHandlerPrevented
=
true
},
get
baseUIHandlerPrevented
()
{
return
baseUIHandlerPrevented
},
}
as
unknown
as
Parameters
<
typeof
handleDropdownMenuItemSelect
>
[
0
]
&
{
baseUIHandlerPrevented
:
boolean
}
}
describe
(
'DropdownMenuItem onSelect compatibility'
,
()
=>
{
test
(
'calls the Radix-style onSelect handler on item click'
,
()
=>
{
const
event
=
createMenuEvent
()
let
selected
=
false
handleDropdownMenuItemSelect
(
event
,
undefined
,
()
=>
{
selected
=
true
})
assert
.
equal
(
selected
,
true
)
assert
.
equal
(
event
.
baseUIHandlerPrevented
,
false
)
})
test
(
'keeps the Base UI menu open when onSelect prevents default'
,
()
=>
{
const
event
=
createMenuEvent
()
handleDropdownMenuItemSelect
(
event
,
undefined
,
(
selectEvent
)
=>
{
selectEvent
.
preventDefault
()
})
assert
.
equal
(
event
.
defaultPrevented
,
true
)
assert
.
equal
(
event
.
baseUIHandlerPrevented
,
true
)
})
})
web/default/src/components/ui/dropdown-menu.tsx
View file @
a720064d
...
@@ -21,6 +21,10 @@ import { Menu as MenuPrimitive } from '@base-ui/react/menu'
...
@@ -21,6 +21,10 @@ import { Menu as MenuPrimitive } from '@base-ui/react/menu'
import
{
ArrowRight01Icon
,
Tick02Icon
}
from
'@hugeicons/core-free-icons'
import
{
ArrowRight01Icon
,
Tick02Icon
}
from
'@hugeicons/core-free-icons'
import
{
HugeiconsIcon
}
from
'@hugeicons/react'
import
{
HugeiconsIcon
}
from
'@hugeicons/react'
import
{
cn
}
from
'@/lib/utils'
import
{
cn
}
from
'@/lib/utils'
import
{
handleDropdownMenuItemSelect
,
type
DropdownMenuItemSelectHandler
,
}
from
'./dropdown-menu-events'
function
DropdownMenu
({
...
props
}:
MenuPrimitive
.
Root
.
Props
)
{
function
DropdownMenu
({
...
props
}:
MenuPrimitive
.
Root
.
Props
)
{
return
<
MenuPrimitive
.
Root
data
-
slot=
'dropdown-menu'
{
...
props
}
/>
return
<
MenuPrimitive
.
Root
data
-
slot=
'dropdown-menu'
{
...
props
}
/>
...
@@ -96,11 +100,21 @@ function DropdownMenuItem({
...
@@ -96,11 +100,21 @@ function DropdownMenuItem({
className
,
className
,
inset
,
inset
,
variant
=
'default'
,
variant
=
'default'
,
onClick
,
onSelect
,
...
props
...
props
}:
MenuPrimitive
.
Item
.
Props
&
{
}:
Omit
<
MenuPrimitive
.
Item
.
Props
,
'onSelect'
>
&
{
inset
?:
boolean
inset
?:
boolean
variant
?:
'default'
|
'destructive'
variant
?:
'default'
|
'destructive'
onSelect
?:
DropdownMenuItemSelectHandler
})
{
})
{
const
handleClick
=
React
.
useCallback
(
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
handleDropdownMenuItemSelect
(
event
,
onClick
,
onSelect
)
},
[
onClick
,
onSelect
]
)
return
(
return
(
<
MenuPrimitive
.
Item
<
MenuPrimitive
.
Item
data
-
slot=
'dropdown-menu-item'
data
-
slot=
'dropdown-menu-item'
...
@@ -110,6 +124,7 @@ function DropdownMenuItem({
...
@@ -110,6 +124,7 @@ function DropdownMenuItem({
"group/dropdown-menu-item focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
,
"group/dropdown-menu-item focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
,
className
className
)
}
)
}
onClick=
{
onClick
||
onSelect
?
handleClick
:
undefined
}
{
...
props
}
{
...
props
}
/>
/>
)
)
...
...
web/default/src/features/keys/components/api-keys-columns.tsx
View file @
a720064d
...
@@ -16,11 +16,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -16,11 +16,9 @@ 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
{
useMemo
}
from
'react'
import
{
useQuery
}
from
'@tanstack/react-query'
import
{
useQuery
}
from
'@tanstack/react-query'
import
{
type
ColumnDef
}
from
'@tanstack/react-table'
import
{
type
ColumnDef
}
from
'@tanstack/react-table'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
useAuthStore
}
from
'@/stores/auth-store'
import
{
getUserGroups
}
from
'@/lib/api'
import
{
getUserGroups
}
from
'@/lib/api'
import
{
formatQuota
,
formatTimestampToDate
}
from
'@/lib/format'
import
{
formatQuota
,
formatTimestampToDate
}
from
'@/lib/format'
import
{
cn
}
from
'@/lib/utils'
import
{
cn
}
from
'@/lib/utils'
...
@@ -34,7 +32,6 @@ import {
...
@@ -34,7 +32,6 @@ import {
import
{
DataTableColumnHeader
}
from
'@/components/data-table'
import
{
DataTableColumnHeader
}
from
'@/components/data-table'
import
{
GroupBadge
}
from
'@/components/group-badge'
import
{
GroupBadge
}
from
'@/components/group-badge'
import
{
StatusBadge
}
from
'@/components/status-badge'
import
{
StatusBadge
}
from
'@/components/status-badge'
import
{
getSystemOptions
}
from
'@/features/system-settings/api'
import
{
API_KEY_STATUSES
}
from
'../constants'
import
{
API_KEY_STATUSES
}
from
'../constants'
import
{
type
ApiKey
}
from
'../types'
import
{
type
ApiKey
}
from
'../types'
import
{
import
{
...
@@ -51,31 +48,9 @@ function getQuotaProgressColor(percentage: number): string {
...
@@ -51,31 +48,9 @@ function getQuotaProgressColor(percentage: number): string {
}
}
function
useGroupRatios
():
Record
<
string
,
number
>
{
function
useGroupRatios
():
Record
<
string
,
number
>
{
const
isAdmin
=
useAuthStore
((
s
)
=>
const
{
data
}
=
useQuery
({
Boolean
(
s
.
auth
.
user
?.
role
&&
s
.
auth
.
user
.
role
>=
10
)
)
const
{
data
:
adminData
}
=
useQuery
({
queryKey
:
[
'system-options-group-ratio'
],
queryFn
:
getSystemOptions
,
enabled
:
isAdmin
,
staleTime
:
5
*
60
*
1000
,
select
:
(
res
)
=>
{
if
(
!
res
.
success
||
!
res
.
data
)
return
{}
const
option
=
res
.
data
.
find
((
o
)
=>
o
.
key
===
'GroupRatio'
)
if
(
!
option
?.
value
)
return
{}
try
{
return
JSON
.
parse
(
option
.
value
)
as
Record
<
string
,
number
>
}
catch
{
return
{}
}
},
})
const
{
data
:
userGroupsData
}
=
useQuery
({
queryKey
:
[
'user-self-groups'
],
queryKey
:
[
'user-self-groups'
],
queryFn
:
getUserGroups
,
queryFn
:
getUserGroups
,
enabled
:
!
isAdmin
,
staleTime
:
5
*
60
*
1000
,
staleTime
:
5
*
60
*
1000
,
select
:
(
res
)
=>
{
select
:
(
res
)
=>
{
if
(
!
res
.
success
||
!
res
.
data
)
return
{}
if
(
!
res
.
success
||
!
res
.
data
)
return
{}
...
@@ -89,10 +64,7 @@ function useGroupRatios(): Record<string, number> {
...
@@ -89,10 +64,7 @@ function useGroupRatios(): Record<string, number> {
},
},
})
})
return
useMemo
(
return
data
??
{}
()
=>
(
isAdmin
?
adminData
:
userGroupsData
)
??
{},
[
isAdmin
,
adminData
,
userGroupsData
]
)
}
}
export
function
useApiKeysColumns
():
ColumnDef
<
ApiKey
>
[]
{
export
function
useApiKeysColumns
():
ColumnDef
<
ApiKey
>
[]
{
...
...
web/default/src/features/playground/lib/storage.ts
View file @
a720064d
...
@@ -88,8 +88,12 @@ export function loadMessages(): Message[] | null {
...
@@ -88,8 +88,12 @@ export function loadMessages(): Message[] | null {
try
{
try
{
const
saved
=
localStorage
.
getItem
(
STORAGE_KEYS
.
MESSAGES
)
const
saved
=
localStorage
.
getItem
(
STORAGE_KEYS
.
MESSAGES
)
if
(
saved
)
{
if
(
saved
)
{
const
parsed
:
Message
[]
=
JSON
.
parse
(
saved
)
const
parsed
:
unknown
=
JSON
.
parse
(
saved
)
const
sanitized
=
sanitizeMessagesOnLoad
(
parsed
)
if
(
!
Array
.
isArray
(
parsed
))
{
localStorage
.
removeItem
(
STORAGE_KEYS
.
MESSAGES
)
return
null
}
const
sanitized
=
sanitizeMessagesOnLoad
(
parsed
as
Message
[])
// Persist sanitized result to avoid re-sanitizing on subsequent loads
// Persist sanitized result to avoid re-sanitizing on subsequent loads
if
(
sanitized
!==
parsed
)
{
if
(
sanitized
!==
parsed
)
{
saveMessages
(
sanitized
)
saveMessages
(
sanitized
)
...
...
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