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
ecea1375
Unverified
Commit
ecea1375
authored
Mar 13, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 13, 2026
Browse files
Merge pull request #955 from DaydreamCoding/feat/gpt-training-off
feat: GPT Private设置数据不用于训练
parents
826090e0
34695acb
Changes
15
Show whitespace changes
Inline
Side-by-side
backend/cmd/server/wire.go
View file @
ecea1375
...
...
@@ -41,6 +41,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
// Server layer ProviderSet
server
.
ProviderSet
,
// Privacy client factory for OpenAI training opt-out
providePrivacyClientFactory
,
// BuildInfo provider
provideServiceBuildInfo
,
...
...
@@ -53,6 +56,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
return
nil
,
nil
}
func
providePrivacyClientFactory
()
service
.
PrivacyClientFactory
{
return
repository
.
CreatePrivacyReqClient
}
func
provideServiceBuildInfo
(
buildInfo
handler
.
BuildInfo
)
service
.
BuildInfo
{
return
service
.
BuildInfo
{
Version
:
buildInfo
.
Version
,
...
...
backend/cmd/server/wire_gen.go
View file @
ecea1375
...
...
@@ -104,7 +104,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
proxyRepository
:=
repository
.
NewProxyRepository
(
client
,
db
)
proxyExitInfoProber
:=
repository
.
NewProxyExitInfoProber
(
configConfig
)
proxyLatencyCache
:=
repository
.
NewProxyLatencyCache
(
redisClient
)
adminService
:=
service
.
NewAdminService
(
userRepository
,
groupRepository
,
accountRepository
,
soraAccountRepository
,
proxyRepository
,
apiKeyRepository
,
redeemCodeRepository
,
userGroupRateRepository
,
billingCacheService
,
proxyExitInfoProber
,
proxyLatencyCache
,
apiKeyAuthCacheInvalidator
,
client
,
settingService
,
subscriptionService
,
userSubscriptionRepository
)
privacyClientFactory
:=
providePrivacyClientFactory
()
adminService
:=
service
.
NewAdminService
(
userRepository
,
groupRepository
,
accountRepository
,
soraAccountRepository
,
proxyRepository
,
apiKeyRepository
,
redeemCodeRepository
,
userGroupRateRepository
,
billingCacheService
,
proxyExitInfoProber
,
proxyLatencyCache
,
apiKeyAuthCacheInvalidator
,
client
,
settingService
,
subscriptionService
,
userSubscriptionRepository
,
privacyClientFactory
)
concurrencyCache
:=
repository
.
ProvideConcurrencyCache
(
redisClient
,
configConfig
)
concurrencyService
:=
service
.
ProvideConcurrencyService
(
concurrencyCache
,
accountRepository
,
configConfig
)
adminUserHandler
:=
admin
.
NewUserHandler
(
adminService
,
concurrencyService
)
...
...
@@ -226,7 +227,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
opsCleanupService
:=
service
.
ProvideOpsCleanupService
(
opsRepository
,
db
,
redisClient
,
configConfig
)
opsScheduledReportService
:=
service
.
ProvideOpsScheduledReportService
(
opsService
,
userService
,
emailService
,
redisClient
,
configConfig
)
soraMediaCleanupService
:=
service
.
ProvideSoraMediaCleanupService
(
soraMediaStorage
,
configConfig
)
tokenRefreshService
:=
service
.
ProvideTokenRefreshService
(
accountRepository
,
soraAccountRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
compositeTokenCacheInvalidator
,
schedulerCache
,
configConfig
,
tempUnschedCache
)
tokenRefreshService
:=
service
.
ProvideTokenRefreshService
(
accountRepository
,
soraAccountRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
compositeTokenCacheInvalidator
,
schedulerCache
,
configConfig
,
tempUnschedCache
,
privacyClientFactory
,
proxyRepository
)
accountExpiryService
:=
service
.
ProvideAccountExpiryService
(
accountRepository
)
subscriptionExpiryService
:=
service
.
ProvideSubscriptionExpiryService
(
userSubscriptionRepository
)
scheduledTestRunnerService
:=
service
.
ProvideScheduledTestRunnerService
(
scheduledTestPlanRepository
,
scheduledTestService
,
accountTestService
,
rateLimitService
,
configConfig
)
...
...
@@ -245,6 +246,10 @@ type Application struct {
Cleanup
func
()
}
func
providePrivacyClientFactory
()
service
.
PrivacyClientFactory
{
return
repository
.
CreatePrivacyReqClient
}
func
provideServiceBuildInfo
(
buildInfo
handler
.
BuildInfo
)
service
.
BuildInfo
{
return
service
.
BuildInfo
{
Version
:
buildInfo
.
Version
,
...
...
backend/internal/handler/admin/account_handler.go
View file @
ecea1375
...
...
@@ -865,6 +865,9 @@ func (h *AccountHandler) refreshSingleAccount(ctx context.Context, account *serv
}
}
// OpenAI OAuth: 刷新成功后检查并设置 privacy_mode
h
.
adminService
.
EnsureOpenAIPrivacy
(
ctx
,
updatedAccount
)
return
updatedAccount
,
""
,
nil
}
...
...
backend/internal/handler/admin/admin_service_stub_test.go
View file @
ecea1375
...
...
@@ -429,5 +429,9 @@ func (s *stubAdminService) ResetAccountQuota(ctx context.Context, id int64) erro
return
nil
}
func
(
s
*
stubAdminService
)
EnsureOpenAIPrivacy
(
ctx
context
.
Context
,
account
*
service
.
Account
)
string
{
return
""
}
// Ensure stub implements interface.
var
_
service
.
AdminService
=
(
*
stubAdminService
)(
nil
)
backend/internal/handler/admin/openai_oauth_handler.go
View file @
ecea1375
...
...
@@ -289,6 +289,7 @@ func (h *OpenAIOAuthHandler) CreateAccountFromOAuth(c *gin.Context) {
Platform
:
platform
,
Type
:
"oauth"
,
Credentials
:
credentials
,
Extra
:
nil
,
ProxyID
:
req
.
ProxyID
,
Concurrency
:
req
.
Concurrency
,
Priority
:
req
.
Priority
,
...
...
backend/internal/repository/req_client_pool.go
View file @
ecea1375
...
...
@@ -73,3 +73,14 @@ func buildReqClientKey(opts reqClientOptions) string {
opts
.
ForceHTTP2
,
)
}
// CreatePrivacyReqClient creates an HTTP client for OpenAI privacy settings API
// This is exported for use by OpenAIPrivacyService
// Uses Chrome TLS fingerprint impersonation to bypass Cloudflare checks
func
CreatePrivacyReqClient
(
proxyURL
string
)
(
*
req
.
Client
,
error
)
{
return
getSharedReqClient
(
reqClientOptions
{
ProxyURL
:
proxyURL
,
Timeout
:
30
*
time
.
Second
,
Impersonate
:
true
,
// Enable Chrome TLS fingerprint impersonation
})
}
backend/internal/server/api_contract_test.go
View file @
ecea1375
...
...
@@ -645,7 +645,7 @@ func newContractDeps(t *testing.T) *contractDeps {
settingRepo
:=
newStubSettingRepo
()
settingService
:=
service
.
NewSettingService
(
settingRepo
,
cfg
)
adminService
:=
service
.
NewAdminService
(
userRepo
,
groupRepo
,
&
accountRepo
,
nil
,
proxyRepo
,
apiKeyRepo
,
redeemRepo
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
)
adminService
:=
service
.
NewAdminService
(
userRepo
,
groupRepo
,
&
accountRepo
,
nil
,
proxyRepo
,
apiKeyRepo
,
redeemRepo
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
)
authHandler
:=
handler
.
NewAuthHandler
(
cfg
,
nil
,
userService
,
settingService
,
nil
,
redeemService
,
nil
)
apiKeyHandler
:=
handler
.
NewAPIKeyHandler
(
apiKeyService
)
usageHandler
:=
handler
.
NewUsageHandler
(
usageService
,
apiKeyService
)
...
...
backend/internal/service/admin_service.go
View file @
ecea1375
...
...
@@ -57,6 +57,8 @@ type AdminService interface {
RefreshAccountCredentials
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
ClearAccountError
(
ctx
context
.
Context
,
id
int64
)
(
*
Account
,
error
)
SetAccountError
(
ctx
context
.
Context
,
id
int64
,
errorMsg
string
)
error
// EnsureOpenAIPrivacy 检查 OpenAI OAuth 账号 privacy_mode,未设置则尝试关闭训练数据共享并持久化。
EnsureOpenAIPrivacy
(
ctx
context
.
Context
,
account
*
Account
)
string
SetAccountSchedulable
(
ctx
context
.
Context
,
id
int64
,
schedulable
bool
)
(
*
Account
,
error
)
BulkUpdateAccounts
(
ctx
context
.
Context
,
input
*
BulkUpdateAccountsInput
)
(
*
BulkUpdateAccountsResult
,
error
)
CheckMixedChannelRisk
(
ctx
context
.
Context
,
currentAccountID
int64
,
currentAccountPlatform
string
,
groupIDs
[]
int64
)
error
...
...
@@ -433,6 +435,7 @@ type adminServiceImpl struct {
settingService
*
SettingService
defaultSubAssigner
DefaultSubscriptionAssigner
userSubRepo
UserSubscriptionRepository
privacyClientFactory
PrivacyClientFactory
}
type
userGroupRateBatchReader
interface
{
...
...
@@ -461,6 +464,7 @@ func NewAdminService(
settingService
*
SettingService
,
defaultSubAssigner
DefaultSubscriptionAssigner
,
userSubRepo
UserSubscriptionRepository
,
privacyClientFactory
PrivacyClientFactory
,
)
AdminService
{
return
&
adminServiceImpl
{
userRepo
:
userRepo
,
...
...
@@ -479,6 +483,7 @@ func NewAdminService(
settingService
:
settingService
,
defaultSubAssigner
:
defaultSubAssigner
,
userSubRepo
:
userSubRepo
,
privacyClientFactory
:
privacyClientFactory
,
}
}
...
...
@@ -2502,3 +2507,39 @@ func (e *MixedChannelError) Error() string {
func
(
s
*
adminServiceImpl
)
ResetAccountQuota
(
ctx
context
.
Context
,
id
int64
)
error
{
return
s
.
accountRepo
.
ResetQuotaUsed
(
ctx
,
id
)
}
// EnsureOpenAIPrivacy 检查 OpenAI OAuth 账号是否已设置 privacy_mode,
// 未设置则调用 disableOpenAITraining 并持久化到 Extra,返回设置的 mode 值。
func
(
s
*
adminServiceImpl
)
EnsureOpenAIPrivacy
(
ctx
context
.
Context
,
account
*
Account
)
string
{
if
account
.
Platform
!=
PlatformOpenAI
||
account
.
Type
!=
AccountTypeOAuth
{
return
""
}
if
s
.
privacyClientFactory
==
nil
{
return
""
}
if
account
.
Extra
!=
nil
{
if
_
,
ok
:=
account
.
Extra
[
"privacy_mode"
];
ok
{
return
""
}
}
token
,
_
:=
account
.
Credentials
[
"access_token"
]
.
(
string
)
if
token
==
""
{
return
""
}
var
proxyURL
string
if
account
.
ProxyID
!=
nil
{
if
p
,
err
:=
s
.
proxyRepo
.
GetByID
(
ctx
,
*
account
.
ProxyID
);
err
==
nil
&&
p
!=
nil
{
proxyURL
=
p
.
URL
()
}
}
mode
:=
disableOpenAITraining
(
ctx
,
s
.
privacyClientFactory
,
token
,
proxyURL
)
if
mode
==
""
{
return
""
}
_
=
s
.
accountRepo
.
UpdateExtra
(
ctx
,
account
.
ID
,
map
[
string
]
any
{
"privacy_mode"
:
mode
})
return
mode
}
backend/internal/service/openai_privacy_service.go
0 → 100644
View file @
ecea1375
package
service
import
(
"context"
"fmt"
"log/slog"
"strings"
"time"
"github.com/imroc/req/v3"
)
// PrivacyClientFactory creates an HTTP client for privacy API calls.
// Injected from repository layer to avoid import cycles.
type
PrivacyClientFactory
func
(
proxyURL
string
)
(
*
req
.
Client
,
error
)
const
(
openAISettingsURL
=
"https://chatgpt.com/backend-api/settings/account_user_setting"
PrivacyModeTrainingOff
=
"training_off"
PrivacyModeFailed
=
"training_set_failed"
PrivacyModeCFBlocked
=
"training_set_cf_blocked"
)
// disableOpenAITraining calls ChatGPT settings API to turn off "Improve the model for everyone".
// Returns privacy_mode value: "training_off" on success, "cf_blocked" / "failed" on failure.
func
disableOpenAITraining
(
ctx
context
.
Context
,
clientFactory
PrivacyClientFactory
,
accessToken
,
proxyURL
string
)
string
{
if
accessToken
==
""
||
clientFactory
==
nil
{
return
""
}
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
15
*
time
.
Second
)
defer
cancel
()
client
,
err
:=
clientFactory
(
proxyURL
)
if
err
!=
nil
{
slog
.
Warn
(
"openai_privacy_client_error"
,
"error"
,
err
.
Error
())
return
PrivacyModeFailed
}
resp
,
err
:=
client
.
R
()
.
SetContext
(
ctx
)
.
SetHeader
(
"Authorization"
,
"Bearer "
+
accessToken
)
.
SetHeader
(
"Origin"
,
"https://chatgpt.com"
)
.
SetHeader
(
"Referer"
,
"https://chatgpt.com/"
)
.
SetQueryParam
(
"feature"
,
"training_allowed"
)
.
SetQueryParam
(
"value"
,
"false"
)
.
Patch
(
openAISettingsURL
)
if
err
!=
nil
{
slog
.
Warn
(
"openai_privacy_request_error"
,
"error"
,
err
.
Error
())
return
PrivacyModeFailed
}
if
resp
.
StatusCode
==
403
||
resp
.
StatusCode
==
503
{
body
:=
resp
.
String
()
if
strings
.
Contains
(
body
,
"cloudflare"
)
||
strings
.
Contains
(
body
,
"cf-"
)
||
strings
.
Contains
(
body
,
"Just a moment"
)
{
slog
.
Warn
(
"openai_privacy_cf_blocked"
,
"status"
,
resp
.
StatusCode
)
return
PrivacyModeCFBlocked
}
}
if
!
resp
.
IsSuccessState
()
{
slog
.
Warn
(
"openai_privacy_failed"
,
"status"
,
resp
.
StatusCode
,
"body"
,
truncate
(
resp
.
String
(),
200
))
return
PrivacyModeFailed
}
slog
.
Info
(
"openai_privacy_training_disabled"
)
return
PrivacyModeTrainingOff
}
func
truncate
(
s
string
,
n
int
)
string
{
if
len
(
s
)
<=
n
{
return
s
}
return
s
[
:
n
]
+
fmt
.
Sprintf
(
"...(%d more)"
,
len
(
s
)
-
n
)
}
backend/internal/service/token_refresh_service.go
View file @
ecea1375
...
...
@@ -21,6 +21,10 @@ type TokenRefreshService struct {
schedulerCache
SchedulerCache
// 用于同步更新调度器缓存,解决 token 刷新后缓存不一致问题
tempUnschedCache
TempUnschedCache
// 用于清除 Redis 中的临时不可调度缓存
// OpenAI privacy: 刷新成功后检查并设置 training opt-out
privacyClientFactory
PrivacyClientFactory
proxyRepo
ProxyRepository
stopCh
chan
struct
{}
wg
sync
.
WaitGroup
}
...
...
@@ -72,6 +76,12 @@ func (s *TokenRefreshService) SetSoraAccountRepo(repo SoraAccountRepository) {
}
}
// SetPrivacyDeps 注入 OpenAI privacy opt-out 所需依赖
func
(
s
*
TokenRefreshService
)
SetPrivacyDeps
(
factory
PrivacyClientFactory
,
proxyRepo
ProxyRepository
)
{
s
.
privacyClientFactory
=
factory
s
.
proxyRepo
=
proxyRepo
}
// Start 启动后台刷新服务
func
(
s
*
TokenRefreshService
)
Start
()
{
if
!
s
.
cfg
.
Enabled
{
...
...
@@ -277,6 +287,8 @@ func (s *TokenRefreshService) refreshWithRetry(ctx context.Context, account *Acc
slog
.
Debug
(
"token_refresh.scheduler_cache_synced"
,
"account_id"
,
account
.
ID
)
}
}
// OpenAI OAuth: 刷新成功后,检查是否已设置 privacy_mode,未设置则尝试关闭训练数据共享
s
.
ensureOpenAIPrivacy
(
ctx
,
account
)
return
nil
}
...
...
@@ -341,3 +353,49 @@ func isNonRetryableRefreshError(err error) bool {
}
return
false
}
// ensureOpenAIPrivacy 检查 OpenAI OAuth 账号是否已设置 privacy_mode,
// 未设置则调用 disableOpenAITraining 并持久化结果到 Extra。
func
(
s
*
TokenRefreshService
)
ensureOpenAIPrivacy
(
ctx
context
.
Context
,
account
*
Account
)
{
if
account
.
Platform
!=
PlatformOpenAI
||
account
.
Type
!=
AccountTypeOAuth
{
return
}
if
s
.
privacyClientFactory
==
nil
{
return
}
// 已设置过则跳过
if
account
.
Extra
!=
nil
{
if
_
,
ok
:=
account
.
Extra
[
"privacy_mode"
];
ok
{
return
}
}
token
,
_
:=
account
.
Credentials
[
"access_token"
]
.
(
string
)
if
token
==
""
{
return
}
var
proxyURL
string
if
account
.
ProxyID
!=
nil
&&
s
.
proxyRepo
!=
nil
{
if
p
,
err
:=
s
.
proxyRepo
.
GetByID
(
ctx
,
*
account
.
ProxyID
);
err
==
nil
&&
p
!=
nil
{
proxyURL
=
p
.
URL
()
}
}
mode
:=
disableOpenAITraining
(
ctx
,
s
.
privacyClientFactory
,
token
,
proxyURL
)
if
mode
==
""
{
return
}
if
err
:=
s
.
accountRepo
.
UpdateExtra
(
ctx
,
account
.
ID
,
map
[
string
]
any
{
"privacy_mode"
:
mode
});
err
!=
nil
{
slog
.
Warn
(
"token_refresh.update_privacy_mode_failed"
,
"account_id"
,
account
.
ID
,
"error"
,
err
,
)
}
else
{
slog
.
Info
(
"token_refresh.privacy_mode_set"
,
"account_id"
,
account
.
ID
,
"privacy_mode"
,
mode
,
)
}
}
backend/internal/service/wire.go
View file @
ecea1375
...
...
@@ -49,10 +49,14 @@ func ProvideTokenRefreshService(
schedulerCache
SchedulerCache
,
cfg
*
config
.
Config
,
tempUnschedCache
TempUnschedCache
,
privacyClientFactory
PrivacyClientFactory
,
proxyRepo
ProxyRepository
,
)
*
TokenRefreshService
{
svc
:=
NewTokenRefreshService
(
accountRepo
,
oauthService
,
openaiOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
cacheInvalidator
,
schedulerCache
,
cfg
,
tempUnschedCache
)
// 注入 Sora 账号扩展表仓储,用于 OpenAI Token 刷新时同步 sora_accounts 表
svc
.
SetSoraAccountRepo
(
soraAccountRepo
)
// 注入 OpenAI privacy opt-out 依赖
svc
.
SetPrivacyDeps
(
privacyClientFactory
,
proxyRepo
)
svc
.
Start
()
return
svc
}
...
...
frontend/src/components/common/PlatformTypeBadge.vue
View file @
ecea1375
<
template
>
<div
class=
"inline-flex items-center overflow-hidden rounded-md text-xs font-medium"
>
<!-- Platform part -->
<div
class=
"inline-flex flex-col gap-0.5 text-xs font-medium"
>
<!-- Row 1: Platform + Type -->
<div
class=
"inline-flex items-center overflow-hidden rounded-md"
>
<span
:class=
"['inline-flex items-center gap-1 px-2 py-1', platformClass]"
>
<PlatformIcon
:platform=
"platform"
size=
"xs"
/>
<span>
{{
platformLabel
}}
</span>
</span>
<!-- Type part -->
<span
:class=
"['inline-flex items-center gap-1 px-1.5 py-1', typeClass]"
>
<!-- OAuth icon -->
<svg
...
...
@@ -28,23 +28,40 @@
<Icon
v-else
name=
"key"
size=
"xs"
/>
<span>
{{
typeLabel
}}
</span>
</span>
<!-- Plan type part (optional) -->
<span
v-if=
"planLabel"
:class=
"['inline-flex items-center gap-1 px-1.5 py-1 border-l border-white/20', typeClass]"
>
</div>
<!-- Row 2: Plan type + Privacy mode (only if either exists) -->
<div
v-if=
"planLabel || privacyBadge"
class=
"inline-flex items-center overflow-hidden rounded-md"
>
<span
v-if=
"planLabel"
:class=
"['inline-flex items-center gap-1 px-1.5 py-1', typeClass]"
>
<span>
{{
planLabel
}}
</span>
</span>
<span
v-if=
"privacyBadge"
:class=
"['inline-flex items-center gap-1 px-1.5 py-1', privacyBadge.class]"
:title=
"privacyBadge.title"
>
<svg
class=
"h-3 w-3"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"2"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
:d=
"privacyBadge.icon"
/>
</svg>
<span>
{{
privacyBadge
.
label
}}
</span>
</span>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
type
{
AccountPlatform
,
AccountType
}
from
'
@/types
'
import
PlatformIcon
from
'
./PlatformIcon.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
const
{
t
}
=
useI18n
()
interface
Props
{
platform
:
AccountPlatform
type
:
AccountType
planType
?:
string
privacyMode
?:
string
}
const
props
=
defineProps
<
Props
>
()
...
...
@@ -119,4 +136,21 @@ const typeClass = computed(() => {
}
return
'
bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400
'
})
// Privacy badge — shows different states for OpenAI OAuth training setting
const
privacyBadge
=
computed
(()
=>
{
if
(
props
.
platform
!==
'
openai
'
||
props
.
type
!==
'
oauth
'
||
!
props
.
privacyMode
)
return
null
const
shieldCheck
=
'
M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z
'
const
shieldX
=
'
M12 9v3.75m0-10.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285zM12 18h.008v.008H12V18z
'
switch
(
props
.
privacyMode
)
{
case
'
training_off
'
:
return
{
label
:
'
Privacy
'
,
icon
:
shieldCheck
,
title
:
t
(
'
admin.accounts.privacyTrainingOff
'
),
class
:
'
bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400
'
}
case
'
training_set_cf_blocked
'
:
return
{
label
:
'
CF
'
,
icon
:
shieldX
,
title
:
t
(
'
admin.accounts.privacyCfBlocked
'
),
class
:
'
bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400
'
}
case
'
training_set_failed
'
:
return
{
label
:
'
Fail
'
,
icon
:
shieldX
,
title
:
t
(
'
admin.accounts.privacyFailed
'
),
class
:
'
bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400
'
}
default
:
return
null
}
})
</
script
>
frontend/src/i18n/locales/en.ts
View file @
ecea1375
...
...
@@ -1743,6 +1743,9 @@ export default {
expiresAt
:
'
Expires At
'
,
actions
:
'
Actions
'
},
privacyTrainingOff
:
'
Training data sharing disabled
'
,
privacyCfBlocked
:
'
Blocked by Cloudflare, training may still be on
'
,
privacyFailed
:
'
Failed to disable training
'
,
// Capacity status tooltips
capacity
:
{
windowCost
:
{
...
...
frontend/src/i18n/locales/zh.ts
View file @
ecea1375
...
...
@@ -1792,6 +1792,9 @@ export default {
expiresAt
:
'
过期时间
'
,
actions
:
'
操作
'
},
privacyTrainingOff
:
'
已关闭训练数据共享
'
,
privacyCfBlocked
:
'
被 Cloudflare 拦截,训练可能仍开启
'
,
privacyFailed
:
'
关闭训练数据共享失败
'
,
// 容量状态提示
capacity
:
{
windowCost
:
{
...
...
frontend/src/views/admin/AccountsView.vue
View file @
ecea1375
...
...
@@ -171,7 +171,7 @@
<
span
v
-
else
class
=
"
text-sm text-gray-400 dark:text-dark-500
"
>-<
/span
>
<
/template
>
<
template
#
cell
-
platform_type
=
"
{ row
}
"
>
<
PlatformTypeBadge
:
platform
=
"
row.platform
"
:
type
=
"
row.type
"
:
plan
-
type
=
"
row.credentials?.plan_type
"
/>
<
PlatformTypeBadge
:
platform
=
"
row.platform
"
:
type
=
"
row.type
"
:
plan
-
type
=
"
row.credentials?.plan_type
"
:
privacy
-
mode
=
"
row.extra?.privacy_mode
"
/>
<
/template
>
<
template
#
cell
-
capacity
=
"
{ row
}
"
>
<
AccountCapacityCell
:
account
=
"
row
"
/>
...
...
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