refactor(settings): 规范化缩写词命名并优化前端帮助界面

- 后端:将 Smtp/Api/Doc 字段改为 SMTP/API/Doc(遵循 Go 命名规范)
- 前端:添加 Gemini 帮助按钮,简化配额说明展示
This commit is contained in:
IanShaw027
2026-01-04 17:02:38 +08:00
parent a185ad1144
commit 2632a7102d
8 changed files with 364 additions and 374 deletions

View File

@@ -36,22 +36,22 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
response.Success(c, dto.SystemSettings{ response.Success(c, dto.SystemSettings{
RegistrationEnabled: settings.RegistrationEnabled, RegistrationEnabled: settings.RegistrationEnabled,
EmailVerifyEnabled: settings.EmailVerifyEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled,
SmtpHost: settings.SmtpHost, SMTPHost: settings.SMTPHost,
SmtpPort: settings.SmtpPort, SMTPPort: settings.SMTPPort,
SmtpUsername: settings.SmtpUsername, SMTPUsername: settings.SMTPUsername,
SmtpPassword: settings.SmtpPassword, SMTPPassword: settings.SMTPPassword,
SmtpFrom: settings.SmtpFrom, SMTPFrom: settings.SMTPFrom,
SmtpFromName: settings.SmtpFromName, SMTPFromName: settings.SMTPFromName,
SmtpUseTLS: settings.SmtpUseTLS, SMTPUseTLS: settings.SMTPUseTLS,
TurnstileEnabled: settings.TurnstileEnabled, TurnstileEnabled: settings.TurnstileEnabled,
TurnstileSiteKey: settings.TurnstileSiteKey, TurnstileSiteKey: settings.TurnstileSiteKey,
TurnstileSecretKey: settings.TurnstileSecretKey, TurnstileSecretKey: settings.TurnstileSecretKey,
SiteName: settings.SiteName, SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo, SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle, SiteSubtitle: settings.SiteSubtitle,
ApiBaseUrl: settings.ApiBaseUrl, APIBaseURL: settings.APIBaseURL,
ContactInfo: settings.ContactInfo, ContactInfo: settings.ContactInfo,
DocUrl: settings.DocUrl, DocURL: settings.DocURL,
DefaultConcurrency: settings.DefaultConcurrency, DefaultConcurrency: settings.DefaultConcurrency,
DefaultBalance: settings.DefaultBalance, DefaultBalance: settings.DefaultBalance,
EnableModelFallback: settings.EnableModelFallback, EnableModelFallback: settings.EnableModelFallback,
@@ -69,13 +69,13 @@ type UpdateSettingsRequest struct {
EmailVerifyEnabled bool `json:"email_verify_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"`
// 邮件服务设置 // 邮件服务设置
SmtpHost string `json:"smtp_host"` SMTPHost string `json:"smtp_host"`
SmtpPort int `json:"smtp_port"` SMTPPort int `json:"smtp_port"`
SmtpUsername string `json:"smtp_username"` SMTPUsername string `json:"smtp_username"`
SmtpPassword string `json:"smtp_password"` SMTPPassword string `json:"smtp_password"`
SmtpFrom string `json:"smtp_from_email"` SMTPFrom string `json:"smtp_from_email"`
SmtpFromName string `json:"smtp_from_name"` SMTPFromName string `json:"smtp_from_name"`
SmtpUseTLS bool `json:"smtp_use_tls"` SMTPUseTLS bool `json:"smtp_use_tls"`
// Cloudflare Turnstile 设置 // Cloudflare Turnstile 设置
TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"`
@@ -86,9 +86,9 @@ type UpdateSettingsRequest struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
ApiBaseUrl string `json:"api_base_url"` APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocUrl string `json:"doc_url"` DocURL string `json:"doc_url"`
// 默认配置 // 默认配置
DefaultConcurrency int `json:"default_concurrency"` DefaultConcurrency int `json:"default_concurrency"`
@@ -118,8 +118,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if req.DefaultBalance < 0 { if req.DefaultBalance < 0 {
req.DefaultBalance = 0 req.DefaultBalance = 0
} }
if req.SmtpPort <= 0 { if req.SMTPPort <= 0 {
req.SmtpPort = 587 req.SMTPPort = 587
} }
// Turnstile 参数验证 // Turnstile 参数验证
@@ -155,22 +155,22 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
settings := &service.SystemSettings{ settings := &service.SystemSettings{
RegistrationEnabled: req.RegistrationEnabled, RegistrationEnabled: req.RegistrationEnabled,
EmailVerifyEnabled: req.EmailVerifyEnabled, EmailVerifyEnabled: req.EmailVerifyEnabled,
SmtpHost: req.SmtpHost, SMTPHost: req.SMTPHost,
SmtpPort: req.SmtpPort, SMTPPort: req.SMTPPort,
SmtpUsername: req.SmtpUsername, SMTPUsername: req.SMTPUsername,
SmtpPassword: req.SmtpPassword, SMTPPassword: req.SMTPPassword,
SmtpFrom: req.SmtpFrom, SMTPFrom: req.SMTPFrom,
SmtpFromName: req.SmtpFromName, SMTPFromName: req.SMTPFromName,
SmtpUseTLS: req.SmtpUseTLS, SMTPUseTLS: req.SMTPUseTLS,
TurnstileEnabled: req.TurnstileEnabled, TurnstileEnabled: req.TurnstileEnabled,
TurnstileSiteKey: req.TurnstileSiteKey, TurnstileSiteKey: req.TurnstileSiteKey,
TurnstileSecretKey: req.TurnstileSecretKey, TurnstileSecretKey: req.TurnstileSecretKey,
SiteName: req.SiteName, SiteName: req.SiteName,
SiteLogo: req.SiteLogo, SiteLogo: req.SiteLogo,
SiteSubtitle: req.SiteSubtitle, SiteSubtitle: req.SiteSubtitle,
ApiBaseUrl: req.ApiBaseUrl, APIBaseURL: req.APIBaseURL,
ContactInfo: req.ContactInfo, ContactInfo: req.ContactInfo,
DocUrl: req.DocUrl, DocURL: req.DocURL,
DefaultConcurrency: req.DefaultConcurrency, DefaultConcurrency: req.DefaultConcurrency,
DefaultBalance: req.DefaultBalance, DefaultBalance: req.DefaultBalance,
EnableModelFallback: req.EnableModelFallback, EnableModelFallback: req.EnableModelFallback,
@@ -195,22 +195,22 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
response.Success(c, dto.SystemSettings{ response.Success(c, dto.SystemSettings{
RegistrationEnabled: updatedSettings.RegistrationEnabled, RegistrationEnabled: updatedSettings.RegistrationEnabled,
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled, EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
SmtpHost: updatedSettings.SmtpHost, SMTPHost: updatedSettings.SMTPHost,
SmtpPort: updatedSettings.SmtpPort, SMTPPort: updatedSettings.SMTPPort,
SmtpUsername: updatedSettings.SmtpUsername, SMTPUsername: updatedSettings.SMTPUsername,
SmtpPassword: updatedSettings.SmtpPassword, SMTPPassword: updatedSettings.SMTPPassword,
SmtpFrom: updatedSettings.SmtpFrom, SMTPFrom: updatedSettings.SMTPFrom,
SmtpFromName: updatedSettings.SmtpFromName, SMTPFromName: updatedSettings.SMTPFromName,
SmtpUseTLS: updatedSettings.SmtpUseTLS, SMTPUseTLS: updatedSettings.SMTPUseTLS,
TurnstileEnabled: updatedSettings.TurnstileEnabled, TurnstileEnabled: updatedSettings.TurnstileEnabled,
TurnstileSiteKey: updatedSettings.TurnstileSiteKey, TurnstileSiteKey: updatedSettings.TurnstileSiteKey,
TurnstileSecretKey: updatedSettings.TurnstileSecretKey, TurnstileSecretKey: updatedSettings.TurnstileSecretKey,
SiteName: updatedSettings.SiteName, SiteName: updatedSettings.SiteName,
SiteLogo: updatedSettings.SiteLogo, SiteLogo: updatedSettings.SiteLogo,
SiteSubtitle: updatedSettings.SiteSubtitle, SiteSubtitle: updatedSettings.SiteSubtitle,
ApiBaseUrl: updatedSettings.ApiBaseUrl, APIBaseURL: updatedSettings.APIBaseURL,
ContactInfo: updatedSettings.ContactInfo, ContactInfo: updatedSettings.ContactInfo,
DocUrl: updatedSettings.DocUrl, DocURL: updatedSettings.DocURL,
DefaultConcurrency: updatedSettings.DefaultConcurrency, DefaultConcurrency: updatedSettings.DefaultConcurrency,
DefaultBalance: updatedSettings.DefaultBalance, DefaultBalance: updatedSettings.DefaultBalance,
EnableModelFallback: updatedSettings.EnableModelFallback, EnableModelFallback: updatedSettings.EnableModelFallback,
@@ -221,30 +221,30 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}) })
} }
// TestSmtpRequest 测试SMTP连接请求 // TestSMTPRequest 测试SMTP连接请求
type TestSmtpRequest struct { type TestSMTPRequest struct {
SmtpHost string `json:"smtp_host" binding:"required"` SMTPHost string `json:"smtp_host" binding:"required"`
SmtpPort int `json:"smtp_port"` SMTPPort int `json:"smtp_port"`
SmtpUsername string `json:"smtp_username"` SMTPUsername string `json:"smtp_username"`
SmtpPassword string `json:"smtp_password"` SMTPPassword string `json:"smtp_password"`
SmtpUseTLS bool `json:"smtp_use_tls"` SMTPUseTLS bool `json:"smtp_use_tls"`
} }
// TestSmtpConnection 测试SMTP连接 // TestSmtpConnection 测试SMTP连接
// POST /api/v1/admin/settings/test-smtp // POST /api/v1/admin/settings/test-smtp
func (h *SettingHandler) TestSmtpConnection(c *gin.Context) { func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
var req TestSmtpRequest var req TestSMTPRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error()) response.BadRequest(c, "Invalid request: "+err.Error())
return return
} }
if req.SmtpPort <= 0 { if req.SMTPPort <= 0 {
req.SmtpPort = 587 req.SMTPPort = 587
} }
// 如果未提供密码,从数据库获取已保存的密码 // 如果未提供密码,从数据库获取已保存的密码
password := req.SmtpPassword password := req.SMTPPassword
if password == "" { if password == "" {
savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context()) savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
if err == nil && savedConfig != nil { if err == nil && savedConfig != nil {
@@ -253,11 +253,11 @@ func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
} }
config := &service.SmtpConfig{ config := &service.SmtpConfig{
Host: req.SmtpHost, Host: req.SMTPHost,
Port: req.SmtpPort, Port: req.SMTPPort,
Username: req.SmtpUsername, Username: req.SMTPUsername,
Password: password, Password: password,
UseTLS: req.SmtpUseTLS, UseTLS: req.SMTPUseTLS,
} }
err := h.emailService.TestSmtpConnectionWithConfig(config) err := h.emailService.TestSmtpConnectionWithConfig(config)
@@ -272,13 +272,13 @@ func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
// SendTestEmailRequest 发送测试邮件请求 // SendTestEmailRequest 发送测试邮件请求
type SendTestEmailRequest struct { type SendTestEmailRequest struct {
Email string `json:"email" binding:"required,email"` Email string `json:"email" binding:"required,email"`
SmtpHost string `json:"smtp_host" binding:"required"` SMTPHost string `json:"smtp_host" binding:"required"`
SmtpPort int `json:"smtp_port"` SMTPPort int `json:"smtp_port"`
SmtpUsername string `json:"smtp_username"` SMTPUsername string `json:"smtp_username"`
SmtpPassword string `json:"smtp_password"` SMTPPassword string `json:"smtp_password"`
SmtpFrom string `json:"smtp_from_email"` SMTPFrom string `json:"smtp_from_email"`
SmtpFromName string `json:"smtp_from_name"` SMTPFromName string `json:"smtp_from_name"`
SmtpUseTLS bool `json:"smtp_use_tls"` SMTPUseTLS bool `json:"smtp_use_tls"`
} }
// SendTestEmail 发送测试邮件 // SendTestEmail 发送测试邮件
@@ -290,12 +290,12 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
return return
} }
if req.SmtpPort <= 0 { if req.SMTPPort <= 0 {
req.SmtpPort = 587 req.SMTPPort = 587
} }
// 如果未提供密码,从数据库获取已保存的密码 // 如果未提供密码,从数据库获取已保存的密码
password := req.SmtpPassword password := req.SMTPPassword
if password == "" { if password == "" {
savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context()) savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
if err == nil && savedConfig != nil { if err == nil && savedConfig != nil {
@@ -304,13 +304,13 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
} }
config := &service.SmtpConfig{ config := &service.SmtpConfig{
Host: req.SmtpHost, Host: req.SMTPHost,
Port: req.SmtpPort, Port: req.SMTPPort,
Username: req.SmtpUsername, Username: req.SMTPUsername,
Password: password, Password: password,
From: req.SmtpFrom, From: req.SMTPFrom,
FromName: req.SmtpFromName, FromName: req.SMTPFromName,
UseTLS: req.SmtpUseTLS, UseTLS: req.SMTPUseTLS,
} }
siteName := h.settingService.GetSiteName(c.Request.Context()) siteName := h.settingService.GetSiteName(c.Request.Context())

View File

@@ -5,13 +5,13 @@ type SystemSettings struct {
RegistrationEnabled bool `json:"registration_enabled"` RegistrationEnabled bool `json:"registration_enabled"`
EmailVerifyEnabled bool `json:"email_verify_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"`
SmtpHost string `json:"smtp_host"` SMTPHost string `json:"smtp_host"`
SmtpPort int `json:"smtp_port"` SMTPPort int `json:"smtp_port"`
SmtpUsername string `json:"smtp_username"` SMTPUsername string `json:"smtp_username"`
SmtpPassword string `json:"smtp_password,omitempty"` SMTPPassword string `json:"smtp_password,omitempty"`
SmtpFrom string `json:"smtp_from_email"` SMTPFrom string `json:"smtp_from_email"`
SmtpFromName string `json:"smtp_from_name"` SMTPFromName string `json:"smtp_from_name"`
SmtpUseTLS bool `json:"smtp_use_tls"` SMTPUseTLS bool `json:"smtp_use_tls"`
TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"`
TurnstileSiteKey string `json:"turnstile_site_key"` TurnstileSiteKey string `json:"turnstile_site_key"`
@@ -20,9 +20,9 @@ type SystemSettings struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
ApiBaseUrl string `json:"api_base_url"` APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocUrl string `json:"doc_url"` DocURL string `json:"doc_url"`
DefaultConcurrency int `json:"default_concurrency"` DefaultConcurrency int `json:"default_concurrency"`
DefaultBalance float64 `json:"default_balance"` DefaultBalance float64 `json:"default_balance"`
@@ -43,8 +43,8 @@ type PublicSettings struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
ApiBaseUrl string `json:"api_base_url"` APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocUrl string `json:"doc_url"` DocURL string `json:"doc_url"`
Version string `json:"version"` Version string `json:"version"`
} }

View File

@@ -39,9 +39,9 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
SiteName: settings.SiteName, SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo, SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle, SiteSubtitle: settings.SiteSubtitle,
ApiBaseUrl: settings.ApiBaseUrl, APIBaseURL: settings.APIBaseURL,
ContactInfo: settings.ContactInfo, ContactInfo: settings.ContactInfo,
DocUrl: settings.DocUrl, DocURL: settings.DocURL,
Version: h.version, Version: h.version,
}) })
} }

View File

@@ -79,9 +79,9 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"), SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"),
SiteLogo: settings[SettingKeySiteLogo], SiteLogo: settings[SettingKeySiteLogo],
SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"),
ApiBaseUrl: settings[SettingKeyApiBaseUrl], APIBaseURL: settings[SettingKeyApiBaseUrl],
ContactInfo: settings[SettingKeyContactInfo], ContactInfo: settings[SettingKeyContactInfo],
DocUrl: settings[SettingKeyDocUrl], DocURL: settings[SettingKeyDocUrl],
}, nil }, nil
} }
@@ -94,15 +94,15 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled) updates[SettingKeyEmailVerifyEnabled] = strconv.FormatBool(settings.EmailVerifyEnabled)
// 邮件服务设置(只有非空才更新密码) // 邮件服务设置(只有非空才更新密码)
updates[SettingKeySmtpHost] = settings.SmtpHost updates[SettingKeySmtpHost] = settings.SMTPHost
updates[SettingKeySmtpPort] = strconv.Itoa(settings.SmtpPort) updates[SettingKeySmtpPort] = strconv.Itoa(settings.SMTPPort)
updates[SettingKeySmtpUsername] = settings.SmtpUsername updates[SettingKeySmtpUsername] = settings.SMTPUsername
if settings.SmtpPassword != "" { if settings.SMTPPassword != "" {
updates[SettingKeySmtpPassword] = settings.SmtpPassword updates[SettingKeySmtpPassword] = settings.SMTPPassword
} }
updates[SettingKeySmtpFrom] = settings.SmtpFrom updates[SettingKeySmtpFrom] = settings.SMTPFrom
updates[SettingKeySmtpFromName] = settings.SmtpFromName updates[SettingKeySmtpFromName] = settings.SMTPFromName
updates[SettingKeySmtpUseTLS] = strconv.FormatBool(settings.SmtpUseTLS) updates[SettingKeySmtpUseTLS] = strconv.FormatBool(settings.SMTPUseTLS)
// Cloudflare Turnstile 设置(只有非空才更新密钥) // Cloudflare Turnstile 设置(只有非空才更新密钥)
updates[SettingKeyTurnstileEnabled] = strconv.FormatBool(settings.TurnstileEnabled) updates[SettingKeyTurnstileEnabled] = strconv.FormatBool(settings.TurnstileEnabled)
@@ -115,9 +115,9 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeySiteName] = settings.SiteName updates[SettingKeySiteName] = settings.SiteName
updates[SettingKeySiteLogo] = settings.SiteLogo updates[SettingKeySiteLogo] = settings.SiteLogo
updates[SettingKeySiteSubtitle] = settings.SiteSubtitle updates[SettingKeySiteSubtitle] = settings.SiteSubtitle
updates[SettingKeyApiBaseUrl] = settings.ApiBaseUrl updates[SettingKeyApiBaseUrl] = settings.APIBaseURL
updates[SettingKeyContactInfo] = settings.ContactInfo updates[SettingKeyContactInfo] = settings.ContactInfo
updates[SettingKeyDocUrl] = settings.DocUrl updates[SettingKeyDocUrl] = settings.DocURL
// 默认配置 // 默认配置
updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency) updates[SettingKeyDefaultConcurrency] = strconv.Itoa(settings.DefaultConcurrency)
@@ -223,26 +223,26 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result := &SystemSettings{ result := &SystemSettings{
RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true",
EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true", EmailVerifyEnabled: settings[SettingKeyEmailVerifyEnabled] == "true",
SmtpHost: settings[SettingKeySmtpHost], SMTPHost: settings[SettingKeySmtpHost],
SmtpUsername: settings[SettingKeySmtpUsername], SMTPUsername: settings[SettingKeySmtpUsername],
SmtpFrom: settings[SettingKeySmtpFrom], SMTPFrom: settings[SettingKeySmtpFrom],
SmtpFromName: settings[SettingKeySmtpFromName], SMTPFromName: settings[SettingKeySmtpFromName],
SmtpUseTLS: settings[SettingKeySmtpUseTLS] == "true", SMTPUseTLS: settings[SettingKeySmtpUseTLS] == "true",
TurnstileEnabled: settings[SettingKeyTurnstileEnabled] == "true", TurnstileEnabled: settings[SettingKeyTurnstileEnabled] == "true",
TurnstileSiteKey: settings[SettingKeyTurnstileSiteKey], TurnstileSiteKey: settings[SettingKeyTurnstileSiteKey],
SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"), SiteName: s.getStringOrDefault(settings, SettingKeySiteName, "Sub2API"),
SiteLogo: settings[SettingKeySiteLogo], SiteLogo: settings[SettingKeySiteLogo],
SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"), SiteSubtitle: s.getStringOrDefault(settings, SettingKeySiteSubtitle, "Subscription to API Conversion Platform"),
ApiBaseUrl: settings[SettingKeyApiBaseUrl], APIBaseURL: settings[SettingKeyApiBaseUrl],
ContactInfo: settings[SettingKeyContactInfo], ContactInfo: settings[SettingKeyContactInfo],
DocUrl: settings[SettingKeyDocUrl], DocURL: settings[SettingKeyDocUrl],
} }
// 解析整数类型 // 解析整数类型
if port, err := strconv.Atoi(settings[SettingKeySmtpPort]); err == nil { if port, err := strconv.Atoi(settings[SettingKeySmtpPort]); err == nil {
result.SmtpPort = port result.SMTPPort = port
} else { } else {
result.SmtpPort = 587 result.SMTPPort = 587
} }
if concurrency, err := strconv.Atoi(settings[SettingKeyDefaultConcurrency]); err == nil { if concurrency, err := strconv.Atoi(settings[SettingKeyDefaultConcurrency]); err == nil {
@@ -259,7 +259,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
} }
// 敏感信息直接返回,方便测试连接时使用 // 敏感信息直接返回,方便测试连接时使用
result.SmtpPassword = settings[SettingKeySmtpPassword] result.SMTPPassword = settings[SettingKeySmtpPassword]
result.TurnstileSecretKey = settings[SettingKeyTurnstileSecretKey] result.TurnstileSecretKey = settings[SettingKeyTurnstileSecretKey]
// Model fallback settings // Model fallback settings

View File

@@ -4,13 +4,13 @@ type SystemSettings struct {
RegistrationEnabled bool RegistrationEnabled bool
EmailVerifyEnabled bool EmailVerifyEnabled bool
SmtpHost string SMTPHost string
SmtpPort int SMTPPort int
SmtpUsername string SMTPUsername string
SmtpPassword string SMTPPassword string
SmtpFrom string SMTPFrom string
SmtpFromName string SMTPFromName string
SmtpUseTLS bool SMTPUseTLS bool
TurnstileEnabled bool TurnstileEnabled bool
TurnstileSiteKey string TurnstileSiteKey string
@@ -19,9 +19,9 @@ type SystemSettings struct {
SiteName string SiteName string
SiteLogo string SiteLogo string
SiteSubtitle string SiteSubtitle string
ApiBaseUrl string APIBaseURL string
ContactInfo string ContactInfo string
DocUrl string DocURL string
DefaultConcurrency int DefaultConcurrency int
DefaultBalance float64 DefaultBalance float64
@@ -42,8 +42,8 @@ type PublicSettings struct {
SiteName string SiteName string
SiteLogo string SiteLogo string
SiteSubtitle string SiteSubtitle string
ApiBaseUrl string APIBaseURL string
ContactInfo string ContactInfo string
DocUrl string DocURL string
Version string Version string
} }

View File

@@ -338,7 +338,19 @@
<!-- Account Type Selection (Gemini) --> <!-- Account Type Selection (Gemini) -->
<div v-if="form.platform === 'gemini'"> <div v-if="form.platform === 'gemini'">
<label class="input-label">{{ t('admin.accounts.accountType') }}</label> <div class="flex items-center justify-between">
<label class="input-label">{{ t('admin.accounts.accountType') }}</label>
<button
type="button"
@click="showGeminiHelpDialog = true"
class="flex items-center gap-1 rounded px-2 py-1 text-xs text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-900/20"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
</svg>
{{ t('admin.accounts.gemini.helpButton') }}
</button>
</div>
<div class="mt-2 grid grid-cols-2 gap-3" data-tour="account-form-type"> <div class="mt-2 grid grid-cols-2 gap-3" data-tour="account-form-type">
<button <button
type="button" type="button"
@@ -439,15 +451,6 @@
> >
{{ t('admin.accounts.gemini.accountType.apiKeyLink') }} {{ t('admin.accounts.gemini.accountType.apiKeyLink') }}
</a> </a>
<span class="text-purple-400">·</span>
<a
:href="geminiHelpLinks.aiStudioPricing"
class="font-medium text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.accountType.quotaLink') }}
</a>
</div> </div>
</div> </div>
@@ -687,79 +690,6 @@
</div> </div>
<p class="input-hint">{{ t('admin.accounts.gemini.tier.hint') }}</p> <p class="input-hint">{{ t('admin.accounts.gemini.tier.hint') }}</p>
</div> </div>
<div class="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-4 text-xs text-blue-900 dark:border-blue-800/40 dark:bg-blue-900/20 dark:text-blue-200">
<div class="flex items-start gap-3">
<svg
class="h-5 w-5 flex-shrink-0 text-blue-600 dark:text-blue-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div class="min-w-0">
<p class="text-sm font-medium text-blue-800 dark:text-blue-300">
{{ t('admin.accounts.gemini.setupGuide.title') }}
</p>
<div class="mt-2 space-y-2">
<div>
<p class="font-semibold text-blue-800 dark:text-blue-300">
{{ t('admin.accounts.gemini.setupGuide.checklistTitle') }}
</p>
<ul class="mt-1 list-disc space-y-1 pl-4">
<li>
{{ t('admin.accounts.gemini.setupGuide.checklistItems.usIp') }}
<a
:href="geminiHelpLinks.countryCheck"
class="ml-1 text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.setupGuide.links.countryCheck') }}
</a>
</li>
<li>{{ t('admin.accounts.gemini.setupGuide.checklistItems.age') }}</li>
</ul>
</div>
<div>
<p class="font-semibold text-blue-800 dark:text-blue-300">
{{ t('admin.accounts.gemini.setupGuide.activationTitle') }}
</p>
<ul class="mt-1 list-disc space-y-1 pl-4">
<li>
{{ t('admin.accounts.gemini.setupGuide.activationItems.geminiWeb') }}
<a
:href="geminiHelpLinks.geminiWebActivation"
class="ml-1 text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.setupGuide.links.geminiWebActivation') }}
</a>
</li>
<li>
{{ t('admin.accounts.gemini.setupGuide.activationItems.gcpProject') }}
<a
:href="geminiHelpLinks.gcpProject"
class="ml-1 text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.setupGuide.links.gcpProject') }}
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!-- Account Type Selection (Antigravity - OAuth only) --> <!-- Account Type Selection (Antigravity - OAuth only) -->
@@ -1188,165 +1118,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Gemini 配额与限流政策说明 -->
<div v-if="form.platform === 'gemini'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
<div class="rounded-lg bg-gray-50 p-4 dark:bg-gray-800/40">
<div class="flex items-start gap-3">
<svg
class="h-5 w-5 flex-shrink-0 text-gray-500 dark:text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-800 dark:text-gray-200">
{{ t('admin.accounts.gemini.quotaPolicy.title') }}
</p>
<p class="mt-1 text-xs text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.note') }}
</p>
<div class="mt-3 overflow-x-auto">
<table class="min-w-full text-xs text-gray-700 dark:text-gray-300">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-700">
<th class="px-2 py-1.5 text-left font-semibold">
{{ t('admin.accounts.gemini.quotaPolicy.columns.channel') }}
</th>
<th class="px-2 py-1.5 text-left font-semibold">
{{ t('admin.accounts.gemini.quotaPolicy.columns.account') }}
</th>
<th class="px-2 py-1.5 text-left font-semibold">
{{ t('admin.accounts.gemini.quotaPolicy.columns.limits') }}
</th>
<th class="px-2 py-1.5 text-left font-semibold">
{{ t('admin.accounts.gemini.quotaPolicy.columns.docs') }}
</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-100 dark:border-gray-800">
<td class="px-2 py-1.5 align-top" rowspan="2">
{{ t('admin.accounts.gemini.quotaPolicy.rows.cli.channel') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.cli.free') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.cli.limitsFree') }}
</td>
<td class="px-2 py-1.5 align-top" rowspan="2">
<a
:href="geminiQuotaDocs.codeAssist"
class="text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.codeAssist') }}
</a>
</td>
</tr>
<tr class="border-b border-gray-100 dark:border-gray-800">
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.cli.premium') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.cli.limitsPremium') }}
</td>
</tr>
<tr class="border-b border-gray-100 dark:border-gray-800">
<td class="px-2 py-1.5 align-top">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcloud.channel') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcloud.account') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcloud.limits') }}
</td>
<td class="px-2 py-1.5 align-top">
<a
:href="geminiQuotaDocs.codeAssist"
class="text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.codeAssist') }}
</a>
</td>
</tr>
<tr class="border-b border-gray-100 dark:border-gray-800">
<td class="px-2 py-1.5 align-top" rowspan="2">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.channel') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.free') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsFree') }}
</td>
<td class="px-2 py-1.5 align-top" rowspan="2">
<a
:href="geminiQuotaDocs.aiStudio"
class="text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.aiStudio') }}
</a>
</td>
</tr>
<tr class="border-b border-gray-100 dark:border-gray-800">
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.paid') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsPaid') }}
</td>
</tr>
<tr>
<td class="px-2 py-1.5 align-top" rowspan="2">
{{ t('admin.accounts.gemini.quotaPolicy.rows.customOAuth.channel') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.customOAuth.free') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.customOAuth.limitsFree') }}
</td>
<td class="px-2 py-1.5 align-top" rowspan="2">
<a
:href="geminiQuotaDocs.vertex"
class="text-blue-600 hover:underline dark:text-blue-400"
target="_blank"
rel="noreferrer"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.vertex') }}
</a>
</td>
</tr>
<tr>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.customOAuth.paid') }}
</td>
<td class="px-2 py-1.5">
{{ t('admin.accounts.gemini.quotaPolicy.rows.customOAuth.limitsPaid') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!-- Temp Unschedulable Rules --> <!-- Temp Unschedulable Rules -->
@@ -1717,6 +1488,214 @@
</div> </div>
</template> </template>
</BaseDialog> </BaseDialog>
<!-- Gemini Help Dialog -->
<BaseDialog
:show="showGeminiHelpDialog"
:title="t('admin.accounts.gemini.helpDialog.title')"
@close="showGeminiHelpDialog = false"
max-width="max-w-3xl"
>
<div class="space-y-6">
<!-- Setup Guide Section -->
<div>
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.setupGuide.title') }}
</h3>
<div class="space-y-4">
<div>
<p class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.gemini.setupGuide.checklistTitle') }}
</p>
<ul class="list-inside list-disc space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li>{{ t('admin.accounts.gemini.setupGuide.checklistItems.usIp') }}</li>
<li>{{ t('admin.accounts.gemini.setupGuide.checklistItems.age') }}</li>
</ul>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.gemini.setupGuide.activationTitle') }}
</p>
<ul class="list-inside list-disc space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li>{{ t('admin.accounts.gemini.setupGuide.activationItems.geminiWeb') }}</li>
<li>{{ t('admin.accounts.gemini.setupGuide.activationItems.gcpProject') }}</li>
</ul>
<div class="mt-2 flex flex-wrap gap-2">
<a
href="https://gemini.google.com/faq#location"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.setupGuide.links.countryCheck') }}
</a>
<span class="text-gray-400">·</span>
<a
href="https://gemini.google.com"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.setupGuide.links.geminiWebActivation') }}
</a>
<span class="text-gray-400">·</span>
<a
href="https://console.cloud.google.com"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.setupGuide.links.gcpProject') }}
</a>
</div>
</div>
</div>
</div>
<!-- Quota Policy Section -->
<div class="border-t border-gray-200 pt-6 dark:border-dark-600">
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.quotaPolicy.title') }}
</h3>
<p class="mb-4 text-xs text-amber-600 dark:text-amber-400">
{{ t('admin.accounts.gemini.quotaPolicy.note') }}
</p>
<div class="overflow-x-auto">
<table class="w-full text-xs">
<thead class="bg-gray-50 dark:bg-dark-600">
<tr>
<th class="px-3 py-2 text-left font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.gemini.quotaPolicy.columns.channel') }}
</th>
<th class="px-3 py-2 text-left font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.gemini.quotaPolicy.columns.account') }}
</th>
<th class="px-3 py-2 text-left font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.gemini.quotaPolicy.columns.limits') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-dark-600">
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.quotaPolicy.rows.googleOne.channel') }}
</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Free</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsFree') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white"></td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Pro</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsPro') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white"></td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Ultra</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.googleOne.limitsUltra') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcp.channel') }}
</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Standard</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcp.limitsStandard') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white"></td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Enterprise</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.gcp.limitsEnterprise') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.channel') }}
</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Free</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsFree') }}
</td>
</tr>
<tr>
<td class="px-3 py-2 text-gray-900 dark:text-white"></td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">Paid</td>
<td class="px-3 py-2 text-gray-600 dark:text-gray-400">
{{ t('admin.accounts.gemini.quotaPolicy.rows.aiStudio.limitsPaid') }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-4 flex flex-wrap gap-3">
<a
:href="geminiQuotaDocs.codeAssist"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.codeAssist') }}
</a>
<a
:href="geminiQuotaDocs.aiStudio"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.aiStudio') }}
</a>
<a
:href="geminiQuotaDocs.vertex"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.quotaPolicy.docs.vertex') }}
</a>
</div>
</div>
<!-- API Key Links Section -->
<div class="border-t border-gray-200 pt-6 dark:border-dark-600">
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.accounts.gemini.helpDialog.apiKeySection') }}
</h3>
<div class="flex flex-wrap gap-3">
<a
:href="geminiHelpLinks.apiKey"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.accountType.apiKeyLink') }}
</a>
<a
:href="geminiHelpLinks.aiStudioPricing"
target="_blank"
rel="noreferrer"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
>
{{ t('admin.accounts.gemini.accountType.quotaLink') }}
</a>
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end">
<button @click="showGeminiHelpDialog = false" type="button" class="btn btn-primary">
{{ t('common.close') }}
</button>
</div>
</template>
</BaseDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -1860,6 +1839,7 @@ const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one') const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
const geminiAIStudioOAuthEnabled = ref(false) const geminiAIStudioOAuthEnabled = ref(false)
const showAdvancedOAuth = ref(false) const showAdvancedOAuth = ref(false)
const showGeminiHelpDialog = ref(false)
// Gemini tier selection (used as fallback when auto-detection is unavailable/fails) // Gemini tier selection (used as fallback when auto-detection is unavailable/fails)
const geminiTierGoogleOne = ref<'google_one_free' | 'google_ai_pro' | 'google_ai_ultra'>('google_one_free') const geminiTierGoogleOne = ref<'google_one_free' | 'google_ai_pro' | 'google_ai_ultra'>('google_one_free')

View File

@@ -1252,28 +1252,33 @@ export default {
}, },
// Gemini specific (platform-wide) // Gemini specific (platform-wide)
gemini: { gemini: {
helpButton: 'Help',
helpDialog: {
title: 'Gemini Usage Guide',
apiKeySection: 'API Key Links'
},
modelPassthrough: 'Gemini Model Passthrough', modelPassthrough: 'Gemini Model Passthrough',
modelPassthroughDesc: modelPassthroughDesc:
'All model requests are forwarded directly to the Gemini API without model restrictions or mappings.', 'All model requests are forwarded directly to the Gemini API without model restrictions or mappings.',
baseUrlHint: 'Leave default for official Gemini API', baseUrlHint: 'Leave default for official Gemini API',
apiKeyHint: 'Your Gemini API Key (starts with AIza)', apiKeyHint: 'Your Gemini API Key (starts with AIza)',
tier: { tier: {
label: 'Tier (Quota Level)', label: 'Account Tier',
hint: 'Tip: The system will try to auto-detect the tier first; if auto-detection is unavailable or fails, your selected tier is used as a fallback (simulated quota).', hint: 'Tip: The system will try to auto-detect the tier first; if auto-detection is unavailable or fails, your selected tier is used as a fallback (simulated quota).',
aiStudioHint: aiStudioHint:
'AI Studio quotas are per-model (Pro/Flash are limited independently). If billing is enabled, choose Pay-as-you-go.', 'AI Studio quotas are per-model (Pro/Flash are limited independently). If billing is enabled, choose Pay-as-you-go.',
googleOne: { googleOne: {
free: 'Google One Free (1000 RPD / 60 RPM, shared pool)', free: 'Google One Free',
pro: 'Google AI Pro (1500 RPD / 120 RPM, shared pool)', pro: 'Google One Pro',
ultra: 'Google AI Ultra (2000 RPD / 120 RPM, shared pool)' ultra: 'Google One Ultra'
}, },
gcp: { gcp: {
standard: 'GCP Standard (1500 RPD / 120 RPM, shared pool)', standard: 'GCP Standard',
enterprise: 'GCP Enterprise (2000 RPD / 120 RPM, shared pool)' enterprise: 'GCP Enterprise'
}, },
aiStudio: { aiStudio: {
free: 'AI Studio Free Tier (Pro: 50 RPD / 2 RPM; Flash: 1500 RPD / 15 RPM)', free: 'Google AI Free',
paid: 'AI Studio Pay-as-you-go (Pro: ∞ RPD / 1000 RPM; Flash: ∞ RPD / 2000 RPM)' paid: 'Google AI Pay-as-you-go'
} }
}, },
accountType: { accountType: {

View File

@@ -1391,26 +1391,31 @@ export default {
}, },
// Gemini specific (platform-wide) // Gemini specific (platform-wide)
gemini: { gemini: {
helpButton: '使用帮助',
helpDialog: {
title: 'Gemini 使用指南',
apiKeySection: 'API Key 相关链接'
},
modelPassthrough: 'Gemini 直接转发模型', modelPassthrough: 'Gemini 直接转发模型',
modelPassthroughDesc: '所有模型请求将直接转发至 Gemini API不进行模型限制或映射。', modelPassthroughDesc: '所有模型请求将直接转发至 Gemini API不进行模型限制或映射。',
baseUrlHint: '留空使用官方 Gemini API', baseUrlHint: '留空使用官方 Gemini API',
apiKeyHint: '您的 Gemini API Key以 AIza 开头)', apiKeyHint: '您的 Gemini API Key以 AIza 开头)',
tier: { tier: {
label: 'Tier配额等级', label: '账号等级',
hint: '提示:系统会优先尝试自动识别 Tier;若自动识别不可用或失败,则使用你选择的 Tier 作为回退(本地模拟配额)。', hint: '提示:系统会优先尝试自动识别账号等级;若自动识别不可用或失败,则使用你选择的等级作为回退(本地模拟配额)。',
aiStudioHint: 'AI Studio 的配额是按模型分别限流Pro/Flash 独立)。若已绑卡(按量付费),请选 Pay-as-you-go。', aiStudioHint: 'AI Studio 的配额是按模型分别限流Pro/Flash 独立)。若已绑卡(按量付费),请选 Pay-as-you-go。',
googleOne: { googleOne: {
free: 'Google One Free1000 RPD / 60 RPM共享池', free: 'Google One Free',
pro: 'Google AI Pro1500 RPD / 120 RPM共享池', pro: 'Google One Pro',
ultra: 'Google AI Ultra2000 RPD / 120 RPM共享池' ultra: 'Google One Ultra'
}, },
gcp: { gcp: {
standard: 'GCP Standard1500 RPD / 120 RPM共享池', standard: 'GCP Standard',
enterprise: 'GCP Enterprise2000 RPD / 120 RPM共享池' enterprise: 'GCP Enterprise'
}, },
aiStudio: { aiStudio: {
free: 'AI Studio Free TierPro: 50 RPD / 2 RPMFlash: 1500 RPD / 15 RPM', free: 'Google AI Free',
paid: 'AI Studio Pay-as-you-goPro: ∞ RPD / 1000 RPMFlash: ∞ RPD / 2000 RPM' paid: 'Google AI Pay-as-you-go'
} }
}, },
accountType: { accountType: {