Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
d89e797b
"git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "13ae0ce7b0299fe83ea7ac0edf61c223ba5f83da"
Commit
d89e797b
authored
Jan 14, 2026
by
墨颜
Browse files
Merge branch 'main' of
https://github.com/whoismonay/sub2api
parents
fb99ceac
99cbfa15
Changes
5
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/openai_codex_transform.go
View file @
d89e797b
...
@@ -90,17 +90,11 @@ func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult {
...
@@ -90,17 +90,11 @@ func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult {
result
.
NormalizedModel
=
normalizedModel
result
.
NormalizedModel
=
normalizedModel
}
}
// 续链场景强制启用 store;非续链仍按原策略强制关闭存储。
// OAuth 走 ChatGPT internal API 时,store 必须为 false;显式 true 也会强制覆盖。
if
needsToolContinuation
{
// 避免上游返回 "Store must be set to false"。
if
v
,
ok
:=
reqBody
[
"store"
]
.
(
bool
);
!
ok
||
!
v
{
if
v
,
ok
:=
reqBody
[
"store"
]
.
(
bool
);
!
ok
||
v
{
reqBody
[
"store"
]
=
true
reqBody
[
"store"
]
=
false
result
.
Modified
=
true
result
.
Modified
=
true
}
}
else
{
if
v
,
ok
:=
reqBody
[
"store"
]
.
(
bool
);
!
ok
||
v
{
reqBody
[
"store"
]
=
false
result
.
Modified
=
true
}
}
}
if
v
,
ok
:=
reqBody
[
"stream"
]
.
(
bool
);
!
ok
||
!
v
{
if
v
,
ok
:=
reqBody
[
"stream"
]
.
(
bool
);
!
ok
||
!
v
{
reqBody
[
"stream"
]
=
true
reqBody
[
"stream"
]
=
true
...
...
backend/internal/service/openai_codex_transform_test.go
View file @
d89e797b
...
@@ -11,7 +11,7 @@ import (
...
@@ -11,7 +11,7 @@ import (
)
)
func
TestApplyCodexOAuthTransform_ToolContinuationPreservesInput
(
t
*
testing
.
T
)
{
func
TestApplyCodexOAuthTransform_ToolContinuationPreservesInput
(
t
*
testing
.
T
)
{
// 续链场景:保留 item_reference 与 id,
并启用 stor
e。
// 续链场景:保留 item_reference 与 id,
但不再强制 store=tru
e。
setupCodexCache
(
t
)
setupCodexCache
(
t
)
reqBody
:=
map
[
string
]
any
{
reqBody
:=
map
[
string
]
any
{
...
@@ -25,9 +25,10 @@ func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {
...
@@ -25,9 +25,10 @@ func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {
applyCodexOAuthTransform
(
reqBody
)
applyCodexOAuthTransform
(
reqBody
)
// 未显式设置 store=true,默认为 false。
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
require
.
True
(
t
,
ok
)
require
.
True
(
t
,
ok
)
require
.
Tru
e
(
t
,
store
)
require
.
Fals
e
(
t
,
store
)
input
,
ok
:=
reqBody
[
"input"
]
.
([]
any
)
input
,
ok
:=
reqBody
[
"input"
]
.
([]
any
)
require
.
True
(
t
,
ok
)
require
.
True
(
t
,
ok
)
...
@@ -45,8 +46,8 @@ func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {
...
@@ -45,8 +46,8 @@ func TestApplyCodexOAuthTransform_ToolContinuationPreservesInput(t *testing.T) {
require
.
Equal
(
t
,
"o1"
,
second
[
"id"
])
require
.
Equal
(
t
,
"o1"
,
second
[
"id"
])
}
}
func
TestApplyCodexOAuthTransform_
ToolContinuationForcesStoreTrue
(
t
*
testing
.
T
)
{
func
TestApplyCodexOAuthTransform_
ExplicitStoreFalsePreserved
(
t
*
testing
.
T
)
{
// 续链场景:显式 store=false
也会被
强制为 true。
// 续链场景:显式 store=false
不再
强制为 true
,保持 false
。
setupCodexCache
(
t
)
setupCodexCache
(
t
)
reqBody
:=
map
[
string
]
any
{
reqBody
:=
map
[
string
]
any
{
...
@@ -62,16 +63,35 @@ func TestApplyCodexOAuthTransform_ToolContinuationForcesStoreTrue(t *testing.T)
...
@@ -62,16 +63,35 @@ func TestApplyCodexOAuthTransform_ToolContinuationForcesStoreTrue(t *testing.T)
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
require
.
True
(
t
,
ok
)
require
.
True
(
t
,
ok
)
require
.
Tru
e
(
t
,
store
)
require
.
Fals
e
(
t
,
store
)
}
}
func
TestApplyCodexOAuthTransform_
NonContinuationForcesStoreFalseAndStripsIDs
(
t
*
testing
.
T
)
{
func
TestApplyCodexOAuthTransform_
ExplicitStoreTrueForcedFalse
(
t
*
testing
.
T
)
{
//
非续链场景:强制 store=false,并移除 input 中的 id
。
//
显式 store=true 也会强制为 false
。
setupCodexCache
(
t
)
setupCodexCache
(
t
)
reqBody
:=
map
[
string
]
any
{
reqBody
:=
map
[
string
]
any
{
"model"
:
"gpt-5.1"
,
"model"
:
"gpt-5.1"
,
"store"
:
true
,
"store"
:
true
,
"input"
:
[]
any
{
map
[
string
]
any
{
"type"
:
"function_call_output"
,
"call_id"
:
"call_1"
},
},
"tool_choice"
:
"auto"
,
}
applyCodexOAuthTransform
(
reqBody
)
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
require
.
True
(
t
,
ok
)
require
.
False
(
t
,
store
)
}
func
TestApplyCodexOAuthTransform_NonContinuationDefaultsStoreFalseAndStripsIDs
(
t
*
testing
.
T
)
{
// 非续链场景:未设置 store 时默认 false,并移除 input 中的 id。
setupCodexCache
(
t
)
reqBody
:=
map
[
string
]
any
{
"model"
:
"gpt-5.1"
,
"input"
:
[]
any
{
"input"
:
[]
any
{
map
[
string
]
any
{
"type"
:
"text"
,
"id"
:
"t1"
,
"text"
:
"hi"
},
map
[
string
]
any
{
"type"
:
"text"
,
"id"
:
"t1"
,
"text"
:
"hi"
},
},
},
...
...
frontend/src/components/admin/user/UserBalanceModal.vue
View file @
d89e797b
...
@@ -3,14 +3,17 @@
...
@@ -3,14 +3,17 @@
<form
v-if=
"user"
id=
"balance-form"
@
submit.prevent=
"handleBalanceSubmit"
class=
"space-y-5"
>
<form
v-if=
"user"
id=
"balance-form"
@
submit.prevent=
"handleBalanceSubmit"
class=
"space-y-5"
>
<div
class=
"flex items-center gap-3 rounded-xl bg-gray-50 p-4 dark:bg-dark-700"
>
<div
class=
"flex items-center gap-3 rounded-xl bg-gray-50 p-4 dark:bg-dark-700"
>
<div
class=
"flex h-10 w-10 items-center justify-center rounded-full bg-primary-100"
><span
class=
"text-lg font-medium text-primary-700"
>
{{
user
.
email
.
charAt
(
0
).
toUpperCase
()
}}
</span></div>
<div
class=
"flex h-10 w-10 items-center justify-center rounded-full bg-primary-100"
><span
class=
"text-lg font-medium text-primary-700"
>
{{
user
.
email
.
charAt
(
0
).
toUpperCase
()
}}
</span></div>
<div
class=
"flex-1"
><p
class=
"font-medium text-gray-900"
>
{{
user
.
email
}}
</p><p
class=
"text-sm text-gray-500"
>
{{
t
(
'
admin.users.currentBalance
'
)
}}
: $
{{
user
.
balance
.
toFixed
(
2
)
}}
</p></div>
<div
class=
"flex-1"
><p
class=
"font-medium text-gray-900"
>
{{
user
.
email
}}
</p><p
class=
"text-sm text-gray-500"
>
{{
t
(
'
admin.users.currentBalance
'
)
}}
: $
{{
formatBalance
(
user
.
balance
)
}}
</p></div>
</div>
</div>
<div>
<div>
<label
class=
"input-label"
>
{{
operation
===
'
add
'
?
t
(
'
admin.users.depositAmount
'
)
:
t
(
'
admin.users.withdrawAmount
'
)
}}
</label>
<label
class=
"input-label"
>
{{
operation
===
'
add
'
?
t
(
'
admin.users.depositAmount
'
)
:
t
(
'
admin.users.withdrawAmount
'
)
}}
</label>
<div
class=
"relative"
><div
class=
"absolute left-3 top-1/2 -translate-y-1/2 font-medium text-gray-500"
>
$
</div><input
v-model.number=
"form.amount"
type=
"number"
step=
"0.01"
min=
"0.01"
required
class=
"input pl-8"
/></div>
<div
class=
"relative flex gap-2"
>
<div
class=
"relative flex-1"
><div
class=
"absolute left-3 top-1/2 -translate-y-1/2 font-medium text-gray-500"
>
$
</div><input
v-model.number=
"form.amount"
type=
"number"
step=
"any"
min=
"0"
required
class=
"input pl-8"
/></div>
<button
v-if=
"operation === 'subtract'"
type=
"button"
@
click=
"fillAllBalance"
class=
"btn btn-secondary whitespace-nowrap"
>
{{
t
(
'
admin.users.withdrawAll
'
)
}}
</button>
</div>
</div>
</div>
<div><label
class=
"input-label"
>
{{
t
(
'
admin.users.notes
'
)
}}
</label><textarea
v-model=
"form.notes"
rows=
"3"
class=
"input"
></textarea></div>
<div><label
class=
"input-label"
>
{{
t
(
'
admin.users.notes
'
)
}}
</label><textarea
v-model=
"form.notes"
rows=
"3"
class=
"input"
></textarea></div>
<div
v-if=
"form.amount > 0"
class=
"rounded-xl border border-blue-200 bg-blue-50 p-4"
><div
class=
"flex items-center justify-between text-sm"
><span>
{{
t
(
'
admin.users.newBalance
'
)
}}
:
</span><span
class=
"font-bold"
>
$
{{
calculateNewBalance
()
.
toFixed
(
2
)
}}
</span></div></div>
<div
v-if=
"form.amount > 0"
class=
"rounded-xl border border-blue-200 bg-blue-50 p-4"
><div
class=
"flex items-center justify-between text-sm"
><span>
{{
t
(
'
admin.users.newBalance
'
)
}}
:
</span><span
class=
"font-bold"
>
$
{{
formatBalance
(
calculateNewBalance
())
}}
</span></div></div>
</form>
</form>
<template
#footer
>
<template
#footer
>
<div
class=
"flex justify-end gap-3"
>
<div
class=
"flex justify-end gap-3"
>
...
@@ -35,11 +38,30 @@ const emit = defineEmits(['close', 'success']); const { t } = useI18n(); const a
...
@@ -35,11 +38,30 @@ const emit = defineEmits(['close', 'success']); const { t } = useI18n(); const a
const
submitting
=
ref
(
false
);
const
form
=
reactive
({
amount
:
0
,
notes
:
''
})
const
submitting
=
ref
(
false
);
const
form
=
reactive
({
amount
:
0
,
notes
:
''
})
watch
(()
=>
props
.
show
,
(
v
)
=>
{
if
(
v
)
{
form
.
amount
=
0
;
form
.
notes
=
''
}
})
watch
(()
=>
props
.
show
,
(
v
)
=>
{
if
(
v
)
{
form
.
amount
=
0
;
form
.
notes
=
''
}
})
// 格式化余额:显示完整精度,去除尾部多余的0
const
formatBalance
=
(
value
:
number
)
=>
{
if
(
value
===
0
)
return
'
0.00
'
// 最多保留8位小数,去除尾部的0
const
formatted
=
value
.
toFixed
(
8
).
replace
(
/
\.?
0+$/
,
''
)
// 确保至少有2位小数
const
parts
=
formatted
.
split
(
'
.
'
)
if
(
parts
.
length
===
1
)
return
formatted
+
'
.00
'
if
(
parts
[
1
].
length
===
1
)
return
formatted
+
'
0
'
return
formatted
}
// 填入全部余额
const
fillAllBalance
=
()
=>
{
if
(
props
.
user
)
{
form
.
amount
=
props
.
user
.
balance
}
}
const
calculateNewBalance
=
()
=>
{
const
calculateNewBalance
=
()
=>
{
if
(
!
props
.
user
)
return
0
if
(
!
props
.
user
)
return
0
const
result
=
props
.
operation
===
'
add
'
?
props
.
user
.
balance
+
form
.
amount
:
props
.
user
.
balance
-
form
.
amount
const
result
=
props
.
operation
===
'
add
'
?
props
.
user
.
balance
+
form
.
amount
:
props
.
user
.
balance
-
form
.
amount
// 避免浮点数精度问题导致的 -0.00 显示
// 避免浮点数精度问题导致的 -0.00 显示
return
result
===
0
||
Object
.
is
(
result
,
-
0
)
?
0
:
result
return
Math
.
abs
(
result
)
<
1
e
-
10
?
0
:
result
}
}
const
handleBalanceSubmit
=
async
()
=>
{
const
handleBalanceSubmit
=
async
()
=>
{
if
(
!
props
.
user
)
return
if
(
!
props
.
user
)
return
...
@@ -47,10 +69,8 @@ const handleBalanceSubmit = async () => {
...
@@ -47,10 +69,8 @@ const handleBalanceSubmit = async () => {
appStore
.
showError
(
t
(
'
admin.users.amountRequired
'
))
appStore
.
showError
(
t
(
'
admin.users.amountRequired
'
))
return
return
}
}
// 使用小数点后两位精度比较,避免浮点数精度问题
// 退款时验证金额不超过实际余额
const
amount
=
Math
.
round
(
form
.
amount
*
100
)
/
100
if
(
props
.
operation
===
'
subtract
'
&&
form
.
amount
>
props
.
user
.
balance
)
{
const
balance
=
Math
.
round
(
props
.
user
.
balance
*
100
)
/
100
if
(
props
.
operation
===
'
subtract
'
&&
amount
>
balance
)
{
appStore
.
showError
(
t
(
'
admin.users.insufficientBalance
'
))
appStore
.
showError
(
t
(
'
admin.users.insufficientBalance
'
))
return
return
}
}
...
...
frontend/src/i18n/locales/en.ts
View file @
d89e797b
...
@@ -727,6 +727,7 @@ export default {
...
@@ -727,6 +727,7 @@ export default {
withdraw
:
'
Withdraw
'
,
withdraw
:
'
Withdraw
'
,
depositAmount
:
'
Deposit Amount
'
,
depositAmount
:
'
Deposit Amount
'
,
withdrawAmount
:
'
Withdraw Amount
'
,
withdrawAmount
:
'
Withdraw Amount
'
,
withdrawAll
:
'
All
'
,
currentBalance
:
'
Current Balance
'
,
currentBalance
:
'
Current Balance
'
,
depositNotesPlaceholder
:
depositNotesPlaceholder
:
'
e.g., New user registration bonus, promotional credit, compensation, etc.
'
,
'
e.g., New user registration bonus, promotional credit, compensation, etc.
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
d89e797b
...
@@ -783,6 +783,7 @@ export default {
...
@@ -783,6 +783,7 @@ export default {
withdraw
:
'
退款
'
,
withdraw
:
'
退款
'
,
depositAmount
:
'
充值金额
'
,
depositAmount
:
'
充值金额
'
,
withdrawAmount
:
'
退款金额
'
,
withdrawAmount
:
'
退款金额
'
,
withdrawAll
:
'
全部
'
,
depositNotesPlaceholder
:
'
例如:新用户注册奖励、活动充值、补偿充值等
'
,
depositNotesPlaceholder
:
'
例如:新用户注册奖励、活动充值、补偿充值等
'
,
withdrawNotesPlaceholder
:
'
例如:服务问题退款、错误充值退回、账户注销退款等
'
,
withdrawNotesPlaceholder
:
'
例如:服务问题退款、错误充值退回、账户注销退款等
'
,
notesOptional
:
'
备注为可选项,有助于未来查账
'
,
notesOptional
:
'
备注为可选项,有助于未来查账
'
,
...
...
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