mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-21 15:14:46 +08:00
Merge tag 'v0.1.90' into merge/upstream-v0.1.90
注册邮箱域名白名单策略上线,后台大数据场景性能大幅优化。 - 注册邮箱域名白名单:支持管理员配置允许注册的邮箱域名策略 - Keys 页面表单筛选:用户 /keys 页面支持按条件筛选 API Key - Settings 页面分 Tab 拆分:管理后台设置页面按功能模块分 Tab 展示 - 后台大数据场景加载性能优化:仪表盘/用户/账号/Ops 页面大数据集加载显著提速 - Usage 大表分页优化:默认避免全量 COUNT(*),大幅降低分页查询耗时 - 消除重复的 normalizeAccountIDList,补充新增组件的单元测试 - 清理无用文件和过时文档,精简项目结构 - EmailVerifyView 硬编码英文字符串替换为 i18n 调用 - 修复 Anthropic 平台无限流重置时间的 429 误标记账号限流问题 - 修复自定义菜单页面管理员视角菜单不生效问题 - 修复 Ops 错误详情弹窗未展示真实上游 payload 的问题 - 修复充值/订阅菜单 icon 显示问题 # Conflicts: # .gitignore # backend/cmd/server/VERSION # backend/ent/group.go # backend/ent/runtime/runtime.go # backend/ent/schema/group.go # backend/go.sum # backend/internal/handler/admin/account_handler.go # backend/internal/handler/admin/dashboard_handler.go # backend/internal/pkg/usagestats/usage_log_types.go # backend/internal/repository/group_repo.go # backend/internal/repository/usage_log_repo.go # backend/internal/server/middleware/security_headers.go # backend/internal/server/router.go # backend/internal/service/account_usage_service.go # backend/internal/service/admin_service_bulk_update_test.go # backend/internal/service/dashboard_service.go # backend/internal/service/gateway_service.go # frontend/src/api/admin/dashboard.ts # frontend/src/components/account/BulkEditAccountModal.vue # frontend/src/components/charts/GroupDistributionChart.vue # frontend/src/components/layout/AppSidebar.vue # frontend/src/i18n/locales/en.ts # frontend/src/i18n/locales/zh.ts # frontend/src/views/admin/GroupsView.vue # frontend/src/views/admin/SettingsView.vue # frontend/src/views/admin/UsageView.vue # frontend/src/views/user/PurchaseSubscriptionView.vue
This commit is contained in:
@@ -270,6 +270,7 @@ export default {
|
||||
redeemCodes: 'Redeem Codes',
|
||||
ops: 'Ops',
|
||||
promoCodes: 'Promo Codes',
|
||||
dataManagement: 'Data Management',
|
||||
settings: 'Settings',
|
||||
myAccount: 'My Account',
|
||||
lightMode: 'Light Mode',
|
||||
@@ -311,6 +312,9 @@ export default {
|
||||
passwordMinLength: 'Password must be at least 6 characters',
|
||||
loginFailed: 'Login failed. Please check your credentials and try again.',
|
||||
registrationFailed: 'Registration failed. Please try again.',
|
||||
emailSuffixNotAllowed: 'This email domain is not allowed for registration.',
|
||||
emailSuffixNotAllowedWithAllowed:
|
||||
'This email domain is not allowed. Allowed domains: {suffixes}',
|
||||
loginSuccess: 'Login successful! Welcome back.',
|
||||
accountCreatedSuccess: 'Account created successfully! Welcome to {siteName}.',
|
||||
reloginRequired: 'Session expired. Please log in again.',
|
||||
@@ -325,6 +329,16 @@ export default {
|
||||
sendingCode: 'Sending...',
|
||||
clickToResend: 'Click to resend code',
|
||||
resendCode: 'Resend verification code',
|
||||
sendCodeDesc: "We'll send a verification code to",
|
||||
codeSentSuccess: 'Verification code sent! Please check your inbox.',
|
||||
verifying: 'Verifying...',
|
||||
verifyAndCreate: 'Verify & Create Account',
|
||||
resendCountdown: 'Resend code in {countdown}s',
|
||||
backToRegistration: 'Back to registration',
|
||||
sendCodeFailed: 'Failed to send verification code. Please try again.',
|
||||
verifyFailed: 'Verification failed. Please try again.',
|
||||
codeRequired: 'Verification code is required',
|
||||
invalidCode: 'Please enter a valid 6-digit code',
|
||||
promoCodeLabel: 'Promo Code',
|
||||
promoCodePlaceholder: 'Enter promo code (optional)',
|
||||
promoCodeValid: 'Valid! You will receive ${amount} bonus balance',
|
||||
@@ -407,9 +421,12 @@ export default {
|
||||
day: 'Day',
|
||||
hour: 'Hour',
|
||||
modelDistribution: 'Model Distribution',
|
||||
groupDistribution: 'Group Usage Distribution',
|
||||
tokenUsageTrend: 'Token Usage Trend',
|
||||
noDataAvailable: 'No data available',
|
||||
model: 'Model',
|
||||
group: 'Group',
|
||||
noGroup: 'No Group',
|
||||
requests: 'Requests',
|
||||
tokens: 'Tokens',
|
||||
actual: 'Actual',
|
||||
@@ -440,6 +457,9 @@ export default {
|
||||
keys: {
|
||||
title: 'API Keys',
|
||||
description: 'Manage your API keys and access tokens',
|
||||
searchPlaceholder: 'Search name or key...',
|
||||
allGroups: 'All Groups',
|
||||
allStatus: 'All Status',
|
||||
createKey: 'Create API Key',
|
||||
editKey: 'Edit API Key',
|
||||
deleteKey: 'Delete API Key',
|
||||
@@ -500,6 +520,7 @@ export default {
|
||||
claudeCode: 'Claude Code',
|
||||
geminiCli: 'Gemini CLI',
|
||||
codexCli: 'Codex CLI',
|
||||
codexCliWs: 'Codex CLI (WebSocket)',
|
||||
opencode: 'OpenCode',
|
||||
},
|
||||
antigravity: {
|
||||
@@ -555,6 +576,19 @@ export default {
|
||||
resetQuotaConfirmMessage: 'Are you sure you want to reset the used quota (${used}) for key "{name}" to 0? This action cannot be undone.',
|
||||
quotaResetSuccess: 'Quota reset successfully',
|
||||
failedToResetQuota: 'Failed to reset quota',
|
||||
rateLimitColumn: 'Rate Limit',
|
||||
rateLimitSection: 'Rate Limit',
|
||||
resetUsage: 'Reset',
|
||||
rateLimit5h: '5-Hour Limit (USD)',
|
||||
rateLimit1d: 'Daily Limit (USD)',
|
||||
rateLimit7d: '7-Day Limit (USD)',
|
||||
rateLimitHint: 'Set the maximum spending for this key within each time window. 0 = unlimited.',
|
||||
rateLimitUsage: 'Rate Limit Usage',
|
||||
resetRateLimitUsage: 'Reset Rate Limit Usage',
|
||||
resetRateLimitTitle: 'Confirm Reset Rate Limit',
|
||||
resetRateLimitConfirmMessage: 'Are you sure you want to reset the rate limit usage for key "{name}"? All time window usage will be reset to zero. This action cannot be undone.',
|
||||
rateLimitResetSuccess: 'Rate limit usage reset successfully',
|
||||
failedToResetRateLimit: 'Failed to reset rate limit usage',
|
||||
expiration: 'Expiration',
|
||||
expiresInDays: '{days} days',
|
||||
extendDays: '+{days} days',
|
||||
@@ -613,8 +647,10 @@ export default {
|
||||
firstToken: 'First Token',
|
||||
duration: 'Duration',
|
||||
time: 'Time',
|
||||
ws: 'WS',
|
||||
stream: 'Stream',
|
||||
sync: 'Sync',
|
||||
unknown: 'Unknown',
|
||||
in: 'In',
|
||||
out: 'Out',
|
||||
cacheRead: 'Read',
|
||||
@@ -828,11 +864,12 @@ export default {
|
||||
day: 'Day',
|
||||
hour: 'Hour',
|
||||
modelDistribution: 'Model Distribution',
|
||||
groupDistribution: 'Group Distribution',
|
||||
groupDistribution: 'Group Usage Distribution',
|
||||
tokenUsageTrend: 'Token Usage Trend',
|
||||
userUsageTrend: 'User Usage Trend (Top 12)',
|
||||
model: 'Model',
|
||||
group: 'Group',
|
||||
noGroup: 'No Group',
|
||||
requests: 'Requests',
|
||||
tokens: 'Tokens',
|
||||
actual: 'Actual',
|
||||
@@ -842,6 +879,181 @@ export default {
|
||||
failedToLoad: 'Failed to load dashboard statistics'
|
||||
},
|
||||
|
||||
dataManagement: {
|
||||
title: 'Data Management',
|
||||
description: 'Manage data management agent status, object storage settings, and backup jobs in one place',
|
||||
agent: {
|
||||
title: 'Data Management Agent Status',
|
||||
description: 'The system probes a fixed Unix socket and enables data management only when reachable.',
|
||||
enabled: 'Data management agent is ready. Data management operations are available.',
|
||||
disabled: 'Data management agent is unavailable. Only diagnostic information is available now.',
|
||||
socketPath: 'Socket Path',
|
||||
version: 'Version',
|
||||
status: 'Status',
|
||||
uptime: 'Uptime',
|
||||
reasonLabel: 'Unavailable Reason',
|
||||
reason: {
|
||||
DATA_MANAGEMENT_AGENT_SOCKET_MISSING: 'Data management socket file is missing',
|
||||
DATA_MANAGEMENT_AGENT_UNAVAILABLE: 'Data management agent is unreachable',
|
||||
BACKUP_AGENT_SOCKET_MISSING: 'Backup socket file is missing',
|
||||
BACKUP_AGENT_UNAVAILABLE: 'Backup agent is unreachable',
|
||||
UNKNOWN: 'Unknown reason'
|
||||
}
|
||||
},
|
||||
sections: {
|
||||
config: {
|
||||
title: 'Backup Configuration',
|
||||
description: 'Configure backup source, retention policy, and S3 settings.'
|
||||
},
|
||||
s3: {
|
||||
title: 'S3 Object Storage',
|
||||
description: 'Configure and test uploads of backup artifacts to a standard S3-compatible storage.'
|
||||
},
|
||||
backup: {
|
||||
title: 'Backup Operations',
|
||||
description: 'Trigger PostgreSQL, Redis, and full backup jobs.'
|
||||
},
|
||||
history: {
|
||||
title: 'Backup History',
|
||||
description: 'Review backup job status, errors, and artifact metadata.'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
sourceMode: 'Source Mode',
|
||||
backupRoot: 'Backup Root',
|
||||
activePostgresProfile: 'Active PostgreSQL Profile',
|
||||
activeRedisProfile: 'Active Redis Profile',
|
||||
activeS3Profile: 'Active S3 Profile',
|
||||
retentionDays: 'Retention Days',
|
||||
keepLast: 'Keep Last Jobs',
|
||||
uploadToS3: 'Upload to S3',
|
||||
useActivePostgresProfile: 'Use Active PostgreSQL Profile',
|
||||
useActiveRedisProfile: 'Use Active Redis Profile',
|
||||
useActiveS3Profile: 'Use Active Profile',
|
||||
idempotencyKey: 'Idempotency Key (Optional)',
|
||||
secretConfigured: 'Configured already, leave empty to keep unchanged',
|
||||
source: {
|
||||
profileID: 'Profile ID (Unique)',
|
||||
profileName: 'Profile Name',
|
||||
setActive: 'Set as active after creation'
|
||||
},
|
||||
postgres: {
|
||||
title: 'PostgreSQL',
|
||||
host: 'Host',
|
||||
port: 'Port',
|
||||
user: 'User',
|
||||
password: 'Password',
|
||||
database: 'Database',
|
||||
sslMode: 'SSL Mode',
|
||||
containerName: 'Container Name (docker_exec mode)'
|
||||
},
|
||||
redis: {
|
||||
title: 'Redis',
|
||||
addr: 'Address (host:port)',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
db: 'Database Index',
|
||||
containerName: 'Container Name (docker_exec mode)'
|
||||
},
|
||||
s3: {
|
||||
enabled: 'Enable S3 Upload',
|
||||
profileID: 'Profile ID (Unique)',
|
||||
profileName: 'Profile Name',
|
||||
endpoint: 'Endpoint (Optional)',
|
||||
region: 'Region',
|
||||
bucket: 'Bucket',
|
||||
accessKeyID: 'Access Key ID',
|
||||
secretAccessKey: 'Secret Access Key',
|
||||
prefix: 'Object Prefix',
|
||||
forcePathStyle: 'Force Path Style',
|
||||
useSSL: 'Use SSL',
|
||||
setActive: 'Set as active after creation'
|
||||
}
|
||||
},
|
||||
sourceProfiles: {
|
||||
createTitle: 'Create Source Profile',
|
||||
editTitle: 'Edit Source Profile',
|
||||
empty: 'No source profiles yet, create one first',
|
||||
deleteConfirm: 'Delete source profile {profileID}?',
|
||||
columns: {
|
||||
profile: 'Profile',
|
||||
active: 'Active',
|
||||
connection: 'Connection',
|
||||
database: 'Database',
|
||||
updatedAt: 'Updated At',
|
||||
actions: 'Actions'
|
||||
}
|
||||
},
|
||||
s3Profiles: {
|
||||
createTitle: 'Create S3 Profile',
|
||||
editTitle: 'Edit S3 Profile',
|
||||
empty: 'No S3 profiles yet, create one first',
|
||||
editHint: 'Click "Edit" to modify profile details in the right drawer.',
|
||||
deleteConfirm: 'Delete S3 profile {profileID}?',
|
||||
columns: {
|
||||
profile: 'Profile',
|
||||
active: 'Active',
|
||||
storage: 'Storage',
|
||||
updatedAt: 'Updated At',
|
||||
actions: 'Actions'
|
||||
}
|
||||
},
|
||||
history: {
|
||||
total: '{count} jobs',
|
||||
empty: 'No backup jobs yet',
|
||||
columns: {
|
||||
jobID: 'Job ID',
|
||||
type: 'Type',
|
||||
status: 'Status',
|
||||
triggeredBy: 'Triggered By',
|
||||
pgProfile: 'PostgreSQL Profile',
|
||||
redisProfile: 'Redis Profile',
|
||||
s3Profile: 'S3 Profile',
|
||||
finishedAt: 'Finished At',
|
||||
artifact: 'Artifact',
|
||||
error: 'Error'
|
||||
},
|
||||
status: {
|
||||
queued: 'Queued',
|
||||
running: 'Running',
|
||||
succeeded: 'Succeeded',
|
||||
failed: 'Failed',
|
||||
partial_succeeded: 'Partial Succeeded'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
refresh: 'Refresh Status',
|
||||
disabledHint: 'Start datamanagementd first and ensure the socket is reachable.',
|
||||
reloadConfig: 'Reload Config',
|
||||
reloadSourceProfiles: 'Reload Source Profiles',
|
||||
reloadProfiles: 'Reload Profiles',
|
||||
newSourceProfile: 'New Source Profile',
|
||||
saveConfig: 'Save Config',
|
||||
configSaved: 'Configuration saved',
|
||||
testS3: 'Test S3 Connection',
|
||||
s3TestOK: 'S3 connection test succeeded',
|
||||
s3TestFailed: 'S3 connection test failed',
|
||||
newProfile: 'New Profile',
|
||||
saveProfile: 'Save Profile',
|
||||
activateProfile: 'Activate',
|
||||
profileIDRequired: 'Profile ID is required',
|
||||
profileNameRequired: 'Profile name is required',
|
||||
profileSelectRequired: 'Select a profile to edit first',
|
||||
profileCreated: 'S3 profile created',
|
||||
profileSaved: 'S3 profile saved',
|
||||
profileActivated: 'S3 profile activated',
|
||||
profileDeleted: 'S3 profile deleted',
|
||||
sourceProfileCreated: 'Source profile created',
|
||||
sourceProfileSaved: 'Source profile saved',
|
||||
sourceProfileActivated: 'Source profile activated',
|
||||
sourceProfileDeleted: 'Source profile deleted',
|
||||
createBackup: 'Create Backup Job',
|
||||
jobCreated: 'Backup job created: {jobID} ({status})',
|
||||
refreshJobs: 'Refresh Jobs',
|
||||
loadMore: 'Load More'
|
||||
}
|
||||
},
|
||||
|
||||
// Users
|
||||
users: {
|
||||
title: 'User Management',
|
||||
@@ -900,6 +1112,9 @@ export default {
|
||||
noApiKeys: 'This user has no API keys',
|
||||
group: 'Group',
|
||||
none: 'None',
|
||||
groupChangedSuccess: 'Group updated successfully',
|
||||
groupChangedWithGrant: 'Group updated. User auto-granted access to "{group}"',
|
||||
groupChangeFailed: 'Failed to update group',
|
||||
noUsersYet: 'No users yet',
|
||||
createFirstUser: 'Create your first user to get started.',
|
||||
userCreated: 'User created successfully',
|
||||
@@ -915,6 +1130,8 @@ export default {
|
||||
failedToLoadApiKeys: 'Failed to load user API keys',
|
||||
emailRequired: 'Please enter email',
|
||||
concurrencyMin: 'Concurrency must be at least 1',
|
||||
soraStorageQuota: 'Sora Storage Quota',
|
||||
soraStorageQuotaHint: 'In GB, 0 means use group or system default quota',
|
||||
amountRequired: 'Please enter a valid amount',
|
||||
insufficientBalance: 'Insufficient balance',
|
||||
deleteConfirm: "Are you sure you want to delete '{email}'? This action cannot be undone.",
|
||||
@@ -1144,7 +1361,9 @@ export default {
|
||||
image360: 'Image 360px ($)',
|
||||
image540: 'Image 540px ($)',
|
||||
video: 'Video (standard) ($)',
|
||||
videoHd: 'Video (Pro-HD) ($)'
|
||||
videoHd: 'Video (Pro-HD) ($)',
|
||||
storageQuota: 'Storage Quota',
|
||||
storageQuotaHint: 'In GB, set the Sora storage quota for users in this group. 0 means use system default'
|
||||
},
|
||||
claudeCode: {
|
||||
title: 'Claude Code Client Restriction',
|
||||
@@ -1389,6 +1608,10 @@ export default {
|
||||
codeAssist: 'Code Assist',
|
||||
antigravityOauth: 'Antigravity OAuth',
|
||||
antigravityApikey: 'Connect via Base URL + API Key',
|
||||
soraApiKey: 'API Key / Upstream',
|
||||
soraApiKeyHint: 'Connect to another Sub2API or compatible API',
|
||||
soraBaseUrlRequired: 'Sora API Key account requires a Base URL',
|
||||
soraBaseUrlInvalidScheme: 'Base URL must start with http:// or https://',
|
||||
upstream: 'Upstream',
|
||||
upstreamDesc: 'Connect via Base URL + API Key'
|
||||
},
|
||||
@@ -1437,7 +1660,19 @@ export default {
|
||||
sessions: {
|
||||
full: 'Active sessions full, new sessions must wait (idle timeout: {idle} min)',
|
||||
normal: 'Active sessions normal (idle timeout: {idle} min)'
|
||||
}
|
||||
},
|
||||
rpm: {
|
||||
full: 'RPM limit reached',
|
||||
warning: 'RPM approaching limit',
|
||||
normal: 'RPM normal',
|
||||
tieredNormal: 'RPM limit (Tiered) - Normal',
|
||||
tieredWarning: 'RPM limit (Tiered) - Approaching limit',
|
||||
tieredStickyOnly: 'RPM limit (Tiered) - Sticky only | Buffer: {buffer}',
|
||||
tieredBlocked: 'RPM limit (Tiered) - Blocked | Buffer: {buffer}',
|
||||
stickyExemptNormal: 'RPM limit (Sticky Exempt) - Normal',
|
||||
stickyExemptWarning: 'RPM limit (Sticky Exempt) - Approaching limit',
|
||||
stickyExemptOver: 'RPM limit (Sticky Exempt) - Over limit, sticky only'
|
||||
},
|
||||
},
|
||||
tempUnschedulable: {
|
||||
title: 'Temp Unschedulable',
|
||||
@@ -1554,6 +1789,24 @@ export default {
|
||||
oauthPassthrough: 'Auto passthrough (auth only)',
|
||||
oauthPassthroughDesc:
|
||||
'When enabled, this OpenAI account uses automatic passthrough: the gateway forwards request/response as-is and only swaps auth, while keeping billing/concurrency/audit and necessary safety filtering.',
|
||||
responsesWebsocketsV2: 'Responses WebSocket v2',
|
||||
responsesWebsocketsV2Desc:
|
||||
'Disabled by default. Enable to allow responses_websockets_v2 capability (still gated by global and account-type switches).',
|
||||
wsMode: 'WS mode',
|
||||
wsModeDesc: 'Only applies to the current OpenAI account type.',
|
||||
wsModeOff: 'Off (off)',
|
||||
wsModeShared: 'Shared (shared)',
|
||||
wsModeDedicated: 'Dedicated (dedicated)',
|
||||
wsModeConcurrencyHint:
|
||||
'When WS mode is enabled, account concurrency becomes the WS connection pool limit for this account.',
|
||||
oauthResponsesWebsocketsV2: 'OAuth WebSocket Mode',
|
||||
oauthResponsesWebsocketsV2Desc:
|
||||
'Only applies to OpenAI OAuth. This account can use OpenAI WebSocket Mode only when enabled.',
|
||||
apiKeyResponsesWebsocketsV2: 'API Key WebSocket Mode',
|
||||
apiKeyResponsesWebsocketsV2Desc:
|
||||
'Only applies to OpenAI API Key. This account can use OpenAI WebSocket Mode only when enabled.',
|
||||
responsesWebsocketsV2PassthroughHint:
|
||||
'Automatic passthrough is currently enabled: it only affects HTTP passthrough and does not disable WS mode.',
|
||||
codexCLIOnly: 'Codex official clients only',
|
||||
codexCLIOnlyDesc:
|
||||
'Only applies to OpenAI OAuth. When enabled, only Codex official client families are allowed; when disabled, the gateway bypasses this restriction and keeps existing behavior.',
|
||||
@@ -1634,6 +1887,27 @@ export default {
|
||||
idleTimeoutPlaceholder: '5',
|
||||
idleTimeoutHint: 'Sessions will be released after idle timeout'
|
||||
},
|
||||
rpmLimit: {
|
||||
label: 'RPM Limit',
|
||||
hint: 'Limit requests per minute to protect upstream accounts',
|
||||
baseRpm: 'Base RPM',
|
||||
baseRpmPlaceholder: '15',
|
||||
baseRpmHint: 'Max requests per minute, 0 or empty means no limit',
|
||||
strategy: 'RPM Strategy',
|
||||
strategyTiered: 'Tiered Model',
|
||||
strategyStickyExempt: 'Sticky Exempt',
|
||||
strategyTieredHint: 'Green → Yellow → Sticky only → Blocked, progressive throttling',
|
||||
strategyStickyExemptHint: 'Only sticky sessions allowed when over limit',
|
||||
strategyHint: 'Tiered: gradually restrict when exceeded; Sticky Exempt: existing sessions unrestricted',
|
||||
stickyBuffer: 'Sticky Buffer',
|
||||
stickyBufferPlaceholder: 'Default: 20% of base RPM',
|
||||
stickyBufferHint: 'Extra requests allowed for sticky sessions after exceeding base RPM. Leave empty to use default (20% of base RPM, min 1)',
|
||||
userMsgQueue: 'User Message Rate Control',
|
||||
userMsgQueueHint: 'Rate-limit user messages to avoid triggering upstream RPM limits',
|
||||
umqModeOff: 'Off',
|
||||
umqModeThrottle: 'Throttle',
|
||||
umqModeSerialize: 'Serialize',
|
||||
},
|
||||
tlsFingerprint: {
|
||||
label: 'TLS Fingerprint Simulation',
|
||||
hint: 'Simulate Node.js/Claude Code client TLS fingerprint'
|
||||
@@ -1763,6 +2037,15 @@ export default {
|
||||
sessionTokenAuth: 'Manual ST Input',
|
||||
sessionTokenDesc: 'Enter your existing Sora Session Token(s). Supports batch input (one per line). The system will automatically validate and create accounts.',
|
||||
sessionTokenPlaceholder: 'Paste your Sora Session Token...\nSupports multiple, one per line',
|
||||
sessionTokenRawLabel: 'Raw Input',
|
||||
sessionTokenRawPlaceholder: 'Paste /api/auth/session raw payload or Session Token...',
|
||||
sessionTokenRawHint: 'You can paste full JSON. The system will auto-parse ST and AT.',
|
||||
openSessionUrl: 'Open Fetch URL',
|
||||
copySessionUrl: 'Copy URL',
|
||||
sessionUrlHint: 'This URL usually returns AT. If sessionToken is absent, copy __Secure-next-auth.session-token from browser cookies as ST.',
|
||||
parsedSessionTokensLabel: 'Parsed ST',
|
||||
parsedSessionTokensEmpty: 'No ST parsed. Please check your input.',
|
||||
parsedAccessTokensLabel: 'Parsed AT',
|
||||
validating: 'Validating...',
|
||||
validateAndCreate: 'Validate & Create Account',
|
||||
pleaseEnterRefreshToken: 'Please enter Refresh Token',
|
||||
@@ -2013,6 +2296,7 @@ export default {
|
||||
selectTestModel: 'Select Test Model',
|
||||
testModel: 'Test model',
|
||||
testPrompt: 'Prompt: "hi"',
|
||||
soraUpstreamBaseUrlHint: 'Upstream Sora service URL (another Sub2API instance or compatible API)',
|
||||
soraTestHint: 'Sora test runs connectivity and capability checks (/backend/me, subscription, Sora2 invite and remaining quota).',
|
||||
soraTestTarget: 'Target: Sora account capability',
|
||||
soraTestMode: 'Mode: Connectivity + Capability checks',
|
||||
@@ -2103,6 +2387,8 @@ export default {
|
||||
dataExportConfirm: 'Confirm Export',
|
||||
dataExported: 'Data exported successfully',
|
||||
dataExportFailed: 'Failed to export data',
|
||||
copyProxyUrl: 'Copy Proxy URL',
|
||||
urlCopied: 'Proxy URL copied',
|
||||
searchProxies: 'Search proxies...',
|
||||
allProtocols: 'All Protocols',
|
||||
allStatus: 'All Status',
|
||||
@@ -2116,6 +2402,7 @@ export default {
|
||||
name: 'Name',
|
||||
protocol: 'Protocol',
|
||||
address: 'Address',
|
||||
auth: 'Auth',
|
||||
location: 'Location',
|
||||
status: 'Status',
|
||||
accounts: 'Accounts',
|
||||
@@ -3255,6 +3542,15 @@ export default {
|
||||
settings: {
|
||||
title: 'System Settings',
|
||||
description: 'Manage registration, email verification, default values, and SMTP settings',
|
||||
tabs: {
|
||||
general: 'General',
|
||||
security: 'Security',
|
||||
users: 'Users',
|
||||
gateway: 'Gateway',
|
||||
email: 'Email',
|
||||
},
|
||||
emailTabDisabledTitle: 'Email Verification Not Enabled',
|
||||
emailTabDisabledHint: 'Enable email verification in the Security tab to configure SMTP settings.',
|
||||
registration: {
|
||||
title: 'Registration Settings',
|
||||
description: 'Control user registration and verification',
|
||||
@@ -3262,6 +3558,11 @@ export default {
|
||||
enableRegistrationHint: 'Allow new users to register',
|
||||
emailVerification: 'Email Verification',
|
||||
emailVerificationHint: 'Require email verification for new registrations',
|
||||
emailSuffixWhitelist: 'Email Domain Whitelist',
|
||||
emailSuffixWhitelistHint:
|
||||
"Only email addresses from the specified domains can register (for example, {'@'}qq.com, {'@'}gmail.com)",
|
||||
emailSuffixWhitelistPlaceholder: 'example.com',
|
||||
emailSuffixWhitelistInputHint: 'Leave empty for no restriction',
|
||||
promoCode: 'Promo Code',
|
||||
promoCodeHint: 'Allow users to use promo codes during registration',
|
||||
invitationCode: 'Invitation Code Registration',
|
||||
@@ -3310,7 +3611,29 @@ export default {
|
||||
defaultBalance: 'Default Balance',
|
||||
defaultBalanceHint: 'Initial balance for new users',
|
||||
defaultConcurrency: 'Default Concurrency',
|
||||
defaultConcurrencyHint: 'Maximum concurrent requests for new users'
|
||||
defaultConcurrencyHint: 'Maximum concurrent requests for new users',
|
||||
defaultSubscriptions: 'Default Subscriptions',
|
||||
defaultSubscriptionsHint: 'Auto-assign these subscriptions when a new user is created or registered',
|
||||
addDefaultSubscription: 'Add Default Subscription',
|
||||
defaultSubscriptionsEmpty: 'No default subscriptions configured.',
|
||||
defaultSubscriptionsDuplicate:
|
||||
'Duplicate subscription group: {groupId}. Each group can only appear once.',
|
||||
subscriptionGroup: 'Subscription Group',
|
||||
subscriptionValidityDays: 'Validity (days)'
|
||||
},
|
||||
claudeCode: {
|
||||
title: 'Claude Code Settings',
|
||||
description: 'Control Claude Code client access requirements',
|
||||
minVersion: 'Minimum Version',
|
||||
minVersionPlaceholder: 'e.g. 2.1.63',
|
||||
minVersionHint:
|
||||
'Reject Claude Code clients below this version (semver format). Leave empty to disable version check.'
|
||||
},
|
||||
scheduling: {
|
||||
title: 'Gateway Scheduling Settings',
|
||||
description: 'Control API Key scheduling behavior',
|
||||
allowUngroupedKey: 'Allow Ungrouped Key Scheduling',
|
||||
allowUngroupedKeyHint: 'When disabled, API Keys not assigned to any group cannot make requests (403 Forbidden). Keep disabled to ensure all Keys belong to a specific group.'
|
||||
},
|
||||
site: {
|
||||
title: 'Site Settings',
|
||||
@@ -3358,6 +3681,33 @@ export default {
|
||||
integrationDoc: 'Payment Integration Docs',
|
||||
integrationDocHint: 'Covers endpoint specs, idempotency semantics, and code samples'
|
||||
},
|
||||
soraClient: {
|
||||
title: 'Sora Client',
|
||||
description: 'Control whether to show the Sora client entry in the sidebar',
|
||||
enabled: 'Enable Sora Client',
|
||||
enabledHint: 'When enabled, the Sora entry will be shown in the sidebar for users to access Sora features'
|
||||
},
|
||||
customMenu: {
|
||||
title: 'Custom Menu Pages',
|
||||
description: 'Add custom iframe pages to the sidebar navigation. Each page can be visible to regular users or administrators.',
|
||||
itemLabel: 'Menu Item #{n}',
|
||||
name: 'Menu Name',
|
||||
namePlaceholder: 'e.g. Help Center',
|
||||
url: 'Page URL',
|
||||
urlPlaceholder: 'https://example.com/page',
|
||||
iconSvg: 'SVG Icon',
|
||||
iconSvgPlaceholder: '<svg>...</svg>',
|
||||
iconPreview: 'Icon Preview',
|
||||
uploadSvg: 'Upload SVG',
|
||||
removeSvg: 'Remove',
|
||||
visibility: 'Visible To',
|
||||
visibilityUser: 'Regular Users',
|
||||
visibilityAdmin: 'Administrators',
|
||||
add: 'Add Menu Item',
|
||||
remove: 'Remove',
|
||||
moveUp: 'Move Up',
|
||||
moveDown: 'Move Down',
|
||||
},
|
||||
smtp: {
|
||||
title: 'SMTP Settings',
|
||||
description: 'Configure email sending for verification codes',
|
||||
@@ -3429,6 +3779,60 @@ export default {
|
||||
securityWarning: 'Warning: This key provides full admin access. Keep it secure.',
|
||||
usage: 'Usage: Add to request header - x-api-key: <your-admin-api-key>'
|
||||
},
|
||||
soraS3: {
|
||||
title: 'Sora S3 Storage',
|
||||
description: 'Manage multiple Sora S3 endpoints and switch the active profile',
|
||||
newProfile: 'New Profile',
|
||||
reloadProfiles: 'Reload Profiles',
|
||||
empty: 'No Sora S3 profiles yet, create one first',
|
||||
createTitle: 'Create Sora S3 Profile',
|
||||
editTitle: 'Edit Sora S3 Profile',
|
||||
profileID: 'Profile ID',
|
||||
profileName: 'Profile Name',
|
||||
setActive: 'Set as active after creation',
|
||||
saveProfile: 'Save Profile',
|
||||
activateProfile: 'Activate',
|
||||
profileCreated: 'Sora S3 profile created',
|
||||
profileSaved: 'Sora S3 profile saved',
|
||||
profileDeleted: 'Sora S3 profile deleted',
|
||||
profileActivated: 'Sora S3 active profile switched',
|
||||
profileIDRequired: 'Profile ID is required',
|
||||
profileNameRequired: 'Profile name is required',
|
||||
profileSelectRequired: 'Please select a profile first',
|
||||
endpointRequired: 'S3 endpoint is required when enabled',
|
||||
bucketRequired: 'Bucket is required when enabled',
|
||||
accessKeyRequired: 'Access Key ID is required when enabled',
|
||||
deleteConfirm: 'Delete Sora S3 profile {profileID}?',
|
||||
columns: {
|
||||
profile: 'Profile',
|
||||
active: 'Active',
|
||||
endpoint: 'Endpoint',
|
||||
bucket: 'Bucket',
|
||||
quota: 'Default Quota',
|
||||
updatedAt: 'Updated At',
|
||||
actions: 'Actions'
|
||||
},
|
||||
enabled: 'Enable S3 Storage',
|
||||
enabledHint: 'When enabled, Sora generated media files will be automatically uploaded to S3 storage',
|
||||
endpoint: 'S3 Endpoint',
|
||||
region: 'Region',
|
||||
bucket: 'Bucket',
|
||||
prefix: 'Object Prefix',
|
||||
accessKeyId: 'Access Key ID',
|
||||
secretAccessKey: 'Secret Access Key',
|
||||
secretConfigured: '(Configured, leave blank to keep)',
|
||||
cdnUrl: 'CDN URL',
|
||||
cdnUrlHint: 'Optional. When configured, files are accessed via CDN URL instead of presigned URLs',
|
||||
forcePathStyle: 'Force Path Style',
|
||||
defaultQuota: 'Default Storage Quota',
|
||||
defaultQuotaHint: 'Default quota when not specified at user or group level. 0 means unlimited',
|
||||
testConnection: 'Test Connection',
|
||||
testing: 'Testing...',
|
||||
testSuccess: 'S3 connection test successful',
|
||||
testFailed: 'S3 connection test failed',
|
||||
saved: 'Sora S3 settings saved successfully',
|
||||
saveFailed: 'Failed to save Sora S3 settings'
|
||||
},
|
||||
streamTimeout: {
|
||||
title: 'Stream Timeout Handling',
|
||||
description: 'Configure account handling strategy when upstream response times out',
|
||||
@@ -3592,6 +3996,16 @@ export default {
|
||||
'The administrator enabled the entry but has not configured a recharge/subscription URL. Please contact admin.'
|
||||
},
|
||||
|
||||
// Custom Page (iframe embed)
|
||||
customPage: {
|
||||
title: 'Custom Page',
|
||||
openInNewTab: 'Open in new tab',
|
||||
notFoundTitle: 'Page not found',
|
||||
notFoundDesc: 'This custom page does not exist or has been removed.',
|
||||
notConfiguredTitle: 'Page URL not configured',
|
||||
notConfiguredDesc: 'The URL for this custom page has not been properly configured.',
|
||||
},
|
||||
|
||||
// Announcements Page
|
||||
announcements: {
|
||||
title: 'Announcements',
|
||||
@@ -3787,5 +4201,93 @@ export default {
|
||||
description: '<div style="line-height: 1.7;"><p style="margin-bottom: 12px;">Click to confirm and create your API key.</p><div style="padding: 8px 12px; background: #fee2e2; border-left: 3px solid #ef4444; border-radius: 4px; font-size: 13px; margin-bottom: 12px;"><b>⚠️ Important:</b><ul style="margin: 8px 0 0 16px;"><li>Copy the key (sk-xxx) immediately after creation</li><li>Key is only shown once, need to regenerate if lost</li></ul></div><p style="padding: 8px 12px; background: #f0fdf4; border-left: 3px solid #10b981; border-radius: 4px; font-size: 13px;"><b>🚀 How to Use:</b><br/>Configure the key in any OpenAI-compatible client (like ChatBox, OpenCat, etc.) and start using!</p><p style="margin-top: 12px; color: #10b981; font-weight: 600;">👉 Click "Create" button</p></div>'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Sora Studio
|
||||
sora: {
|
||||
title: 'Sora Studio',
|
||||
description: 'Generate videos and images with Sora AI',
|
||||
notEnabled: 'Feature Not Available',
|
||||
notEnabledDesc: 'The Sora Studio feature has not been enabled by the administrator. Please contact your admin.',
|
||||
tabGenerate: 'Generate',
|
||||
tabLibrary: 'Library',
|
||||
noActiveGenerations: 'No active generations',
|
||||
startGenerating: 'Enter a prompt below to start creating',
|
||||
storage: 'Storage',
|
||||
promptPlaceholder: 'Describe what you want to create...',
|
||||
generate: 'Generate',
|
||||
generating: 'Generating...',
|
||||
selectModel: 'Select Model',
|
||||
statusPending: 'Pending',
|
||||
statusGenerating: 'Generating',
|
||||
statusCompleted: 'Completed',
|
||||
statusFailed: 'Failed',
|
||||
statusCancelled: 'Cancelled',
|
||||
cancel: 'Cancel',
|
||||
delete: 'Delete',
|
||||
save: 'Save to Cloud',
|
||||
saved: 'Saved',
|
||||
retry: 'Retry',
|
||||
download: 'Download',
|
||||
justNow: 'Just now',
|
||||
minutesAgo: '{n} min ago',
|
||||
hoursAgo: '{n} hr ago',
|
||||
noSavedWorks: 'No saved works',
|
||||
saveWorksHint: 'Save your completed generations to the library',
|
||||
filterAll: 'All',
|
||||
filterVideo: 'Video',
|
||||
filterImage: 'Image',
|
||||
confirmDelete: 'Are you sure you want to delete this work?',
|
||||
loading: 'Loading...',
|
||||
loadMore: 'Load More',
|
||||
noStorageWarningTitle: 'No Storage Configured',
|
||||
noStorageWarningDesc: 'Generated content is only available via temporary upstream links that expire in ~15 minutes. Consider configuring S3 storage.',
|
||||
mediaTypeVideo: 'Video',
|
||||
mediaTypeImage: 'Image',
|
||||
notificationCompleted: 'Generation Complete',
|
||||
notificationFailed: 'Generation Failed',
|
||||
notificationCompletedBody: 'Your {model} task has completed',
|
||||
notificationFailedBody: 'Your {model} task has failed',
|
||||
upstreamExpiresSoon: 'Expiring soon',
|
||||
upstreamExpired: 'Link expired',
|
||||
upstreamCountdown: '{time} remaining',
|
||||
previewTitle: 'Preview',
|
||||
closePreview: 'Close',
|
||||
beforeUnloadWarning: 'You have unsaved generated content. Are you sure you want to leave?',
|
||||
downloadTitle: 'Download Generated Content',
|
||||
downloadExpirationWarning: 'This link expires in approximately 15 minutes. Please download and save promptly.',
|
||||
downloadNow: 'Download Now',
|
||||
referenceImage: 'Reference Image',
|
||||
removeImage: 'Remove',
|
||||
imageTooLarge: 'Image size cannot exceed 20MB',
|
||||
// Sora dark theme additions
|
||||
welcomeTitle: 'Turn your imagination into video',
|
||||
welcomeSubtitle: 'Enter a description and Sora will create realistic videos or images for you. Try the examples below to get started.',
|
||||
queueTasks: 'tasks',
|
||||
queueWaiting: 'Queued',
|
||||
waiting: 'Waiting',
|
||||
waited: 'Waited',
|
||||
errorCategory: 'Content Policy Violation',
|
||||
savedToCloud: 'Saved to Cloud',
|
||||
downloadLocal: 'Download',
|
||||
canDownload: 'to download',
|
||||
regenrate: 'Regenerate',
|
||||
creatorPlaceholder: 'Describe the video or image you want to create...',
|
||||
videoModels: 'Video Models',
|
||||
imageModels: 'Image Models',
|
||||
noStorageConfigured: 'No Storage',
|
||||
selectCredential: 'Select Credential',
|
||||
apiKeys: 'API Keys',
|
||||
subscriptions: 'Subscriptions',
|
||||
subscription: 'Subscription',
|
||||
noCredentialHint: 'Please create an API Key or contact admin for subscription',
|
||||
uploadReference: 'Upload reference image',
|
||||
generatingCount: 'Generating {current}/{max}',
|
||||
noStorageToastMessage: 'Cloud storage is not configured. Please use "Download" to save files after generation, otherwise they will be lost.',
|
||||
galleryCount: '{count} works',
|
||||
galleryEmptyTitle: 'No works yet',
|
||||
galleryEmptyDesc: 'Your creations will be displayed here. Go to the generate page to start your first creation.',
|
||||
startCreating: 'Start Creating',
|
||||
yesterday: 'Yesterday'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +312,8 @@ export default {
|
||||
passwordMinLength: '密码至少需要 6 个字符',
|
||||
loginFailed: '登录失败,请检查您的凭据后重试。',
|
||||
registrationFailed: '注册失败,请重试。',
|
||||
emailSuffixNotAllowed: '该邮箱域名不在允许注册范围内。',
|
||||
emailSuffixNotAllowedWithAllowed: '该邮箱域名不被允许。可用域名:{suffixes}',
|
||||
loginSuccess: '登录成功!欢迎回来。',
|
||||
accountCreatedSuccess: '账户创建成功!欢迎使用 {siteName}。',
|
||||
reloginRequired: '会话已过期,请重新登录。',
|
||||
@@ -326,6 +328,16 @@ export default {
|
||||
sendingCode: '发送中...',
|
||||
clickToResend: '点击重新发送验证码',
|
||||
resendCode: '重新发送验证码',
|
||||
sendCodeDesc: '我们将发送验证码到',
|
||||
codeSentSuccess: '验证码已发送!请查收您的邮箱。',
|
||||
verifying: '验证中...',
|
||||
verifyAndCreate: '验证并创建账户',
|
||||
resendCountdown: '{countdown}秒后可重新发送',
|
||||
backToRegistration: '返回注册',
|
||||
sendCodeFailed: '发送验证码失败,请重试。',
|
||||
verifyFailed: '验证失败,请重试。',
|
||||
codeRequired: '请输入验证码',
|
||||
invalidCode: '请输入有效的6位验证码',
|
||||
promoCodeLabel: '优惠码',
|
||||
promoCodePlaceholder: '输入优惠码(可选)',
|
||||
promoCodeValid: '有效!注册后将获得 ${amount} 赠送余额',
|
||||
@@ -414,6 +426,7 @@ export default {
|
||||
noDataAvailable: '暂无数据',
|
||||
model: '模型',
|
||||
group: '分组',
|
||||
noGroup: '无分组',
|
||||
requests: '请求',
|
||||
tokens: 'Token',
|
||||
actual: '实际',
|
||||
@@ -444,6 +457,9 @@ export default {
|
||||
keys: {
|
||||
title: 'API 密钥',
|
||||
description: '管理您的 API 密钥和访问令牌',
|
||||
searchPlaceholder: '搜索名称或Key...',
|
||||
allGroups: '全部分组',
|
||||
allStatus: '全部状态',
|
||||
createKey: '创建密钥',
|
||||
editKey: '编辑密钥',
|
||||
deleteKey: '删除密钥',
|
||||
@@ -565,6 +581,19 @@ export default {
|
||||
resetQuotaConfirmMessage: '确定要将密钥 "{name}" 的已用额度(${used})重置为 0 吗?此操作不可撤销。',
|
||||
quotaResetSuccess: '额度重置成功',
|
||||
failedToResetQuota: '重置额度失败',
|
||||
rateLimitColumn: '速率限制',
|
||||
rateLimitSection: '速率限制',
|
||||
resetUsage: '重置',
|
||||
rateLimit5h: '5小时限额 (USD)',
|
||||
rateLimit1d: '日限额 (USD)',
|
||||
rateLimit7d: '7天限额 (USD)',
|
||||
rateLimitHint: '设置此密钥在指定时间窗口内的最大消费额。0 = 无限制。',
|
||||
rateLimitUsage: '速率限制用量',
|
||||
resetRateLimitUsage: '重置速率限制用量',
|
||||
resetRateLimitTitle: '确认重置速率限制',
|
||||
resetRateLimitConfirmMessage: '确定要重置密钥 "{name}" 的速率限制用量吗?所有时间窗口的已用额度将归零。此操作不可撤销。',
|
||||
rateLimitResetSuccess: '速率限制已重置',
|
||||
failedToResetRateLimit: '重置速率限制失败',
|
||||
expiration: '密钥有效期',
|
||||
expiresInDays: '{days} 天',
|
||||
extendDays: '+{days} 天',
|
||||
@@ -853,6 +882,7 @@ export default {
|
||||
noDataAvailable: '暂无数据',
|
||||
model: '模型',
|
||||
group: '分组',
|
||||
noGroup: '无分组',
|
||||
requests: '请求',
|
||||
tokens: 'Token',
|
||||
cache: '缓存',
|
||||
@@ -2005,7 +2035,12 @@ export default {
|
||||
strategyHint: '三区模型: 超限后逐步限制; 粘性豁免: 已有会话不受限',
|
||||
stickyBuffer: '粘性缓冲区',
|
||||
stickyBufferPlaceholder: '默认: base RPM 的 20%',
|
||||
stickyBufferHint: '超过 base RPM 后,粘性会话额外允许的请求数。为空则使用默认值(base RPM 的 20%,最小为 1)'
|
||||
stickyBufferHint: '超过 base RPM 后,粘性会话额外允许的请求数。为空则使用默认值(base RPM 的 20%,最小为 1)',
|
||||
userMsgQueue: '用户消息限速',
|
||||
userMsgQueueHint: '对用户消息施加发送限制,避免触发上游 RPM 限制',
|
||||
umqModeOff: '关闭',
|
||||
umqModeThrottle: '软性限速',
|
||||
umqModeSerialize: '串行队列',
|
||||
},
|
||||
tlsFingerprint: {
|
||||
label: 'TLS 指纹模拟',
|
||||
@@ -2457,6 +2492,7 @@ export default {
|
||||
name: '名称',
|
||||
protocol: '协议',
|
||||
address: '地址',
|
||||
auth: '认证',
|
||||
location: '地理位置',
|
||||
status: '状态',
|
||||
accounts: '账号数',
|
||||
@@ -2484,6 +2520,8 @@ export default {
|
||||
allStatuses: '全部状态'
|
||||
},
|
||||
// Additional keys used in ProxiesView
|
||||
copyProxyUrl: '复制代理 URL',
|
||||
urlCopied: '代理 URL 已复制',
|
||||
allProtocols: '全部协议',
|
||||
allStatus: '全部状态',
|
||||
searchProxies: '搜索代理...',
|
||||
@@ -3665,6 +3703,15 @@ export default {
|
||||
settings: {
|
||||
title: '系统设置',
|
||||
description: '管理注册、邮箱验证、默认值和 SMTP 设置',
|
||||
tabs: {
|
||||
general: '通用设置',
|
||||
security: '安全与认证',
|
||||
users: '用户默认值',
|
||||
gateway: '网关服务',
|
||||
email: '邮件设置',
|
||||
},
|
||||
emailTabDisabledTitle: '邮箱验证未启用',
|
||||
emailTabDisabledHint: '请在「安全与认证」选项卡中启用邮箱验证后,再配置 SMTP 设置。',
|
||||
registration: {
|
||||
title: '注册设置',
|
||||
description: '控制用户注册和验证',
|
||||
@@ -3672,6 +3719,11 @@ export default {
|
||||
enableRegistrationHint: '允许新用户注册',
|
||||
emailVerification: '邮箱验证',
|
||||
emailVerificationHint: '新用户注册时需要验证邮箱',
|
||||
emailSuffixWhitelist: '邮箱域名白名单',
|
||||
emailSuffixWhitelistHint:
|
||||
"仅允许使用指定域名的邮箱注册账号(例如 {'@'}qq.com, {'@'}gmail.com)",
|
||||
emailSuffixWhitelistPlaceholder: 'example.com',
|
||||
emailSuffixWhitelistInputHint: '留空则不限制',
|
||||
promoCode: '优惠码',
|
||||
promoCodeHint: '允许用户在注册时使用优惠码',
|
||||
invitationCode: '邀请码注册',
|
||||
@@ -3720,7 +3772,27 @@ export default {
|
||||
defaultBalance: '默认余额',
|
||||
defaultBalanceHint: '新用户的初始余额',
|
||||
defaultConcurrency: '默认并发数',
|
||||
defaultConcurrencyHint: '新用户的最大并发请求数'
|
||||
defaultConcurrencyHint: '新用户的最大并发请求数',
|
||||
defaultSubscriptions: '默认订阅列表',
|
||||
defaultSubscriptionsHint: '新用户创建或注册时自动分配这些订阅',
|
||||
addDefaultSubscription: '添加默认订阅',
|
||||
defaultSubscriptionsEmpty: '未配置默认订阅。新用户不会自动获得订阅套餐。',
|
||||
defaultSubscriptionsDuplicate: '默认订阅存在重复分组:{groupId}。每个分组只能出现一次。',
|
||||
subscriptionGroup: '订阅分组',
|
||||
subscriptionValidityDays: '有效期(天)'
|
||||
},
|
||||
claudeCode: {
|
||||
title: 'Claude Code 设置',
|
||||
description: '控制 Claude Code 客户端访问要求',
|
||||
minVersion: '最低版本号',
|
||||
minVersionPlaceholder: '例如 2.1.63',
|
||||
minVersionHint: '拒绝低于此版本的 Claude Code 客户端请求(semver 格式)。留空则不检查版本。'
|
||||
},
|
||||
scheduling: {
|
||||
title: '网关调度设置',
|
||||
description: '控制 API Key 的调度行为',
|
||||
allowUngroupedKey: '允许未分组 Key 调度',
|
||||
allowUngroupedKeyHint: '关闭后,未分配到任何分组的 API Key 将无法发起请求(返回 403)。建议保持关闭以确保所有 Key 都归属明确的分组。'
|
||||
},
|
||||
site: {
|
||||
title: '站点设置',
|
||||
@@ -3776,6 +3848,27 @@ export default {
|
||||
enabled: '启用 Sora 客户端',
|
||||
enabledHint: '开启后,侧边栏将显示 Sora 入口,用户可访问 Sora 功能'
|
||||
},
|
||||
customMenu: {
|
||||
title: '自定义菜单页面',
|
||||
description: '添加自定义 iframe 页面到侧边栏导航。每个页面可以设置为普通用户或管理员可见。',
|
||||
itemLabel: '菜单项 #{n}',
|
||||
name: '菜单名称',
|
||||
namePlaceholder: '如:帮助中心',
|
||||
url: '页面 URL',
|
||||
urlPlaceholder: 'https://example.com/page',
|
||||
iconSvg: 'SVG 图标',
|
||||
iconSvgPlaceholder: '<svg>...</svg>',
|
||||
iconPreview: '图标预览',
|
||||
uploadSvg: '上传 SVG',
|
||||
removeSvg: '清除',
|
||||
visibility: '可见角色',
|
||||
visibilityUser: '普通用户',
|
||||
visibilityAdmin: '管理员',
|
||||
add: '添加菜单项',
|
||||
remove: '删除',
|
||||
moveUp: '上移',
|
||||
moveDown: '下移',
|
||||
},
|
||||
smtp: {
|
||||
title: 'SMTP 设置',
|
||||
description: '配置用于发送验证码的邮件服务',
|
||||
@@ -4062,6 +4155,16 @@ export default {
|
||||
notConfiguredDesc: '管理员已开启入口,但尚未配置充值/订阅链接,请联系管理员。'
|
||||
},
|
||||
|
||||
// Custom Page (iframe embed)
|
||||
customPage: {
|
||||
title: '自定义页面',
|
||||
openInNewTab: '新窗口打开',
|
||||
notFoundTitle: '页面不存在',
|
||||
notFoundDesc: '该自定义页面不存在或已被删除。',
|
||||
notConfiguredTitle: '页面链接未配置',
|
||||
notConfiguredDesc: '该自定义页面的 URL 未正确配置。',
|
||||
},
|
||||
|
||||
// Announcements Page
|
||||
announcements: {
|
||||
title: '公告',
|
||||
|
||||
Reference in New Issue
Block a user