diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index e87d83ee5..f18a91a93 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -322,29 +322,31 @@ type AgentAccountModelDeleteReq struct { } type AgentAccountCreateReq struct { - Provider string `json:"provider" validate:"required"` - Name string `json:"name" validate:"required"` - APIKey string `json:"apiKey" validate:"required"` - RememberAPIKey bool `json:"rememberApiKey"` - BaseURL string `json:"baseURL"` - Models []AgentAccountModel `json:"models"` - APIType string `json:"apiType" validate:"required"` - AuthMode string `json:"authMode"` - VerifyModel string `json:"verifyModel"` - Remark string `json:"remark"` + Provider string `json:"provider" validate:"required"` + Name string `json:"name" validate:"required"` + APIKey string `json:"apiKey" validate:"required"` + RememberAPIKey bool `json:"rememberApiKey"` + BaseURL string `json:"baseURL"` + Models []AgentAccountModel `json:"models"` + APIType string `json:"apiType" validate:"required"` + AuthMode string `json:"authMode"` + VerifyModel string `json:"verifyModel"` + ValidateAvailability *bool `json:"validateAvailability"` + Remark string `json:"remark"` } type AgentAccountUpdateReq struct { - ID uint `json:"id" validate:"required"` - Name string `json:"name" validate:"required"` - APIKey string `json:"apiKey" validate:"required"` - RememberAPIKey bool `json:"rememberApiKey"` - BaseURL string `json:"baseURL"` - APIType string `json:"apiType" validate:"required"` - AuthMode string `json:"authMode"` - VerifyModel string `json:"verifyModel"` - Remark string `json:"remark"` - SyncAgents bool `json:"syncAgents"` + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + APIKey string `json:"apiKey" validate:"required"` + RememberAPIKey bool `json:"rememberApiKey"` + BaseURL string `json:"baseURL"` + APIType string `json:"apiType" validate:"required"` + AuthMode string `json:"authMode"` + VerifyModel string `json:"verifyModel"` + ValidateAvailability *bool `json:"validateAvailability"` + Remark string `json:"remark"` + SyncAgents bool `json:"syncAgents"` } type AgentAccountVerifyReq struct { @@ -363,6 +365,8 @@ type AgentAccountDeleteReq struct { type AgentAccountSearch struct { PageInfo Provider string `json:"provider"` + APIType string `json:"apiType"` + TextOnly bool `json:"textOnly"` Name string `json:"name"` } @@ -394,11 +398,13 @@ type ProviderModelInfo struct { } type ProviderAPIInfo struct { - APIType string `json:"apiType"` - BaseURL string `json:"baseUrl"` - EditableBaseURL bool `json:"editableBaseUrl"` - DefaultAuthMode string `json:"defaultAuthMode"` - AuthModes []string `json:"authModes"` + APIType string `json:"apiType"` + BaseURL string `json:"baseUrl"` + EditableBaseURL bool `json:"editableBaseUrl"` + SupportsModelDiscovery bool `json:"supportsModelDiscovery"` + DefaultAuthMode string `json:"defaultAuthMode"` + AuthModes []string `json:"authModes"` + Models []ProviderModelInfo `json:"models"` } type ProviderInfo struct { diff --git a/agent/app/provider/catalog.go b/agent/app/provider/catalog.go index 92b5ffca0..3d9f156cc 100644 --- a/agent/app/provider/catalog.go +++ b/agent/app/provider/catalog.go @@ -10,8 +10,10 @@ type APIConfig struct { APIType string BaseURL string EditableBaseURL bool + DiscoverModels bool DefaultAuthMode string AuthModes []string + Models []Model } const ( @@ -27,6 +29,7 @@ type Model struct { type Meta struct { Key string DisplayName string + DisplayNameKey string Sort uint DefaultAPIType string APIConfigs []APIConfig @@ -37,15 +40,15 @@ type Meta struct { var catalog = map[string]Meta{ "custom": { Key: "custom", DisplayName: "Custom", Sort: 10, DefaultAPIType: "openai-completions", EnvKey: "CUSTOM_API_KEY", - APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"), + APIConfigs: editableAPIConfigs(true, "openai-completions", "openai-responses", "anthropic-messages", "openai-images", "dashscope-images", "minimax-images", "openrouter-images"), }, "ollama": { Key: "ollama", DisplayName: "Ollama", Sort: 15, DefaultAPIType: "openai-responses", - APIConfigs: editableAPIConfigs("openai-responses", "openai-completions"), + APIConfigs: editableAPIConfigs(false, "openai-responses", "openai-completions"), }, "vllm": { Key: "vllm", DisplayName: "vLLM", Sort: 20, DefaultAPIType: "openai-completions", EnvKey: "VLLM_API_KEY", - APIConfigs: editableAPIConfigs("openai-completions", "openai-responses", "anthropic-messages"), + APIConfigs: editableAPIConfigs(false, "openai-completions", "openai-responses", "anthropic-messages", "openai-images"), }, "deepseek": { Key: "deepseek", DisplayName: "DeepSeek", Sort: 25, DefaultAPIType: "openai-completions", EnvKey: "DEEPSEEK_API_KEY", @@ -56,7 +59,7 @@ var catalog = map[string]Meta{ Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}}, }, "bailian-coding-plan": { - Key: "bailian-coding-plan", DisplayName: "阿里云百炼 Coding Plan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY", + Key: "bailian-coding-plan", DisplayNameKey: "AIProviderBailianCodingPlan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY", APIConfigs: []APIConfig{ {APIType: "openai-completions", BaseURL: "https://coding.dashscope.aliyuncs.com/v1"}, anthropicAPIConfig("https://coding.dashscope.aliyuncs.com/apps/anthropic", AuthModeBearer), @@ -75,7 +78,7 @@ var catalog = map[string]Meta{ }, }, "ark-coding-plan": { - Key: "ark-coding-plan", DisplayName: "方舟 Coding Plan", Sort: 35, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY", + Key: "ark-coding-plan", DisplayNameKey: "AIProviderArkCodingPlan", Sort: 35, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY", APIConfigs: []APIConfig{ {APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3"}, anthropicAPIConfig("https://ark.cn-beijing.volces.com/api/coding", AuthModeBearer), @@ -88,14 +91,18 @@ var catalog = map[string]Meta{ }, "zai": { Key: "zai", DisplayName: "Z.ai", Sort: 40, DefaultAPIType: "openai-completions", EnvKey: "ZAI_API_KEY", - APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true}}, - Models: []Model{{ID: "glm-5", Name: "GLM-5"}, {ID: "glm-4.7", Name: "GLM-4.7"}, {ID: "glm-4.7-flash", Name: "GLM-4.7-Flash"}, {ID: "glm-4.7-flashx", Name: "GLM-4.7-FlashX"}}, + APIConfigs: []APIConfig{ + {APIType: "openai-completions", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true}, + {APIType: "openai-images", BaseURL: "https://open.bigmodel.cn/api/paas/v4", EditableBaseURL: true}, + }, + Models: []Model{{ID: "glm-5", Name: "GLM-5"}, {ID: "glm-4.7", Name: "GLM-4.7"}, {ID: "glm-4.7-flash", Name: "GLM-4.7-Flash"}, {ID: "glm-4.7-flashx", Name: "GLM-4.7-FlashX"}}, }, "minimax": { Key: "minimax", DisplayName: "MiniMax (CN)", Sort: 45, DefaultAPIType: "anthropic-messages", EnvKey: "MINIMAX_API_KEY", APIConfigs: []APIConfig{ anthropicAPIConfig("https://api.minimaxi.com/anthropic", AuthModeXAPIKey, AuthModeBearer), {APIType: "openai-completions", BaseURL: "https://api.minimaxi.com/v1"}, + {APIType: "minimax-images", BaseURL: "https://api.minimaxi.com"}, }, Models: []Model{{ID: "MiniMax-M3", Name: "MiniMax M3"}, {ID: "MiniMax-M2.7", Name: "MiniMax M2.7"}, {ID: "MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"}}, }, @@ -122,13 +129,17 @@ var catalog = map[string]Meta{ APIConfigs: []APIConfig{ {APIType: "openai-responses", BaseURL: "https://api.openai.com/v1"}, {APIType: "openai-completions", BaseURL: "https://api.openai.com/v1"}, + {APIType: "openai-images", BaseURL: "https://api.openai.com/v1"}, }, Models: []Model{{ID: "gpt-5.4", Name: "gpt-5.4"}, {ID: "gpt-5.4-pro", Name: "gpt-5.4-pro"}, {ID: "gpt-5.4-mini", Name: "gpt-5.4-mini"}, {ID: "gpt-5.4-nano", Name: "gpt-5.4-nano"}}, }, "openrouter": { Key: "openrouter", DisplayName: "OpenRouter", Sort: 56, DefaultAPIType: "openai-completions", EnvKey: "OPENROUTER_API_KEY", - APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://openrouter.ai/api/v1"}}, - Models: []Model{{ID: "openrouter/free", Name: "openrouter/free"}, {ID: "openrouter/auto", Name: "openrouter/auto"}}, + APIConfigs: []APIConfig{ + {APIType: "openai-completions", BaseURL: "https://openrouter.ai/api/v1"}, + {APIType: "openrouter-images", BaseURL: "https://openrouter.ai"}, + }, + Models: []Model{{ID: "openrouter/free", Name: "openrouter/free"}, {ID: "openrouter/auto", Name: "openrouter/auto"}}, }, "anthropic": { Key: "anthropic", DisplayName: "Anthropic", Sort: 60, DefaultAPIType: "anthropic-messages", EnvKey: "ANTHROPIC_API_KEY", @@ -145,9 +156,62 @@ var catalog = map[string]Meta{ APIConfigs: []APIConfig{{APIType: "openai-completions", BaseURL: "https://api.moonshot.ai/v1"}}, Models: []Model{{ID: "kimi-k2.5", Name: "Kimi K2.5"}, {ID: "kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"}, {ID: "kimi-k2-thinking", Name: "Kimi K2 Thinking"}}, }, + "bailian": { + Key: "bailian", DisplayNameKey: "AIProviderBailian", Sort: 31, DefaultAPIType: "openai-completions", EnvKey: "DASHSCOPE_API_KEY", + APIConfigs: []APIConfig{ + { + APIType: "openai-completions", BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", + DiscoverModels: true, + Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}}, + }, + { + APIType: "openai-responses", BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", + DiscoverModels: true, + Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}}, + }, + { + APIType: "anthropic-messages", BaseURL: "https://dashscope.aliyuncs.com/apps/anthropic", + DefaultAuthMode: AuthModeBearer, + AuthModes: []string{AuthModeBearer}, + Models: []Model{{ID: "qwen3.7-plus", Name: "qwen3.7-plus"}, {ID: "qwen3.6-plus", Name: "qwen3.6-plus"}, {ID: "qwen3.6-flash", Name: "qwen3.6-flash"}}, + }, + { + APIType: "dashscope-images", BaseURL: "https://dashscope.aliyuncs.com", + Models: []Model{ + {ID: "qwen-image-2.0-pro", Name: "qwen-image-2.0-pro"}, + {ID: "qwen-image-2.0", Name: "qwen-image-2.0"}, + {ID: "wan2.7-image-pro", Name: "wan2.7-image-pro"}, + {ID: "wan2.7-image", Name: "wan2.7-image"}, + }, + }, + }, + }, + "ark": { + Key: "ark", DisplayNameKey: "AIProviderArk", Sort: 36, DefaultAPIType: "openai-completions", EnvKey: "ARK_API_KEY", + APIConfigs: []APIConfig{ + { + APIType: "openai-completions", BaseURL: "https://ark.cn-beijing.volces.com/api/v3", + DiscoverModels: true, + Models: []Model{{ID: "doubao-seed-2-0-pro-260215", Name: "doubao-seed-2-0-pro-260215"}, {ID: "doubao-seed-2-0-lite-260215", Name: "doubao-seed-2-0-lite-260215"}}, + }, + { + APIType: "openai-responses", BaseURL: "https://ark.cn-beijing.volces.com/api/v3", + DiscoverModels: true, + Models: []Model{{ID: "doubao-seed-2-0-pro-260215", Name: "doubao-seed-2-0-pro-260215"}, {ID: "doubao-seed-2-0-lite-260215", Name: "doubao-seed-2-0-lite-260215"}}, + }, + { + APIType: "openai-images", BaseURL: "https://ark.cn-beijing.volces.com/api/v3", + Models: []Model{ + {ID: "doubao-seedream-5-0-260128", Name: "doubao-seedream-5-0-260128"}, + {ID: "doubao-seedream-5-0-lite-260128", Name: "doubao-seedream-5-0-lite-260128"}, + {ID: "doubao-seedream-4-5-251128", Name: "doubao-seedream-4-5-251128"}, + }, + }, + }, + }, } -func editableAPIConfigs(apiTypes ...string) []APIConfig { +func editableAPIConfigs(discoverModels bool, apiTypes ...string) []APIConfig { configs := make([]APIConfig, 0, len(apiTypes)) for _, apiType := range apiTypes { if apiType == "anthropic-messages" { @@ -156,7 +220,11 @@ func editableAPIConfigs(apiTypes ...string) []APIConfig { configs = append(configs, config) continue } - configs = append(configs, APIConfig{APIType: apiType, EditableBaseURL: true}) + configs = append(configs, APIConfig{ + APIType: apiType, + EditableBaseURL: true, + DiscoverModels: discoverModels && (apiType == "openai-completions" || apiType == "openai-responses"), + }) } return configs } @@ -203,12 +271,38 @@ func FindAPIConfig(key, apiType string) (APIConfig, bool) { } for _, config := range meta.APIConfigs { if config.APIType == target { + config.AuthModes = append([]string(nil), config.AuthModes...) + config.Models = append([]Model(nil), config.Models...) return config, true } } return APIConfig{}, false } +func DefaultModels(key, apiType string) []Model { + meta, ok := catalog[key] + if !ok { + return nil + } + target := strings.TrimSpace(apiType) + if target == "" { + target = meta.DefaultAPIType + } + for _, config := range meta.APIConfigs { + if config.APIType != target { + continue + } + if len(config.Models) > 0 { + return append([]Model(nil), config.Models...) + } + if IsImageAPIType(config.APIType) { + return nil + } + break + } + return append([]Model(nil), meta.Models...) +} + func ResolveAuthMode(provider, apiType, requested string) (string, error) { config, ok := FindAPIConfig(provider, apiType) if !ok { @@ -264,6 +358,9 @@ func ResolveBaseURL(key, apiType, requested string) (string, error) { if err != nil || parsed.Scheme == "" || parsed.Host == "" { return "", fmt.Errorf("invalid base url") } + if key == "custom" && IsImageAPIType(config.APIType) { + return baseURL, nil + } parsed.Path = normalizeEndpointPath(config.APIType, parsed.Path) parsed.RawQuery = "" parsed.Fragment = "" @@ -271,6 +368,9 @@ func ResolveBaseURL(key, apiType, requested string) (string, error) { } func normalizeEndpointPath(apiType, value string) string { + if IsImageAPIType(apiType) { + return strings.TrimRight(value, "/") + } path := strings.TrimRight(value, "/") suffixes := []string{} switch apiType { @@ -289,6 +389,15 @@ func normalizeEndpointPath(apiType, value string) string { return path } +func IsImageAPIType(apiType string) bool { + switch apiType { + case "openai-images", "dashscope-images", "minimax-images", "openrouter-images": + return true + default: + return false + } +} + func EnvKey(key string) string { meta, ok := catalog[key] if !ok { @@ -305,6 +414,14 @@ func DisplayName(key string) string { return meta.DisplayName } +func DisplayNameKey(key string) string { + meta, ok := catalog[key] + if !ok { + return "" + } + return meta.DisplayNameKey +} + func NormalizeModelID(provider, modelID string) string { target := strings.TrimLeft(strings.TrimSpace(modelID), "/") for _, prefix := range legacyModelPrefixes[provider] { @@ -344,6 +461,7 @@ func cloneMeta(meta Meta) Meta { for index, config := range meta.APIConfigs { clone.APIConfigs[index] = config clone.APIConfigs[index].AuthModes = append([]string(nil), config.AuthModes...) + clone.APIConfigs[index].Models = append([]Model(nil), config.Models...) } clone.Models = append([]Model(nil), meta.Models...) return clone diff --git a/agent/app/provider/discover.go b/agent/app/provider/discover.go index 549c760b4..dff67c517 100644 --- a/agent/app/provider/discover.go +++ b/agent/app/provider/discover.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" ) @@ -54,11 +55,20 @@ func buildModelDiscoveryURL(baseURL string) string { base = normalizeEndpointPath(apiType, base) } switch { - case strings.HasSuffix(base, "/v1/models"): + case strings.HasSuffix(base, "/models"): return base - case strings.HasSuffix(base, "/v1"): + case hasAPIVersionSuffix(base): return base + "/models" default: return base + "/v1/models" } } + +func hasAPIVersionSuffix(value string) bool { + segment := value[strings.LastIndex(value, "/")+1:] + if len(segment) < 2 || segment[0] != 'v' { + return false + } + _, err := strconv.Atoi(segment[1:]) + return err == nil +} diff --git a/agent/app/provider/openclaw.go b/agent/app/provider/openclaw.go index 39ee5435f..9b119f2fa 100644 --- a/agent/app/provider/openclaw.go +++ b/agent/app/provider/openclaw.go @@ -23,6 +23,9 @@ func BuildOpenClawProviderPatch(provider, modelName, apiType, authMode, baseURL, if _, ok := FindAPIConfig(provider, resolvedAPIType); !ok { resolvedAPIType = DefaultAPIType(provider) } + if IsImageAPIType(resolvedAPIType) { + return nil, fmt.Errorf("api type %s does not support text generation", resolvedAPIType) + } resolvedAuthMode, err := ResolveAuthMode(provider, resolvedAPIType, authMode) if err != nil { return nil, err diff --git a/agent/app/provider/verify.go b/agent/app/provider/verify.go index 80894c06e..318c50ba3 100644 --- a/agent/app/provider/verify.go +++ b/agent/app/provider/verify.go @@ -64,7 +64,10 @@ func VerifyAccount(provider, apiType, authMode, baseURL, apiKey, model string) e } func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model string) VerifyRequest { - baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/") + baseURL = strings.TrimSpace(baseURL) + if provider != "custom" || !IsImageAPIType(apiType) { + baseURL = strings.TrimRight(baseURL, "/") + } headers := map[string]string{"Content-Type": "application/json"} request := VerifyRequest{Method: http.MethodPost, Headers: headers} @@ -78,6 +81,28 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri } switch apiType { + case "openai-images": + request.URL = imageVerifyURL(provider, baseURL, "/images/generations") + headers["Authorization"] = "Bearer " + apiKey + request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"}) + case "dashscope-images": + request.URL = imageVerifyURL(provider, baseURL, "/api/v1/services/aigc/multimodal-generation/generation") + headers["Authorization"] = "Bearer " + apiKey + request.Body = mustJSON(map[string]interface{}{ + "model": model, + "input": map[string]interface{}{"messages": []map[string]interface{}{ + {"role": "user", "content": []map[string]string{{"text": "test"}}}, + }}, + "parameters": map[string]interface{}{"n": 1}, + }) + case "minimax-images": + request.URL = imageVerifyURL(provider, baseURL, "/v1/image_generation") + headers["Authorization"] = "Bearer " + apiKey + request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"}) + case "openrouter-images": + request.URL = imageVerifyURL(provider, baseURL, "/api/v1/images") + headers["Authorization"] = "Bearer " + apiKey + request.Body = mustJSON(map[string]interface{}{"model": model, "prompt": "test", "n": 1, "response_format": "url"}) case "anthropic-messages": request.URL = baseURL + "/v1/messages" if authMode == AuthModeBearer { @@ -106,6 +131,13 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri return request } +func imageVerifyURL(provider, baseURL, endpoint string) string { + if provider == "custom" || strings.HasSuffix(strings.ToLower(baseURL), endpoint) { + return baseURL + } + return baseURL + endpoint +} + func verifyHTTPError(statusCode int, body []byte) string { message := strings.TrimSpace(string(body)) var payload verifyErrorResponse diff --git a/agent/app/repo/agent_account.go b/agent/app/repo/agent_account.go index f691f58ea..0f99b2fbf 100644 --- a/agent/app/repo/agent_account.go +++ b/agent/app/repo/agent_account.go @@ -17,7 +17,7 @@ type IAgentAccountRepo interface { Save(account *model.AgentAccount) error DeleteByID(id uint) error List(opts ...DBOption) ([]model.AgentAccount, error) - CountByProviders(providers []string) (map[string]int64, error) + CountTextByProviders(providers []string) (map[string]int64, error) } func NewIAgentAccountRepo() IAgentAccountRepo { @@ -67,7 +67,7 @@ func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) { return accounts, nil } -func (a AgentAccountRepo) CountByProviders(providers []string) (map[string]int64, error) { +func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]int64, error) { normalizedProviders := normalizeProviders(providers) counts := make(map[string]int64, len(normalizedProviders)) for _, provider := range normalizedProviders { @@ -86,6 +86,7 @@ func (a AgentAccountRepo) CountByProviders(providers []string) (map[string]int64 Model(&model.AgentAccount{}). Select("provider, COUNT(*) as count"). Where("provider IN ?", normalizedProviders). + Where("api_type NOT LIKE ?", "%-images"). Group("provider"). Scan(&rows).Error; err != nil { return nil, err diff --git a/agent/app/repo/common.go b/agent/app/repo/common.go index df24bd5b8..d50889e93 100644 --- a/agent/app/repo/common.go +++ b/agent/app/repo/common.go @@ -94,6 +94,18 @@ func WithByProvider(provider string) DBOption { } } +func WithByAPIType(apiType string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("api_type = ?", apiType) + } +} + +func WithTextAPIType() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("api_type NOT LIKE ?", "%-images") + } +} + func WithByModel(model string) DBOption { return func(g *gorm.DB) *gorm.DB { if len(model) == 0 { diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 8533b14a8..76315a062 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -984,19 +984,28 @@ func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) { } apiTypes := make([]dto.ProviderAPIInfo, 0, len(def.APIConfigs)) for _, item := range def.APIConfigs { + apiModels := make([]dto.ProviderModelInfo, 0, len(item.Models)) + for _, model := range item.Models { + apiModels = append(apiModels, dto.ProviderModelInfo{ + ID: model.ID, + Name: model.Name, + }) + } apiTypes = append(apiTypes, dto.ProviderAPIInfo{ - APIType: item.APIType, - BaseURL: item.BaseURL, - EditableBaseURL: item.EditableBaseURL, - DefaultAuthMode: item.DefaultAuthMode, - AuthModes: item.AuthModes, + APIType: item.APIType, + BaseURL: item.BaseURL, + EditableBaseURL: item.EditableBaseURL, + SupportsModelDiscovery: item.DiscoverModels, + DefaultAuthMode: item.DefaultAuthMode, + AuthModes: item.AuthModes, + Models: apiModels, }) } baseURL, _ := providercatalog.DefaultBaseURL(key) providers = append(providers, dto.ProviderInfo{ Sort: def.Sort, Provider: key, - DisplayName: def.DisplayName, + DisplayName: localizedAgentProviderName(key), BaseURL: baseURL, DefaultAPIType: def.DefaultAPIType, APITypes: apiTypes, @@ -1016,7 +1025,7 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error { if err := ensureAgentAccountNameAvailable(provider, req.Name, 0); err != nil { return err } - initialModels, err := buildInitialAgentAccountModels(&model.AgentAccount{Provider: provider}, req.Models) + initialModels, err := buildInitialAgentAccountModels(&model.AgentAccount{Provider: provider, APIType: req.APIType}, req.Models) if err != nil { return err } @@ -1024,7 +1033,8 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error { if err != nil { return err } - resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel) + validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability + resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability) if err != nil { return err } @@ -1062,6 +1072,9 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error { if err != nil { return err } + if req.APIType != account.APIType { + return buserr.WithDetail("ErrInvalidParams", "API type cannot be changed", nil) + } provider := account.Provider if err := ensureAgentAccountNameAvailable(provider, req.Name, account.ID); err != nil { return err @@ -1078,7 +1091,8 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error { if err != nil { return err } - resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel) + validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability + resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability) if err != nil { return err } @@ -1110,6 +1124,12 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age if strings.TrimSpace(req.Provider) != "" { opts = append(opts, repo.WithByProvider(req.Provider)) } + if apiType := strings.TrimSpace(req.APIType); apiType != "" { + opts = append(opts, repo.WithByAPIType(apiType)) + } + if req.TextOnly { + opts = append(opts, repo.WithTextAPIType()) + } if strings.TrimSpace(req.Name) != "" { opts = append(opts, repo.WithByLikeName(req.Name)) } @@ -1127,7 +1147,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age ID: item.ID, MasterAccountID: item.MasterAccountID, Provider: item.Provider, - ProviderName: providercatalog.DisplayName(item.Provider), + ProviderName: localizedAgentProviderName(item.Provider), Name: item.Name, APIKey: apiKey, RememberAPIKey: item.RememberAPIKey, @@ -1166,7 +1186,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age } func (a AgentService) CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error) { - return agentAccountRepo.CountByProviders(req.Providers) + return agentAccountRepo.CountTextByProviders(req.Providers) } func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error) { @@ -1178,7 +1198,8 @@ func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.Agen } func (a AgentService) DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error) { - if req.APIType != "openai-completions" && req.APIType != "openai-responses" { + config, ok := providercatalog.FindAPIConfig(req.Provider, req.APIType) + if !ok || !config.DiscoverModels { return nil, buserr.New("ErrAgentAccountModelsRequired") } baseURL, err := providercatalog.ResolveBaseURL(req.Provider, req.APIType, req.BaseURL) @@ -1320,7 +1341,7 @@ func (a AgentService) SyncAgentsByAccount(account *model.AgentAccount) error { } func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error { - _, err := resolveAgentAccountInput(req.Provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, req.Model) + _, err := resolveAgentAccountInput(req.Provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, req.Model, true) return err } diff --git a/agent/app/service/agents_utils.go b/agent/app/service/agents_utils.go index 74997258d..e2de1335e 100644 --- a/agent/app/service/agents_utils.go +++ b/agent/app/service/agents_utils.go @@ -21,6 +21,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/common" agentenv "github.com/1Panel-dev/1Panel/agent/utils/env" "github.com/1Panel-dev/1Panel/agent/utils/files" @@ -96,7 +97,7 @@ func ensureContainerRunning(containerName string) error { return nil } -func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, modelID string) (resolvedAgentAccountInput, error) { +func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, modelID string, validateAvailability bool) (resolvedAgentAccountInput, error) { resolvedAPIKey := strings.TrimSpace(apiKey) resolvedAPIType := strings.TrimSpace(apiType) resolvedAuthMode, err := providercatalog.ResolveAuthMode(provider, resolvedAPIType, authMode) @@ -114,7 +115,8 @@ func resolveAgentAccountInput(provider, apiType, authMode, apiKey, baseURL, mode if modelID == "" { return resolvedAgentAccountInput{}, buserr.New("ErrAgentAccountModelsRequired") } - if !providercatalog.SkipVerification(provider) { + imageAPI := providercatalog.IsImageAPIType(resolvedAPIType) + if validateAvailability && (imageAPI || !providercatalog.SkipVerification(provider)) { if err := providercatalog.VerifyAccount(provider, resolvedAPIType, resolvedAuthMode, resolvedBaseURL, resolvedAPIKey, modelID); err != nil { return resolvedAgentAccountInput{}, err } @@ -386,7 +388,7 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map Remark: agent.Remark, AgentType: agentType, Provider: agent.Provider, - ProviderName: providercatalog.DisplayName(agent.Provider), + ProviderName: localizedAgentProviderName(agent.Provider), Model: agent.Model, APIType: agent.APIType, BaseURL: agent.BaseURL, @@ -429,6 +431,15 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map return item } +func localizedAgentProviderName(provider string) string { + if key := providercatalog.DisplayNameKey(provider); key != "" { + if name := strings.TrimSpace(i18n.GetMsgByKey(key)); name != "" { + return name + } + } + return providercatalog.DisplayName(provider) +} + func isAgentAppKey(appKey string) bool { return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent } @@ -1159,15 +1170,15 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto if len(requested) > 0 { return normalizeAgentAccountModels(account, requested) } - meta, ok := providercatalog.Get(account.Provider) - if !ok || len(meta.Models) == 0 { + defaultModels := providercatalog.DefaultModels(account.Provider, account.APIType) + if len(defaultModels) == 0 { if requiresInitialAgentAccountModels(account.Provider) { return nil, buserr.New("ErrAgentAccountModelsRequired") } return nil, nil } - requested = make([]dto.AgentAccountModel, 0, len(meta.Models)) - for _, item := range meta.Models { + requested = make([]dto.AgentAccountModel, 0, len(defaultModels)) + for _, item := range defaultModels { requested = append(requested, dto.AgentAccountModel{ ID: item.ID, Name: item.Name, diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 671bf4c44..0722d10b8 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'Model mapping cannot be empty' ErrAIProxyModelMapEmptyExternalModel: 'Model mapping contains an empty request model' ErrAIProxyModelMapEmptyUpstreamModel: 'Upstream model for {{ .model }} cannot be empty' AIProxyUserFallback: 'Deleted user (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 837cf872d..b30ed5979 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'El mapeo de modelos no puede estar vacío' ErrAIProxyModelMapEmptyExternalModel: 'El mapeo de modelos contiene un modelo solicitado vacío' ErrAIProxyModelMapEmptyUpstreamModel: 'El modelo upstream de {{ .model }} no puede estar vacío' AIProxyUserFallback: 'Usuario eliminado (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/fa.yaml b/agent/i18n/lang/fa.yaml index 1d3d1ed28..73b5b2235 100644 --- a/agent/i18n/lang/fa.yaml +++ b/agent/i18n/lang/fa.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'نگاشت مدل نمیتواند خالی باش ErrAIProxyModelMapEmptyExternalModel: 'نگاشت مدل شامل یک مدل درخواست خالی است' ErrAIProxyModelMapEmptyUpstreamModel: 'مدل بالادست برای {{ .model }} نمیتواند خالی باشد' AIProxyUserFallback: 'کاربر حذف شده (شناسه: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 44f5d1ae5..4f16d4cca 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'モデルマッピングは空にできません' ErrAIProxyModelMapEmptyExternalModel: 'モデルマッピングに空のリクエストモデルがあります' ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} の上流モデルは空にできません' AIProxyUserFallback: '削除済みユーザー (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 32f26b10b..e19034683 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: '모델 매핑은 비워 둘 수 없습니다' ErrAIProxyModelMapEmptyExternalModel: '모델 매핑에 빈 요청 모델이 있습니다' ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }}의 업스트림 모델은 비워 둘 수 없습니다' AIProxyUserFallback: '삭제된 사용자 (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/lo.yaml b/agent/i18n/lang/lo.yaml index 643038829..27a8aeb9d 100644 --- a/agent/i18n/lang/lo.yaml +++ b/agent/i18n/lang/lo.yaml @@ -640,3 +640,7 @@ ErrAIProxyModelMapEmpty: 'ການຈັບຄູ່ໂມເດລບໍ່ສ ErrAIProxyModelMapEmptyExternalModel: 'ການຈັບຄູ່ໂມເດລມີໂມເດລຄຳຮ້ອງຂໍທີ່ຫວ່າງເປົ່າ' ErrAIProxyModelMapEmptyUpstreamModel: 'ໂມເດລ Upstream ສຳລັບ {{ .model }} ບໍ່ສາມາດຫວ່າງເປົ່າໄດ້' AIProxyUserFallback: 'ຜູ້ໃຊ້ທີ່ຖືກລຶບ (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index d172d03c3..4bfb7fd68 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'Pemetaan model tidak boleh kosong' ErrAIProxyModelMapEmptyExternalModel: 'Pemetaan model mengandungi model permintaan kosong' ErrAIProxyModelMapEmptyUpstreamModel: 'Model upstream untuk {{ .model }} tidak boleh kosong' AIProxyUserFallback: 'Pengguna dipadam (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 40d6f8889..222305822 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'O mapeamento de modelos não pode estar vazio' ErrAIProxyModelMapEmptyExternalModel: 'O mapeamento de modelos contém um modelo solicitado vazio' ErrAIProxyModelMapEmptyUpstreamModel: 'O modelo upstream de {{ .model }} não pode estar vazio' AIProxyUserFallback: 'Usuário excluído (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 6758ea2ed..5f90328d9 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'Сопоставление моделей не мож ErrAIProxyModelMapEmptyExternalModel: 'Сопоставление моделей содержит пустую запрошенную модель' ErrAIProxyModelMapEmptyUpstreamModel: 'Upstream-модель для {{ .model }} не может быть пустой' AIProxyUserFallback: 'Удаленный пользователь (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 9f50b83ab..6d17f0a5c 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: 'Model eşlemesi boş olamaz' ErrAIProxyModelMapEmptyExternalModel: 'Model eşlemesi boş bir istek modeli içeriyor' ErrAIProxyModelMapEmptyUpstreamModel: '{{ .model }} için upstream model boş olamaz' AIProxyUserFallback: 'Silinen kullanıcı (ID: {{ .id }})' +AIProviderBailian: 'Alibaba Cloud Model Studio' +AIProviderBailianCodingPlan: 'Alibaba Cloud Model Studio Coding Plan' +AIProviderArk: 'Volcengine Ark' +AIProviderArkCodingPlan: 'Volcengine Ark Coding Plan' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index a0cd160ba..72e0433b4 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: '模型映射不能為空' ErrAIProxyModelMapEmptyExternalModel: '模型映射中存在空的請求模型' ErrAIProxyModelMapEmptyUpstreamModel: '模型 {{ .model }} 的上游模型不能為空' AIProxyUserFallback: '已刪除使用者 (ID: {{ .id }})' +AIProviderBailian: '阿里雲百煉' +AIProviderBailianCodingPlan: '阿里雲百煉 Coding Plan' +AIProviderArk: '火山方舟' +AIProviderArkCodingPlan: '火山方舟 Coding Plan' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 877579054..2dcc5993f 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: "模型映射不能为空" ErrAIProxyModelMapEmptyExternalModel: "模型映射中存在空的请求模型" ErrAIProxyModelMapEmptyUpstreamModel: "模型 {{ .model }} 的上游模型不能为空" AIProxyUserFallback: "已删除用户 (ID: {{ .id }})" +AIProviderBailian: "阿里云百炼" +AIProviderBailianCodingPlan: "阿里云百炼 Coding Plan" +AIProviderArk: "火山方舟" +AIProviderArkCodingPlan: "火山方舟 Coding Plan" diff --git a/agent/utils/terminal/ai/config_runtime.go b/agent/utils/terminal/ai/config_runtime.go index e6d839abf..427141431 100644 --- a/agent/utils/terminal/ai/config_runtime.go +++ b/agent/utils/terminal/ai/config_runtime.go @@ -50,6 +50,10 @@ func ResolveGeneratorConfig(accountID uint) (GeneratorConfig, time.Duration, err if provider == "" { return GeneratorConfig{}, 0, fmt.Errorf("agent account provider is required") } + apiType := strings.TrimSpace(account.APIType) + if providercatalog.IsImageAPIType(apiType) { + return GeneratorConfig{}, 0, fmt.Errorf("api type %s does not support text generation", apiType) + } model, err := resolveAccountModelConfig(account.ID, provider) if err != nil { return GeneratorConfig{}, 0, err @@ -72,7 +76,7 @@ func ResolveGeneratorConfig(accountID uint) (GeneratorConfig, time.Duration, err BaseURL: baseURL, APIKey: strings.TrimSpace(apiKey), Model: model, - APIType: strings.TrimSpace(account.APIType), + APIType: apiType, AuthMode: strings.TrimSpace(account.AuthMode), MaxTokens: defaultTerminalTokens, }, 30 * time.Second, nil diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index fa318cd8a..c31a3fb3c 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -198,6 +198,23 @@ SubTask: "Subtask" Skip: "Ignore errors and continue" Success: "Success" +# AI gateway smart routing tasks +AIProxySmartRouteCreateTask: "Create AI gateway smart routing samples" +AIProxySmartRouteCreateSubTask: "Create samples and generate vectors" +AIProxySmartRouteCreateStart: "Starting sample creation, {{ .total }} in total" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Creation failed: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Created: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Creation completed: {{ .success }} succeeded, {{ .failed }} failed" +AIProxySmartRouteCreateError: "Sample creation failed: {{ .failed }} failed, last error" +AIProxySmartRouteRebuildTask: "Rebuild AI gateway smart routing sample vectors" +AIProxySmartRouteRebuildSubTask: "Rebuild sample vectors" +AIProxySmartRouteEmbeddingRequired: "Embedding endpoint and model are required" +AIProxySmartRouteRebuildStart: "Starting sample vector rebuild, {{ .total }} in total" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Rebuild failed: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Rebuilt: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Rebuild completed: {{ .success }} succeeded, {{ .failed }} failed" +AIProxySmartRouteRebuildError: "Sample vector rebuild failed: {{ .failed }} failed, last error" + # agent batch task BatchInstallAgent: "Batch install agent" DispatchAgentInstallTasks: "Dispatch agent install tasks" diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml index 09855870f..c84164c84 100644 --- a/core/i18n/lang/es-ES.yaml +++ b/core/i18n/lang/es-ES.yaml @@ -198,6 +198,23 @@ SubTask: "Subtarea" Skip: "Omitir errores y continuar..." Success: "Éxito" +# tareas de enrutamiento inteligente del gateway de IA +AIProxySmartRouteCreateTask: "Crear muestras de enrutamiento inteligente del gateway de IA" +AIProxySmartRouteCreateSubTask: "Crear muestras y generar vectores" +AIProxySmartRouteCreateStart: "Iniciando la creación de muestras, {{ .total }} en total" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Error de creación: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Creada: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Creación completada: {{ .success }} correctas, {{ .failed }} fallidas" +AIProxySmartRouteCreateError: "Error al crear muestras: {{ .failed }} fallidas, último error" +AIProxySmartRouteRebuildTask: "Reconstruir vectores de muestras de enrutamiento inteligente del gateway de IA" +AIProxySmartRouteRebuildSubTask: "Reconstruir vectores de muestras" +AIProxySmartRouteEmbeddingRequired: "El endpoint y el modelo de Embedding son obligatorios" +AIProxySmartRouteRebuildStart: "Iniciando la reconstrucción de vectores, {{ .total }} en total" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Error de reconstrucción: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Reconstruida: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Reconstrucción completada: {{ .success }} correctas, {{ .failed }} fallidas" +AIProxySmartRouteRebuildError: "Error al reconstruir vectores de muestras: {{ .failed }} fallidas, último error" + # tarea por lotes de agentes BatchInstallAgent: "Instalación masiva de agentes" DispatchAgentInstallTasks: "Enviar tareas de instalación de agentes" diff --git a/core/i18n/lang/fa.yaml b/core/i18n/lang/fa.yaml index 8da79f3d0..459296eec 100644 --- a/core/i18n/lang/fa.yaml +++ b/core/i18n/lang/fa.yaml @@ -198,6 +198,23 @@ SubTask: "وظیفه فرعی" Skip: "نادیده گرفتن خطاها و ادامه" Success: "موفق" +# وظایف مسیریابی هوشمند دروازه AI +AIProxySmartRouteCreateTask: "ایجاد نمونههای مسیریابی هوشمند دروازه AI" +AIProxySmartRouteCreateSubTask: "ایجاد نمونهها و تولید بردارها" +AIProxySmartRouteCreateStart: "شروع ایجاد نمونهها، در مجموع {{ .total }}" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] ایجاد ناموفق بود: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] ایجاد شد: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "ایجاد کامل شد: {{ .success }} موفق، {{ .failed }} ناموفق" +AIProxySmartRouteCreateError: "ایجاد نمونهها ناموفق بود: {{ .failed }} ناموفق، آخرین خطا" +AIProxySmartRouteRebuildTask: "بازسازی بردارهای نمونه مسیریابی هوشمند دروازه AI" +AIProxySmartRouteRebuildSubTask: "بازسازی بردارهای نمونه" +AIProxySmartRouteEmbeddingRequired: "آدرس و مدل Embedding الزامی است" +AIProxySmartRouteRebuildStart: "شروع بازسازی بردارهای نمونه، در مجموع {{ .total }}" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] بازسازی ناموفق بود: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] بازسازی شد: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "بازسازی کامل شد: {{ .success }} موفق، {{ .failed }} ناموفق" +AIProxySmartRouteRebuildError: "بازسازی بردارهای نمونه ناموفق بود: {{ .failed }} ناموفق، آخرین خطا" + # وظیفه دستهای عامل BatchInstallAgent: "نصب دستهای عامل" DispatchAgentInstallTasks: "ارسال وظایف نصب عامل" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index ebc4b52f0..fa368ac79 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -198,6 +198,23 @@ SubTask: "サブタスク" Skip: "エラーを無視して続行..." Success: "成功" +# AI ゲートウェイスマートルーティングタスク +AIProxySmartRouteCreateTask: "AI ゲートウェイのスマートルーティングサンプルを作成" +AIProxySmartRouteCreateSubTask: "サンプルを作成してベクトルを生成" +AIProxySmartRouteCreateStart: "サンプルの作成を開始します。合計 {{ .total }} 件" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] 作成失敗:type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] 作成成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "作成完了:成功 {{ .success }} 件、失敗 {{ .failed }} 件" +AIProxySmartRouteCreateError: "サンプルの作成に失敗しました:{{ .failed }} 件失敗、最後のエラー" +AIProxySmartRouteRebuildTask: "AI ゲートウェイのスマートルーティングサンプルベクトルを再構築" +AIProxySmartRouteRebuildSubTask: "サンプルベクトルを再構築" +AIProxySmartRouteEmbeddingRequired: "Embedding エンドポイントとモデルは必須です" +AIProxySmartRouteRebuildStart: "サンプルベクトルの再構築を開始します。合計 {{ .total }} 件" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] 再構築失敗:sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] 再構築成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "再構築完了:成功 {{ .success }} 件、失敗 {{ .failed }} 件" +AIProxySmartRouteRebuildError: "サンプルベクトルの再構築に失敗しました:{{ .failed }} 件失敗、最後のエラー" + # エージェント一括タスク BatchInstallAgent: "エージェントの一括インストール" DispatchAgentInstallTasks: "エージェントインストールタスクを配信" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index b02a14870..ca80845ca 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -198,6 +198,23 @@ SubTask: "서브 작업" Skip: "오류 무시하고 계속..." Success: "성공" +# AI 게이트웨이 스마트 라우팅 작업 +AIProxySmartRouteCreateTask: "AI 게이트웨이 스마트 라우팅 샘플 생성" +AIProxySmartRouteCreateSubTask: "샘플 생성 및 벡터 생성" +AIProxySmartRouteCreateStart: "샘플 생성을 시작합니다. 총 {{ .total }}개" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] 생성 실패: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] 생성 성공: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "생성 완료: 성공 {{ .success }}개, 실패 {{ .failed }}개" +AIProxySmartRouteCreateError: "샘플 생성 실패: {{ .failed }}개 실패, 마지막 오류" +AIProxySmartRouteRebuildTask: "AI 게이트웨이 스마트 라우팅 샘플 벡터 재구성" +AIProxySmartRouteRebuildSubTask: "샘플 벡터 재구성" +AIProxySmartRouteEmbeddingRequired: "Embedding 엔드포인트와 모델은 필수입니다" +AIProxySmartRouteRebuildStart: "샘플 벡터 재구성을 시작합니다. 총 {{ .total }}개" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] 재구성 실패: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] 재구성 성공: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "재구성 완료: 성공 {{ .success }}개, 실패 {{ .failed }}개" +AIProxySmartRouteRebuildError: "샘플 벡터 재구성 실패: {{ .failed }}개 실패, 마지막 오류" + # 에이전트 일괄 작업 BatchInstallAgent: "에이전트 일괄 설치" DispatchAgentInstallTasks: "에이전트 설치 작업 배포" diff --git a/core/i18n/lang/lo.yaml b/core/i18n/lang/lo.yaml index 0ee1e6282..d8368d7af 100644 --- a/core/i18n/lang/lo.yaml +++ b/core/i18n/lang/lo.yaml @@ -198,6 +198,23 @@ SubTask: "ວຽກຍ່ອຍ" Skip: "ບໍ່ສົນໃຈຂໍ້ຜິດພາດ ແລະ ສືບຕໍ່" Success: "ສຳເລັດ" +# ວຽກ Smart Route ຂອງ AI Gateway +AIProxySmartRouteCreateTask: "ສ້າງຕົວຢ່າງ Smart Route ຂອງ AI Gateway" +AIProxySmartRouteCreateSubTask: "ສ້າງຕົວຢ່າງ ແລະ ສ້າງ Vector" +AIProxySmartRouteCreateStart: "ເລີ່ມສ້າງຕົວຢ່າງ, ທັງໝົດ {{ .total }}" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] ສ້າງບໍ່ສຳເລັດ: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] ສ້າງສຳເລັດ: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "ສ້າງສຳເລັດ: {{ .success }} ສຳເລັດ, {{ .failed }} ບໍ່ສຳເລັດ" +AIProxySmartRouteCreateError: "ສ້າງຕົວຢ່າງບໍ່ສຳເລັດ: {{ .failed }} ລາຍການ, ຂໍ້ຜິດພາດຫຼ້າສຸດ" +AIProxySmartRouteRebuildTask: "ສ້າງ Vector ຕົວຢ່າງ Smart Route ຂອງ AI Gateway ຄືນໃໝ່" +AIProxySmartRouteRebuildSubTask: "ສ້າງ Vector ຕົວຢ່າງຄືນໃໝ່" +AIProxySmartRouteEmbeddingRequired: "ຕ້ອງລະບຸ Endpoint ແລະ Model ຂອງ Embedding" +AIProxySmartRouteRebuildStart: "ເລີ່ມສ້າງ Vector ຕົວຢ່າງຄືນໃໝ່, ທັງໝົດ {{ .total }}" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] ສ້າງຄືນໃໝ່ບໍ່ສຳເລັດ: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] ສ້າງຄືນໃໝ່ສຳເລັດ: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "ສ້າງຄືນໃໝ່ສຳເລັດ: {{ .success }} ສຳເລັດ, {{ .failed }} ບໍ່ສຳເລັດ" +AIProxySmartRouteRebuildError: "ສ້າງ Vector ຕົວຢ່າງຄືນໃໝ່ບໍ່ສຳເລັດ: {{ .failed }} ລາຍການ, ຂໍ້ຜິດພາດຫຼ້າສຸດ" + # agent batch task BatchInstallAgent: "ຕິດຕັ້ງເອເຈນແບບກຸ່ມ" DispatchAgentInstallTasks: "ຈັດສົ່ງວຽກຕິດຕັ້ງເອເຈນ" diff --git a/core/i18n/lang/ms.yaml b/core/i18n/lang/ms.yaml index 5e581c7a8..b3b3ca73b 100644 --- a/core/i18n/lang/ms.yaml +++ b/core/i18n/lang/ms.yaml @@ -198,6 +198,23 @@ SubTask: "Tugas Sub" Skip: "Abaikan ralat dan teruskan..." Success: "Berjaya" +# tugas penghalaan pintar get laluan AI +AIProxySmartRouteCreateTask: "Cipta sampel penghalaan pintar get laluan AI" +AIProxySmartRouteCreateSubTask: "Cipta sampel dan jana vektor" +AIProxySmartRouteCreateStart: "Memulakan penciptaan sampel, jumlah {{ .total }}" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Penciptaan gagal: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Berjaya dicipta: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Penciptaan selesai: {{ .success }} berjaya, {{ .failed }} gagal" +AIProxySmartRouteCreateError: "Penciptaan sampel gagal: {{ .failed }} gagal, ralat terakhir" +AIProxySmartRouteRebuildTask: "Bina semula vektor sampel penghalaan pintar get laluan AI" +AIProxySmartRouteRebuildSubTask: "Bina semula vektor sampel" +AIProxySmartRouteEmbeddingRequired: "Endpoint dan model Embedding diperlukan" +AIProxySmartRouteRebuildStart: "Memulakan pembinaan semula vektor sampel, jumlah {{ .total }}" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Pembinaan semula gagal: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Berjaya dibina semula: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Pembinaan semula selesai: {{ .success }} berjaya, {{ .failed }} gagal" +AIProxySmartRouteRebuildError: "Pembinaan semula vektor sampel gagal: {{ .failed }} gagal, ralat terakhir" + # tugas kelompok ejen BatchInstallAgent: "Pasang ejen secara pukal" DispatchAgentInstallTasks: "Hantar tugas pemasangan ejen" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index 0bb7bb6a6..b7357fcc7 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -198,6 +198,23 @@ SubTask: "Subtarefa" Skip: "Ignorar erros e continuar..." Success: "Sucesso" +# tarefas de roteamento inteligente do gateway de IA +AIProxySmartRouteCreateTask: "Criar amostras de roteamento inteligente do gateway de IA" +AIProxySmartRouteCreateSubTask: "Criar amostras e gerar vetores" +AIProxySmartRouteCreateStart: "Iniciando a criação de amostras, {{ .total }} no total" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Falha na criação: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Criada: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Criação concluída: {{ .success }} com sucesso, {{ .failed }} com falha" +AIProxySmartRouteCreateError: "Falha ao criar amostras: {{ .failed }} com falha, último erro" +AIProxySmartRouteRebuildTask: "Reconstruir vetores de amostras de roteamento inteligente do gateway de IA" +AIProxySmartRouteRebuildSubTask: "Reconstruir vetores de amostras" +AIProxySmartRouteEmbeddingRequired: "O endpoint e o modelo de Embedding são obrigatórios" +AIProxySmartRouteRebuildStart: "Iniciando a reconstrução dos vetores, {{ .total }} no total" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Falha na reconstrução: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Reconstruída: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Reconstrução concluída: {{ .success }} com sucesso, {{ .failed }} com falha" +AIProxySmartRouteRebuildError: "Falha ao reconstruir vetores de amostras: {{ .failed }} com falha, último erro" + # tarefa em lote do agente BatchInstallAgent: "Instalar agentes em lote" DispatchAgentInstallTasks: "Distribuir tarefas de instalação de agentes" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index 19aa042ae..488923d37 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -198,6 +198,23 @@ SubTask: "Подзадача" Skip: "Пропустить ошибки и продолжить..." Success: "Успешно" +# задачи умной маршрутизации AI-шлюза +AIProxySmartRouteCreateTask: "Создание образцов умной маршрутизации AI-шлюза" +AIProxySmartRouteCreateSubTask: "Создание образцов и генерация векторов" +AIProxySmartRouteCreateStart: "Начато создание образцов, всего: {{ .total }}" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Ошибка создания: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Создано: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Создание завершено: успешно {{ .success }}, с ошибкой {{ .failed }}" +AIProxySmartRouteCreateError: "Не удалось создать образцы: ошибок {{ .failed }}, последняя ошибка" +AIProxySmartRouteRebuildTask: "Перестроение векторов образцов умной маршрутизации AI-шлюза" +AIProxySmartRouteRebuildSubTask: "Перестроение векторов образцов" +AIProxySmartRouteEmbeddingRequired: "Требуются endpoint и модель Embedding" +AIProxySmartRouteRebuildStart: "Начато перестроение векторов образцов, всего: {{ .total }}" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Ошибка перестроения: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Перестроено: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Перестроение завершено: успешно {{ .success }}, с ошибкой {{ .failed }}" +AIProxySmartRouteRebuildError: "Не удалось перестроить векторы образцов: ошибок {{ .failed }}, последняя ошибка" + # пакетная задача агента BatchInstallAgent: "Пакетная установка агентов" DispatchAgentInstallTasks: "Отправить задачи установки агентов" diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml index 1e87510cf..73a2dd9d8 100644 --- a/core/i18n/lang/tr.yaml +++ b/core/i18n/lang/tr.yaml @@ -198,6 +198,23 @@ SubTask: "Alt görev" Skip: "Hataları atla ve devam et..." Success: "Başarılı" +# AI ağ geçidi akıllı yönlendirme görevleri +AIProxySmartRouteCreateTask: "AI ağ geçidi akıllı yönlendirme örnekleri oluştur" +AIProxySmartRouteCreateSubTask: "Örnekler oluştur ve vektörleri üret" +AIProxySmartRouteCreateStart: "Örnek oluşturma başlatılıyor, toplam {{ .total }}" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] Oluşturma başarısız: type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] Oluşturuldu: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "Oluşturma tamamlandı: {{ .success }} başarılı, {{ .failed }} başarısız" +AIProxySmartRouteCreateError: "Örnek oluşturma başarısız: {{ .failed }} başarısız, son hata" +AIProxySmartRouteRebuildTask: "AI ağ geçidi akıllı yönlendirme örnek vektörlerini yeniden oluştur" +AIProxySmartRouteRebuildSubTask: "Örnek vektörlerini yeniden oluştur" +AIProxySmartRouteEmbeddingRequired: "Embedding endpoint ve modeli gereklidir" +AIProxySmartRouteRebuildStart: "Örnek vektörleri yeniden oluşturuluyor, toplam {{ .total }}" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] Yeniden oluşturma başarısız: sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] Yeniden oluşturuldu: sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "Yeniden oluşturma tamamlandı: {{ .success }} başarılı, {{ .failed }} başarısız" +AIProxySmartRouteRebuildError: "Örnek vektörlerini yeniden oluşturma başarısız: {{ .failed }} başarısız, son hata" + # ajan toplu görevi BatchInstallAgent: "Aracıları toplu kur" DispatchAgentInstallTasks: "Aracı kurulum görevlerini gönder" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index f0af3270d..356d2504b 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -198,6 +198,23 @@ SubTask: "子任務" Skip: "忽略錯誤並繼續..." Success: "成功" +# AI 閘道智慧路由任務 +AIProxySmartRouteCreateTask: "建立 AI 閘道智慧路由樣本" +AIProxySmartRouteCreateSubTask: "建立樣本並產生向量" +AIProxySmartRouteCreateStart: "開始建立樣本,共 {{ .total }} 條" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] 建立失敗:type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] 建立成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "建立完成:成功 {{ .success }} 條,失敗 {{ .failed }} 條" +AIProxySmartRouteCreateError: "建立樣本失敗:失敗 {{ .failed }} 條,最後錯誤" +AIProxySmartRouteRebuildTask: "重建 AI 閘道智慧路由樣本向量" +AIProxySmartRouteRebuildSubTask: "重建樣本向量" +AIProxySmartRouteEmbeddingRequired: "Embedding 位址和模型不能為空" +AIProxySmartRouteRebuildStart: "開始重建樣本向量,共 {{ .total }} 條" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] 重建失敗:sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] 重建成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "重建完成:成功 {{ .success }} 條,失敗 {{ .failed }} 條" +AIProxySmartRouteRebuildError: "重建樣本向量失敗:失敗 {{ .failed }} 條,最後錯誤" + # 智慧體批次任務 BatchInstallAgent: "批量安裝智能體" DispatchAgentInstallTasks: "下發智能體安裝任務" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 2c9f7aecf..90d427e88 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -198,6 +198,23 @@ SubTask: "子任务" Skip: "忽略错误并继续..." Success: "成功" +# AI 网关智能路由任务 +AIProxySmartRouteCreateTask: "创建 AI 网关智能路由样本" +AIProxySmartRouteCreateSubTask: "创建样本并生成向量" +AIProxySmartRouteCreateStart: "开始创建样本,共 {{ .total }} 条" +AIProxySmartRouteCreateFailed: "[{{ .current }}/{{ .total }}] 创建失败:type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteCreateSuccess: "[{{ .current }}/{{ .total }}] 创建成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteCreateComplete: "创建完成:成功 {{ .success }} 条,失败 {{ .failed }} 条" +AIProxySmartRouteCreateError: "创建样本失败:失败 {{ .failed }} 条,最后错误" +AIProxySmartRouteRebuildTask: "重建 AI 网关智能路由样本向量" +AIProxySmartRouteRebuildSubTask: "重建样本向量" +AIProxySmartRouteEmbeddingRequired: "Embedding 地址和模型不能为空" +AIProxySmartRouteRebuildStart: "开始重建样本向量,共 {{ .total }} 条" +AIProxySmartRouteRebuildFailed: "[{{ .current }}/{{ .total }}] 重建失败:sampleID={{ .id }} type={{ .type }} label={{ .label }} error={{ .err }}" +AIProxySmartRouteRebuildSuccess: "[{{ .current }}/{{ .total }}] 重建成功:sampleID={{ .id }} type={{ .type }} label={{ .label }}" +AIProxySmartRouteRebuildComplete: "重建完成:成功 {{ .success }} 条,失败 {{ .failed }} 条" +AIProxySmartRouteRebuildError: "重建样本向量失败:失败 {{ .failed }} 条,最后错误" + # 智能体批量任务 BatchInstallAgent: "批量安装智能体" DispatchAgentInstallTasks: "下发智能体安装任务" diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index e8a4feb3e..8787aecf6 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -556,8 +556,10 @@ export namespace AI { apiType: string; baseUrl: string; editableBaseUrl: boolean; + supportsModelDiscovery: boolean; defaultAuthMode: string; authModes: string[]; + models: ProviderModelInfo[]; } export interface ProviderInfo { @@ -579,6 +581,7 @@ export namespace AI { authMode: string; models?: AgentAccountModel[]; verifyModel: string; + validateAvailability: boolean; remark: string; } @@ -591,6 +594,7 @@ export namespace AI { apiType: string; authMode: string; verifyModel: string; + validateAvailability: boolean; remark: string; syncAgents: boolean; } @@ -599,6 +603,8 @@ export namespace AI { page: number; pageSize: number; provider: string; + apiType?: string; + textOnly?: boolean; name: string; } diff --git a/frontend/src/components/agent-provider-label/index.vue b/frontend/src/components/agent-provider-label/index.vue new file mode 100644 index 000000000..3c078919b --- /dev/null +++ b/frontend/src/components/agent-provider-label/index.vue @@ -0,0 +1,43 @@ + + + + {{ name }} + + + + + + diff --git a/frontend/src/components/api-type-tag/index.vue b/frontend/src/components/api-type-tag/index.vue new file mode 100644 index 000000000..58a8fc244 --- /dev/null +++ b/frontend/src/components/api-type-tag/index.vue @@ -0,0 +1,44 @@ + + + {{ apiType || '-' }} + + + + + + diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index b69a1cb63..5516b0c83 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -742,6 +742,7 @@ const message = { manualModelConfiguration: 'Manual setup', discoverModelsHelper: 'Get models from the provider /v1/models API and add all of them to the model pool', discoverModelsFailedFallback: 'Failed to get models. Configure a model manually.', + discoverModelsFailedUseDefaults: 'Failed to get models. Default models are being used.', accountModelsDuplicate: 'Duplicate models were found in the catalog', modelPool: 'Model Pool', fallbackModels: 'Fallback Models', @@ -894,8 +895,13 @@ const message = { 'Saving the username or password will automatically rebuild the container so the changes take effect.', channelDeleteConfirm: 'Delete the {0} channel configuration?', apiTypeBaseURLHelper: 'This type sends requests to {0}. Recommended Base URL: {1}', + customImageURLHelper: + 'Enter the complete image generation endpoint for Custom accounts. The path is not appended automatically. Example: {0}', apiTypeBaseURLMismatch: 'The current Base URL looks like a {0} path, but {1} is selected. Recommended URL: {2}', + validateAvailability: 'Validate account availability', + validateAvailabilityHelper: 'Send a minimal message before saving to validate the API', + validateImageAvailabilityHelper: 'Generates one minimal image before saving and may incur charges', }, model: { model: 'Models', @@ -955,6 +961,7 @@ const message = { }, aiProxy: { title: 'AI Gateway', + apiReference: 'API Reference', notInstalled: 'Not installed', usage: 'Usage', accountPool: 'Account Pool', @@ -1076,13 +1083,20 @@ const message = { validationModelMapEmpty: 'Model mapping names cannot be empty', validationModelMapDuplicate: 'Request model {0} is duplicated', modelGroup: 'Model Group', + modelCapability: 'Model Capability', + textGeneration: 'Text Generation', + imageGeneration: 'Image Generation', + textModels: 'Text Models', + textToImageModels: 'Text-to-Image Models', + unauthorized: 'Unauthorized', + imageCount: 'Image Count', availableModels: 'Available Models', modelGroupModels: 'Request Models', modelGroupModelsPlaceholder: 'Select or enter request model names', modelGroupModelsHelper: - 'Only enter real upstream models; smart routing tries models in order and does not load balance between models.', + 'Enter the request model names used by clients to call the AI Gateway; smart routing tries models in order and does not load balance between models.', modelGroupGuide: - 'Model groups only contain real upstream models. auto is a virtual model name used by clients when requesting the AI Gateway.', + 'Model groups contain request model names exposed by the AI Gateway. Configure upstream models in the account pool model mapping. auto is a virtual model name and does not belong in a model group.', modelGroupModelCount: '{0} models', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 8dacb5361..9a59a7ba5 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -742,6 +742,8 @@ const message = { discoverModelsHelper: 'Obtenga los modelos mediante la API /v1/models del proveedor y añádalos todos al conjunto de modelos', discoverModelsFailedFallback: 'No se pudieron obtener los modelos. Configure un modelo manualmente.', + discoverModelsFailedUseDefaults: + 'No se pudieron obtener los modelos. Se usarán los modelos predeterminados.', accountModelsDuplicate: 'Existen modelos duplicados en el catálogo', modelPool: 'Pool de modelos', fallbackModels: 'Modelos de respaldo', @@ -899,8 +901,13 @@ const message = { 'Al guardar el usuario o la contraseña, el contenedor se reconstruirá automáticamente para que los cambios surtan efecto.', channelDeleteConfirm: '¿Eliminar la configuración del canal {0}?', apiTypeBaseURLHelper: 'Este tipo envía solicitudes a {0}. Base URL recomendada: {1}', + customImageURLHelper: + 'Introduce la URL completa del endpoint de generación de imágenes para cuentas Custom. La ruta no se añade automáticamente. Ejemplo: {0}', apiTypeBaseURLMismatch: 'La Base URL actual parece una ruta {0}, pero está seleccionado {1}. URL recomendada: {2}', + validateAvailability: 'Validar disponibilidad de la cuenta', + validateAvailabilityHelper: 'Envía un mensaje mínimo antes de guardar para validar la API', + validateImageAvailabilityHelper: 'Genera una imagen mínima antes de guardar y puede generar costes', }, model: { model: 'Modelo', @@ -962,6 +969,7 @@ const message = { }, aiProxy: { title: 'Gateway de IA', + apiReference: 'Referencia de API', notInstalled: 'No instalado', usage: 'Uso', accountPool: 'Grupo de cuentas', @@ -1085,13 +1093,20 @@ const message = { validationModelMapEmpty: 'Los nombres del mapeo de modelos no pueden estar vacíos', validationModelMapDuplicate: 'El modelo solicitado {0} está duplicado', modelGroup: 'Grupo de modelos', + modelCapability: 'Capacidad del modelo', + textGeneration: 'Generación de texto', + imageGeneration: 'Generación de imágenes', + textModels: 'Modelos de texto', + textToImageModels: 'Modelos de texto a imagen', + unauthorized: 'Sin autorización', + imageCount: 'Cantidad de imágenes', availableModels: 'Modelos disponibles', modelGroupModels: 'Modelos solicitados', modelGroupModelsPlaceholder: 'Seleccione o ingrese nombres de modelos solicitados', modelGroupModelsHelper: - 'Ingrese solo modelos upstream reales; el enrutamiento inteligente prueba los modelos en orden y no balancea carga entre modelos.', + 'Ingrese los nombres de modelo que usan los clientes al llamar al gateway de IA; el enrutamiento inteligente prueba los modelos en orden y no balancea carga entre modelos.', modelGroupGuide: - 'Los grupos de modelos solo contienen modelos upstream reales. auto es un nombre de modelo virtual usado por los clientes al llamar al gateway de IA.', + 'Los grupos de modelos contienen los nombres de modelo expuestos por el gateway de IA. Configure los modelos upstream en el mapeo de modelos del pool de cuentas. auto es un nombre de modelo virtual y no debe añadirse al grupo.', modelGroupModelCount: '{0} modelos', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 00240dc85..58d9cdf63 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -730,6 +730,7 @@ const message = { discoverModelsHelper: 'مدلها را از API /v1/models ارائهدهنده دریافت و همه را به مجموعه مدلها اضافه کنید', discoverModelsFailedFallback: 'دریافت مدلها ناموفق بود. مدل را بهصورت دستی پیکربندی کنید.', + discoverModelsFailedUseDefaults: 'دریافت مدلها ناموفق بود. از مدلهای پیشفرض استفاده میشود.', accountModelsDuplicate: 'مدلهای تکراری در کاتالوگ یافت شد', modelPool: 'مجموعه مدلها', fallbackModels: 'مدلهای جایگزین', @@ -878,6 +879,13 @@ const message = { 'برای شروع کار ورود با QR کلیک کنید. کد QR در لاگ وظیفه ظاهر میشود و پس از اسکان موفق، کانتینر به طور خودکار مجدداً راهاندازی میشود.', channelAutoRestartHelper: 'ذخیره به طور خودکار کانتینر را مجدداً راهاندازی میکند تا تغییرات اعمال شوند.', channelDeleteConfirm: 'پیکربندی کانال {0} حذف شود؟', + apiTypeBaseURLHelper: 'این نوع درخواستها را به {0} ارسال میکند. Base URL پیشنهادی: {1}', + apiTypeBaseURLMismatch: 'Base URL فعلی شبیه مسیر {0} است، اما {1} انتخاب شده است. URL پیشنهادی: {2}', + customImageURLHelper: + 'برای حساب Custom، URL کامل رابط تولید تصویر را وارد کنید. مسیر بهصورت خودکار افزوده نمیشود. نمونه: {0}', + validateAvailability: 'اعتبارسنجی دسترسپذیری حساب', + validateAvailabilityHelper: 'پیش از ذخیره یک پیام حداقلی برای بررسی API ارسال میشود', + validateImageAvailabilityHelper: 'پیش از ذخیره یک تصویر حداقلی تولید میشود و ممکن است هزینه داشته باشد', }, model: { model: 'مدلها', @@ -937,6 +945,7 @@ const message = { }, aiProxy: { title: 'دروازه AI', + apiReference: 'راهنمای API', notInstalled: 'نصب نشده', usage: 'استفاده', accountPool: 'مجموعه حسابها', @@ -1058,13 +1067,20 @@ const message = { validationModelMapEmpty: 'نامهای نگاشت مدل نمیتوانند خالی باشند', validationModelMapDuplicate: 'مدل درخواست {0} تکراری است', modelGroup: 'گروه مدل', + modelCapability: 'قابلیت مدل', + textGeneration: 'تولید متن', + imageGeneration: 'تولید تصویر', + textModels: 'مدلهای متنی', + textToImageModels: 'مدلهای متن به تصویر', + unauthorized: 'بدون مجوز', + imageCount: 'تعداد تصاویر', availableModels: 'مدلهای موجود', modelGroupModels: 'مدلهای درخواست', modelGroupModelsPlaceholder: 'نامهای مدل درخواست را انتخاب یا وارد کنید', modelGroupModelsHelper: - 'فقط مدلهای upstream واقعی را وارد کنید؛ مسیریابی هوشمند مدلها را بهترتیب امتحان میکند و بین مدلها توازن بار انجام نمیدهد.', + 'نام مدلهای درخواستی را که کلاینت برای فراخوانی دروازه AI استفاده میکند وارد کنید؛ مسیریابی هوشمند مدلها را بهترتیب امتحان میکند و بین مدلها توازن بار انجام نمیدهد.', modelGroupGuide: - 'گروههای مدل فقط شامل مدلهای upstream واقعی هستند. auto نام مدل مجازی است که کلاینت هنگام درخواست به دروازه AI استفاده میکند.', + 'گروههای مدل شامل نام مدلهای درخواستی ارائهشده توسط دروازه AI هستند. مدلهای upstream را در نگاشت مدل استخر حسابها تنظیم کنید. auto نام مدل مجازی است و نباید به گروه مدل اضافه شود.', modelGroupModelCount: '{0} مدل', contentCompliance: 'انطباق محتوا', contentAction: 'اقدام', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 7ee9a0f2f..35e0c0c43 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -736,6 +736,7 @@ const message = { manualModelConfiguration: '手動設定', discoverModelsHelper: 'プロバイダーの /v1/models API からモデルを取得し、すべてモデルプールに追加します', discoverModelsFailedFallback: 'モデルを取得できませんでした。手動でモデルを設定してください。', + discoverModelsFailedUseDefaults: 'モデルを取得できませんでした。デフォルトモデルを使用しています。', accountModelsDuplicate: 'カタログに重複したモデルがあります', modelPool: 'モデルプール', fallbackModels: 'フォールバックモデル', @@ -890,7 +891,12 @@ const message = { 'ユーザー名またはパスワードを保存すると、変更を反映するためにコンテナが自動で再作成されます。', channelDeleteConfirm: '{0} チャンネルの設定を削除しますか?', apiTypeBaseURLHelper: 'このタイプは最終的に {0} にリクエストします。推奨 Base URL: {1}', + customImageURLHelper: + 'Custom アカウントには画像生成 API の完全な URL を入力してください。パスは自動追加されません。例: {0}', apiTypeBaseURLMismatch: '現在の Base URL は {0} のパスに見えますが、{1} が選択されています。推奨 URL: {2}', + validateAvailability: 'アカウントの可用性を検証', + validateAvailabilityHelper: '保存前に最小メッセージを送信して API を検証します', + validateImageAvailabilityHelper: '保存前に最小画像を1枚生成するため、料金が発生する場合があります', }, model: { model: 'モデル', @@ -950,6 +956,7 @@ const message = { }, aiProxy: { title: 'AI ゲートウェイ', + apiReference: 'API リファレンス', notInstalled: '未インストール', usage: '使用量', accountPool: 'アカウントプール', @@ -1073,13 +1080,20 @@ const message = { validationModelMapEmpty: 'モデルマッピングのモデル名は空にできません', validationModelMapDuplicate: 'リクエストモデル {0} が重複しています', modelGroup: 'モデルグループ', + modelCapability: 'モデル機能', + textGeneration: 'テキスト生成', + imageGeneration: '画像生成', + textModels: 'テキストモデル', + textToImageModels: 'テキスト画像生成モデル', + unauthorized: '未承認', + imageCount: '画像数', availableModels: '利用可能なモデル', modelGroupModels: 'リクエストモデル', modelGroupModelsPlaceholder: 'リクエストモデル名を選択または入力してください', modelGroupModelsHelper: - '実際の上流モデルのみ入力してください。スマートルーティングは順番にモデルを試し、モデル間の負荷分散は行いません。', + 'クライアントが AI ゲートウェイを呼び出す際に使用するリクエストモデル名を入力してください。スマートルーティングは順番にモデルを試し、モデル間の負荷分散は行いません。', modelGroupGuide: - 'モデルグループには実際の上流モデルだけを設定します。auto はクライアントが AI ゲートウェイにリクエストする時の仮想モデル名です。', + 'モデルグループには AI ゲートウェイが公開するリクエストモデル名を設定します。上流モデルはアカウントプールのモデルマッピングで設定してください。auto は仮想モデル名のため、モデルグループには追加しません。', modelGroupModelCount: '{0} モデル', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index d070f62a3..dd0372c95 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -727,6 +727,7 @@ const message = { manualModelConfiguration: '수동 설정', discoverModelsHelper: '공급자의 /v1/models API에서 모델을 가져와 모두 모델 풀에 추가합니다.', discoverModelsFailedFallback: '모델을 가져오지 못했습니다. 모델을 직접 설정하세요.', + discoverModelsFailedUseDefaults: '모델을 가져오지 못했습니다. 기본 모델을 사용합니다.', accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다', modelPool: '모델 풀', fallbackModels: '폴백 모델', @@ -875,7 +876,12 @@ const message = { '사용자 이름 또는 비밀번호를 저장하면 변경 사항 적용을 위해 컨테이너가 자동으로 재생성됩니다.', channelDeleteConfirm: '{0} 채널 구성을 삭제하시겠습니까?', apiTypeBaseURLHelper: '이 유형은 최종적으로 {0}에 요청합니다. 권장 Base URL: {1}', + customImageURLHelper: + 'Custom 계정에는 전체 이미지 생성 엔드포인트 URL을 입력하세요. 경로는 자동으로 추가되지 않습니다. 예: {0}', apiTypeBaseURLMismatch: '현재 Base URL은 {0} 경로처럼 보이지만 {1}이 선택되었습니다. 권장 URL: {2}', + validateAvailability: '계정 가용성 확인', + validateAvailabilityHelper: '저장 전에 최소 메시지를 보내 API를 확인합니다', + validateImageAvailabilityHelper: '저장 전에 최소 이미지 한 장을 생성하며 비용이 발생할 수 있습니다', }, model: { model: '모델', @@ -934,6 +940,7 @@ const message = { }, aiProxy: { title: 'AI 게이트웨이', + apiReference: 'API 안내', notInstalled: '설치되지 않음', usage: '사용량', accountPool: '계정 풀', @@ -1057,13 +1064,20 @@ const message = { validationModelMapEmpty: '모델 매핑의 모델 이름은 비워 둘 수 없습니다', validationModelMapDuplicate: '요청 모델 {0}이(가) 중복되었습니다', modelGroup: '모델 그룹', + modelCapability: '모델 기능', + textGeneration: '텍스트 생성', + imageGeneration: '이미지 생성', + textModels: '텍스트 모델', + textToImageModels: '텍스트-이미지 모델', + unauthorized: '권한 없음', + imageCount: '이미지 수', availableModels: '사용 가능한 모델', modelGroupModels: '요청 모델', modelGroupModelsPlaceholder: '요청 모델 이름을 선택하거나 입력하세요', modelGroupModelsHelper: - '실제 업스트림 모델만 입력하세요. 스마트 라우팅은 순서대로 모델을 우선 선택하며 모델 간 부하 분산은 하지 않습니다.', + '클라이언트가 AI 게이트웨이를 호출할 때 사용하는 요청 모델 이름을 입력하세요. 스마트 라우팅은 순서대로 모델을 우선 선택하며 모델 간 부하 분산은 하지 않습니다.', modelGroupGuide: - '모델 그룹에는 실제 업스트림 모델만 설정합니다. auto는 클라이언트가 AI 게이트웨이에 요청할 때 사용하는 가상 모델 이름입니다.', + '모델 그룹에는 AI 게이트웨이가 노출하는 요청 모델 이름을 설정합니다. 업스트림 모델은 계정 풀의 모델 매핑에서 설정하세요. auto는 가상 모델 이름이므로 모델 그룹에 추가하지 않습니다.', modelGroupModelCount: '{0}개 모델', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 463f94340..8f68180e4 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -736,6 +736,7 @@ const message = { manualModelConfiguration: 'ຕັ້ງຄ່າດ້ວຍຕົນເອງ', discoverModelsHelper: 'ດຶງໂມເດວຈາກ API /v1/models ຂອງຜູ້ໃຫ້ບໍລິການ ແລະ ເພີ່ມທັງໝົດເຂົ້າໃນຄັງໂມເດວ', discoverModelsFailedFallback: 'ດຶງລາຍການໂມເດວບໍ່ສຳເລັດ. ກະລຸນາຕັ້ງຄ່າໂມເດວດ້ວຍຕົນເອງ.', + discoverModelsFailedUseDefaults: 'ດຶງລາຍການໂມເດວບໍ່ສຳເລັດ. ກຳລັງໃຊ້ໂມເດວເລີ່ມຕົ້ນ.', accountModelsDuplicate: 'ພົບໂມເດວທີ່ຊ້ຳກັນໃນລາຍການ', modelPool: 'ກຸ່ມໂມເດວ (Model Pool)', fallbackModels: 'ໂມເດວສຳຮອງ (Fallback)', @@ -887,7 +888,12 @@ const message = { channelDeleteConfirm: 'ລຶບການຕັ້ງຄ່າຊ່ອງທາງ {0}?', customProviderHelper: 'ຜູ້ໃຫ້ບໍລິການແບບກຳນົດເອງຈະບໍ່ກວດສອບວ່າບັນຊີສາມາດໃຊ້ງານໄດ້ຫຼືບໍ່.', apiTypeBaseURLHelper: 'ປະເພດນີ້ຈະສົ່ງການຮ້ອງຂໍໄປທີ່ {0}. Base URL ທີ່ແນະນຳ: {1}', + customImageURLHelper: + 'ສຳລັບບັນຊີ Custom ໃຫ້ປ້ອນ URL ຂອງ endpoint ສ້າງຮູບທີ່ຄົບຖ້ວນ. ລະບົບຈະບໍ່ເພີ່ມ path ອັດຕະໂນມັດ. ຕົວຢ່າງ: {0}', apiTypeBaseURLMismatch: 'Base URL ປັດຈຸບັນເບິ່ງຄືວ່າເປັນເສັ້ນທາງ {0}, ແຕ່ໄດ້ເລືອກ {1}. URL ທີ່ແນະນຳ: {2}', + validateAvailability: 'ກວດສອບການພ້ອມໃຊ້ງານຂອງບັນຊີ', + validateAvailabilityHelper: 'ສົ່ງຂໍ້ຄວາມຂະໜາດນ້ອຍກ່ອນບັນທຶກເພື່ອກວດສອບ API', + validateImageAvailabilityHelper: 'ສ້າງຮູບຂະໜາດນ້ອຍໜຶ່ງຮູບກ່ອນບັນທຶກ ແລະ ອາດມີຄ່າໃຊ້ຈ່າຍ', }, model: { model: 'ໂມເດວ', @@ -946,6 +952,7 @@ const message = { }, aiProxy: { title: 'AI Gateway', + apiReference: 'ຄູ່ມື API', notInstalled: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ', usage: 'ການໃຊ້ງານ', accountPool: 'ກຸ່ມບັນຊີ', @@ -1068,13 +1075,20 @@ const message = { validateAvailability: 'ກວດສອບການພ້ອມໃຊ້ງານຂອງບັນຊີ', validateAvailabilityHelper: 'ສົ່ງຂໍ້ຄວາມຂະໜາດນ້ອຍກ່ອນບັນທຶກເພື່ອຢືນຢັນການໃຊ້ງານ API', modelGroup: 'ກຸ່ມໂມເດວ', + modelCapability: 'ຄວາມສາມາດຂອງໂມເດວ', + textGeneration: 'ການສ້າງຂໍ້ຄວາມ', + imageGeneration: 'ການສ້າງຮູບພາບ', + textModels: 'ໂມເດວຂໍ້ຄວາມ', + textToImageModels: 'ໂມເດວສ້າງຮູບຈາກຂໍ້ຄວາມ', + unauthorized: 'ບໍ່ມີສິດ', + imageCount: 'ຈຳນວນຮູບ', availableModels: 'ໂມເດວທີ່ມີໃຫ້ໃຊ້', modelGroupModels: 'ໂມເດວທີ່ຮ້ອງຂໍ', modelGroupModelsPlaceholder: 'ເລືອກ ຫຼື ປ້ອນຊື່ໂມເດວທີ່ຮ້ອງຂໍ', modelGroupModelsHelper: - 'ປ້ອນສະເພາະໂມເດວຕົ້ນທາງທີ່ມີຢູ່ຈິງ; Smart Route ຈະລອງໂມເດວຕາມລຳດັບ ແລະ ບໍ່ເຮັດ Load Balance ລະຫວ່າງໂມເດວ.', + 'ປ້ອນຊື່ໂມເດວຄຳຂໍທີ່ Client ໃຊ້ເອີ້ນ AI Gateway; Smart Route ຈະລອງໂມເດວຕາມລຳດັບ ແລະ ບໍ່ເຮັດ Load Balance ລະຫວ່າງໂມເດວ.', modelGroupGuide: - 'ກຸ່ມໂມເດວຈະມີສະເພາະໂມເດວຕົ້ນທາງທີ່ມີຢູ່ຈິງ. auto ແມ່ນຊື່ໂມເດວສະເໝືອນທີ່ Client ໃຊ້ເມື່ອຮ້ອງຂໍ AI Gateway.', + 'ກຸ່ມໂມເດວຈະມີຊື່ໂມເດວຄຳຂໍທີ່ AI Gateway ເປີດໃຫ້ໃຊ້. ຕັ້ງຄ່າໂມເດວຕົ້ນທາງໃນການຈັບຄູ່ໂມເດວຂອງກຸ່ມບັນຊີ. auto ແມ່ນຊື່ໂມເດວສະເໝືອນ ແລະ ບໍ່ຕ້ອງເພີ່ມໃນກຸ່ມໂມເດວ.', modelGroupModelCount: '{0} ໂມເດວ', contentCompliance: 'ການປະຕິບັດຕາມເນື້ອຫາ', contentAction: 'ການດຳເນີນການ', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index b57447e80..319e7fbce 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -744,6 +744,7 @@ const message = { discoverModelsHelper: 'Dapatkan model daripada API /v1/models penyedia dan tambahkan semuanya ke katalog model', discoverModelsFailedFallback: 'Gagal mendapatkan model. Konfigurasikan model secara manual.', + discoverModelsFailedUseDefaults: 'Gagal mendapatkan model. Model lalai sedang digunakan.', accountModelsDuplicate: 'Terdapat model pendua dalam katalog', modelPool: 'Katalog model', fallbackModels: 'Model fallback', @@ -898,8 +899,13 @@ const message = { 'Menyimpan nama pengguna atau kata laluan akan membina semula bekas secara automatik supaya perubahan berkuat kuasa.', channelDeleteConfirm: 'Padam konfigurasi saluran {0}?', apiTypeBaseURLHelper: 'Jenis ini menghantar permintaan ke {0}. Base URL yang disyorkan: {1}', + customImageURLHelper: + 'Masukkan URL titik akhir penjanaan imej yang lengkap untuk akaun Custom. Laluan tidak ditambah secara automatik. Contoh: {0}', apiTypeBaseURLMismatch: 'Base URL semasa kelihatan seperti laluan {0}, tetapi {1} dipilih. URL yang disyorkan: {2}', + validateAvailability: 'Sahkan ketersediaan akaun', + validateAvailabilityHelper: 'Hantar mesej minimum sebelum menyimpan untuk mengesahkan API', + validateImageAvailabilityHelper: 'Menjana satu imej minimum sebelum menyimpan dan mungkin dikenakan caj', }, model: { model: 'Model', @@ -960,6 +966,7 @@ const message = { }, aiProxy: { title: 'Gateway AI', + apiReference: 'Rujukan API', notInstalled: 'Belum dipasang', usage: 'Penggunaan', accountPool: 'Kolam Akaun', @@ -1083,13 +1090,20 @@ const message = { validationModelMapEmpty: 'Nama model dalam pemetaan model tidak boleh kosong', validationModelMapDuplicate: 'Model permintaan {0} berulang', modelGroup: 'Kumpulan Model', + modelCapability: 'Keupayaan Model', + textGeneration: 'Penjanaan Teks', + imageGeneration: 'Penjanaan Imej', + textModels: 'Model Teks', + textToImageModels: 'Model Teks-ke-Imej', + unauthorized: 'Tidak dibenarkan', + imageCount: 'Bilangan Imej', availableModels: 'Model Tersedia', modelGroupModels: 'Model Permintaan', modelGroupModelsPlaceholder: 'Pilih atau masukkan nama model permintaan', modelGroupModelsHelper: - 'Masukkan model upstream sebenar sahaja; penghalaan pintar mencuba model mengikut susunan dan tidak mengimbang beban antara model.', + 'Masukkan nama model permintaan yang digunakan klien untuk memanggil AI Gateway; penghalaan pintar mencuba model mengikut susunan dan tidak mengimbang beban antara model.', modelGroupGuide: - 'Kumpulan model hanya mengandungi model upstream sebenar. auto ialah nama model maya yang digunakan klien semasa meminta AI Gateway.', + 'Kumpulan model mengandungi nama model permintaan yang didedahkan oleh AI Gateway. Konfigurasikan model upstream dalam pemetaan model kumpulan akaun. auto ialah nama model maya dan tidak perlu ditambah pada kumpulan model.', modelGroupModelCount: '{0} model', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 31ab59a24..8a837cdcd 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -738,6 +738,7 @@ const message = { discoverModelsHelper: 'Obtenha os modelos pela API /v1/models do provedor e adicione todos ao catálogo de modelos', discoverModelsFailedFallback: 'Não foi possível obter os modelos. Configure um modelo manualmente.', + discoverModelsFailedUseDefaults: 'Não foi possível obter os modelos. Os modelos padrão estão em uso.', accountModelsDuplicate: 'Há modelos duplicados no catálogo', modelPool: 'Catálogo de modelos', fallbackModels: 'Modelos de fallback', @@ -894,8 +895,13 @@ const message = { 'Ao salvar o usuário ou a senha, o contêiner será reconstruído automaticamente para que as alterações entrem em vigor.', channelDeleteConfirm: 'Excluir a configuração do canal {0}?', apiTypeBaseURLHelper: 'Este tipo envia requisições para {0}. Base URL recomendada: {1}', + customImageURLHelper: + 'Informe a URL completa do endpoint de geração de imagens para contas Custom. O caminho não é adicionado automaticamente. Exemplo: {0}', apiTypeBaseURLMismatch: 'A Base URL atual parece um caminho {0}, mas {1} está selecionado. URL recomendada: {2}', + validateAvailability: 'Validar disponibilidade da conta', + validateAvailabilityHelper: 'Envia uma mensagem mínima antes de salvar para validar a API', + validateImageAvailabilityHelper: 'Gera uma imagem mínima antes de salvar e pode gerar custos', }, model: { model: 'Modelo', @@ -956,6 +962,7 @@ const message = { }, aiProxy: { title: 'Gateway de IA', + apiReference: 'Referência da API', notInstalled: 'Não instalado', usage: 'Uso', accountPool: 'Pool de contas', @@ -1079,13 +1086,20 @@ const message = { validationModelMapEmpty: 'Os nomes no mapeamento de modelos não podem ficar vazios', validationModelMapDuplicate: 'O modelo solicitado {0} está duplicado', modelGroup: 'Grupo de modelos', + modelCapability: 'Capacidade do modelo', + textGeneration: 'Geração de texto', + imageGeneration: 'Geração de imagens', + textModels: 'Modelos de texto', + textToImageModels: 'Modelos de texto para imagem', + unauthorized: 'Não autorizado', + imageCount: 'Quantidade de imagens', availableModels: 'Modelos disponíveis', modelGroupModels: 'Modelos solicitados', modelGroupModelsPlaceholder: 'Selecione ou informe nomes de modelos solicitados', modelGroupModelsHelper: - 'Informe apenas modelos upstream reais; o roteamento inteligente tenta os modelos em ordem e não faz balanceamento entre modelos.', + 'Informe os nomes de modelo usados pelos clientes ao chamar o gateway de IA; o roteamento inteligente tenta os modelos em ordem e não faz balanceamento entre modelos.', modelGroupGuide: - 'Grupos de modelos contêm apenas modelos upstream reais. auto é um nome de modelo virtual usado pelos clientes ao chamar o gateway de IA.', + 'Grupos de modelos contêm os nomes de modelo expostos pelo gateway de IA. Configure os modelos upstream no mapeamento de modelos do pool de contas. auto é um nome de modelo virtual e não deve ser adicionado ao grupo.', modelGroupModelCount: '{0} modelos', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 17566956c..32868e30b 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -734,6 +734,7 @@ const message = { manualModelConfiguration: 'Настроить вручную', discoverModelsHelper: 'Получить модели через API /v1/models поставщика и добавить их все в каталог моделей', discoverModelsFailedFallback: 'Не удалось получить модели. Настройте модель вручную.', + discoverModelsFailedUseDefaults: 'Не удалось получить модели. Используются модели по умолчанию.', accountModelsDuplicate: 'В каталоге обнаружены повторяющиеся модели', modelPool: 'Каталог моделей', fallbackModels: 'Резервные модели', @@ -888,7 +889,13 @@ const message = { 'После сохранения имени пользователя или пароля контейнер будет автоматически пересобран, чтобы изменения вступили в силу.', channelDeleteConfirm: 'Удалить конфигурацию канала {0}?', apiTypeBaseURLHelper: 'Этот тип отправляет запросы в {0}. Рекомендуемый Base URL: {1}', + customImageURLHelper: + 'Для аккаунта Custom укажите полный URL интерфейса генерации изображений. Путь не добавляется автоматически. Пример: {0}', apiTypeBaseURLMismatch: 'Текущий Base URL похож на путь {0}, но выбран {1}. Рекомендуемый URL: {2}', + validateAvailability: 'Проверять доступность аккаунта', + validateAvailabilityHelper: 'Перед сохранением отправляется минимальное сообщение для проверки API', + validateImageAvailabilityHelper: + 'Перед сохранением создаётся одно минимальное изображение; возможна оплата', }, model: { model: 'Модель', @@ -948,6 +955,7 @@ const message = { }, aiProxy: { title: 'AI-шлюз', + apiReference: 'Справочник API', notInstalled: 'Не установлен', usage: 'Использование', accountPool: 'Пул аккаунтов', @@ -1071,13 +1079,20 @@ const message = { validationModelMapEmpty: 'Имена моделей в сопоставлении не могут быть пустыми', validationModelMapDuplicate: 'Запрошенная модель {0} дублируется', modelGroup: 'Группа моделей', + modelCapability: 'Возможность модели', + textGeneration: 'Генерация текста', + imageGeneration: 'Генерация изображений', + textModels: 'Текстовые модели', + textToImageModels: 'Модели генерации изображений по тексту', + unauthorized: 'Нет доступа', + imageCount: 'Количество изображений', availableModels: 'Доступные модели', modelGroupModels: 'Запрошенные модели', modelGroupModelsPlaceholder: 'Выберите или введите имена запрошенных моделей', modelGroupModelsHelper: - 'Указывайте только реальные upstream-модели; умная маршрутизация пробует модели по порядку и не балансирует нагрузку между моделями.', + 'Указывайте имена моделей, которые клиенты используют при обращении к AI-шлюзу; умная маршрутизация пробует модели по порядку и не балансирует нагрузку между моделями.', modelGroupGuide: - 'Группы моделей содержат только реальные upstream-модели. auto — виртуальное имя модели, которое клиент использует при запросе к AI-шлюзу.', + 'Группы моделей содержат имена моделей, доступные клиентам AI-шлюза. Upstream-модели настраиваются в сопоставлении моделей пула аккаунтов. auto — виртуальное имя и не добавляется в группу моделей.', modelGroupModelCount: '{0} моделей', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 74b6123b4..e7a6a3f87 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -740,6 +740,7 @@ const message = { discoverModelsHelper: 'Sağlayıcının /v1/models API’sinden modelleri alın ve tümünü model kataloğuna ekleyin', discoverModelsFailedFallback: 'Modeller alınamadı. Bir modeli elle yapılandırın.', + discoverModelsFailedUseDefaults: 'Modeller alınamadı. Varsayılan modeller kullanılıyor.', accountModelsDuplicate: 'Katalogda yinelenen modeller bulundu', modelPool: 'Model kataloğu', fallbackModels: 'Yedek modeller', @@ -897,8 +898,13 @@ const message = { 'Kullanıcı adı veya parola kaydedildiğinde değişikliklerin etkili olması için konteyner otomatik olarak yeniden oluşturulur.', channelDeleteConfirm: '{0} kanal yapılandırması silinsin mi?', apiTypeBaseURLHelper: 'Bu tür istekleri {0} yoluna gönderir. Önerilen Base URL: {1}', + customImageURLHelper: + 'Custom hesaplar için tam görsel oluşturma uç noktası URL’sini girin. Yol otomatik eklenmez. Örnek: {0}', apiTypeBaseURLMismatch: 'Geçerli Base URL bir {0} yolu gibi görünüyor, ancak {1} seçildi. Önerilen URL: {2}', + validateAvailability: 'Hesap kullanılabilirliğini doğrula', + validateAvailabilityHelper: 'Kaydetmeden önce API’yi doğrulamak için en küçük iletiyi gönderir', + validateImageAvailabilityHelper: 'Kaydetmeden önce bir küçük görsel üretir ve ücret doğurabilir', }, model: { model: 'Model', @@ -959,6 +965,7 @@ const message = { }, aiProxy: { title: 'AI Ağ Geçidi', + apiReference: 'API Referansı', notInstalled: 'Kurulu değil', usage: 'Kullanım', accountPool: 'Hesap Havuzu', @@ -1082,13 +1089,20 @@ const message = { validationModelMapEmpty: 'Model eşlemesindeki model adları boş olamaz', validationModelMapDuplicate: 'İstek modeli {0} yineleniyor', modelGroup: 'Model Grubu', + modelCapability: 'Model Yeteneği', + textGeneration: 'Metin Üretimi', + imageGeneration: 'Görsel Üretimi', + textModels: 'Metin Modelleri', + textToImageModels: 'Metinden Görsele Modeller', + unauthorized: 'Yetkisiz', + imageCount: 'Görsel Sayısı', availableModels: 'Kullanılabilir Modeller', modelGroupModels: 'İstek Modelleri', modelGroupModelsPlaceholder: 'İstek modeli adlarını seçin veya girin', modelGroupModelsHelper: - 'Yalnızca gerçek üst modelleri girin; akıllı yönlendirme modelleri sırayla dener ve modeller arasında yük dengelemez.', + 'İstemcilerin AI Ağ Geçidini çağırırken kullandığı istek modeli adlarını girin; akıllı yönlendirme modelleri sırayla dener ve modeller arasında yük dengelemez.', modelGroupGuide: - 'Model grupları yalnızca gerçek üst modelleri içerir. auto, istemcilerin AI Ağ Geçidi isteğinde kullandığı sanal model adıdır.', + 'Model grupları AI Ağ Geçidinin sunduğu istek modeli adlarını içerir. Üst modelleri hesap havuzundaki model eşlemesinde yapılandırın. auto sanal bir model adıdır ve model grubuna eklenmez.', modelGroupModelCount: '{0} model', contentCompliance: 'Content Compliance', contentAction: 'Action', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index f1f2769ae..446e3c0fb 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -703,6 +703,7 @@ const message = { manualModelConfiguration: '手動配置', discoverModelsHelper: '從供應商的 /v1/models 介面取得模型,取得後將全部加入模型池', discoverModelsFailedFallback: '取得模型失敗,請手動配置模型', + discoverModelsFailedUseDefaults: '取得模型失敗,已使用預設模型', accountModelsDuplicate: '模型池中存在重複模型,請檢查後重試', modelPool: '模型池', fallbackModels: '備用模型', @@ -839,7 +840,11 @@ const message = { dashboardAuthAutoRebuildHelper: '保存使用者名稱或密碼後將自動重建容器以使設定生效。', channelDeleteConfirm: '確認刪除 {0} 頻道設定?', apiTypeBaseURLHelper: '目前類型最終請求 {0},Base URL 建議填寫 {1}', + customImageURLHelper: 'Custom 圖片帳號請填寫完整的圖片產生介面 URL,系統不會自動補全路徑,例如:{0}', apiTypeBaseURLMismatch: '目前 Base URL 看起來是 {0} 路徑,和已選擇的 {1} 不一致,建議改為 {2}', + validateAvailability: '驗證帳號可用性', + validateAvailabilityHelper: '儲存前傳送最小訊息驗證介面可用性', + validateImageAvailabilityHelper: '儲存前會實際產生一張最小圖片,可能產生費用', }, model: { model: '模型', @@ -896,6 +901,7 @@ const message = { }, aiProxy: { title: 'AI 閘道', + apiReference: '介面說明', notInstalled: '未安裝', usage: '用量統計', accountPool: '帳號池', @@ -1015,12 +1021,20 @@ const message = { validationModelMapEmpty: '模型映射的模型名稱不能為空', validationModelMapDuplicate: '請求模型 {0} 重複', modelGroup: '模型組', + modelCapability: '模型能力', + textGeneration: '文字生成', + imageGeneration: '圖片生成', + textModels: '文字模型', + textToImageModels: '文生圖模型', + unauthorized: '未授權', + imageCount: '圖片數量', availableModels: '可用模型', modelGroupModels: '請求模型', modelGroupModelsPlaceholder: '請選擇或輸入請求模型名稱', - modelGroupModelsHelper: '只填寫真實上游模型;智慧路由會按順序優先選擇靠前模型,不做模型間負載均衡。', + modelGroupModelsHelper: + '填寫用戶端請求 AI 網關時使用的請求模型名稱;智慧路由會按順序優先選擇靠前模型,不做模型間負載均衡。', modelGroupGuide: - '模型組只配置真實上游模型;auto 是用戶端請求 AI 網關時使用的虛擬模型名稱,不需要放進模型組。', + '模型組配置用戶端請求 AI 網關時使用的請求模型名稱;上游模型在帳號池的模型映射中配置。auto 是虛擬模型名稱,不需要放進模型組。', modelGroupModelCount: '{0} 個模型', contentCompliance: '內容合規', contentAction: '處理動作', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 5487d1f74..909946b76 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -706,6 +706,7 @@ const message = { manualModelConfiguration: '手动配置', discoverModelsHelper: '从供应商的 /v1/models 接口获取模型,获取后将全部加入模型池', discoverModelsFailedFallback: '获取模型失败,请手动配置模型', + discoverModelsFailedUseDefaults: '获取模型失败,已使用默认模型', accountModelsDuplicate: '模型池中存在重复模型,请检查后重试', modelPool: '模型池', fallbackModels: '备用模型', @@ -838,7 +839,11 @@ const message = { dashboardAuthAutoRebuildHelper: '保存用户名密码后将自动重建容器以使配置生效。', channelDeleteConfirm: '确认删除 {0} 频道配置?', apiTypeBaseURLHelper: '当前类型最终请求 {0},Base URL 建议填写 {1}', + customImageURLHelper: 'Custom 图片账号请填写完整的图片生成接口 URL,系统不会自动补全路径,例如:{0}', apiTypeBaseURLMismatch: '当前 Base URL 看起来是 {0} 路径,和已选择的 {1} 不一致,建议改为 {2}', + validateAvailability: '验证账号可用性', + validateAvailabilityHelper: '保存前发送最小消息验证接口可用性', + validateImageAvailabilityHelper: '保存前会真实生成一张最小图片,可能产生费用', }, model: { model: '模型', @@ -895,6 +900,7 @@ const message = { }, aiProxy: { title: 'AI 网关', + apiReference: '接口说明', notInstalled: '未安装', usage: '用量统计', accountPool: '账号池', @@ -1012,12 +1018,20 @@ const message = { validationModelMapEmpty: '模型映射的模型名不能为空', validationModelMapDuplicate: '请求模型 {0} 重复', modelGroup: '模型组', + modelCapability: '模型能力', + textGeneration: '文本生成', + imageGeneration: '图片生成', + textModels: '文本模型', + textToImageModels: '文生图模型', + unauthorized: '未授权', + imageCount: '图片数量', availableModels: '可用模型', modelGroupModels: '请求模型', modelGroupModelsPlaceholder: '请选择或输入请求模型名', - modelGroupModelsHelper: '只填写真实上游模型;智能路由会按顺序优先选择靠前模型,不做模型间负载均衡。', + modelGroupModelsHelper: + '填写客户端请求 AI 网关时使用的请求模型名;智能路由会按顺序优先选择靠前模型,不做模型间负载均衡。', modelGroupGuide: - '模型组只配置真实上游模型;auto 是客户端请求 AI 网关时使用的虚拟模型名,不需要放进模型组。', + '模型组配置客户端请求 AI 网关时使用的请求模型名;上游模型在账号池的模型映射中配置。auto 是虚拟模型名,不需要放进模型组。', modelGroupModelCount: '{0} 个模型', contentCompliance: '内容合规', contentAction: '处理动作', diff --git a/frontend/src/utils/agent-provider-logo.ts b/frontend/src/utils/agent-provider-logo.ts index 263ea8e12..cb0bde4a9 100644 --- a/frontend/src/utils/agent-provider-logo.ts +++ b/frontend/src/utils/agent-provider-logo.ts @@ -9,6 +9,24 @@ export interface AgentProviderLogo { const asset = (name: string) => new URL(`../assets/images/ai-providers/${name}`, import.meta.url).href; +const aliyunLogo: AgentProviderLogo = { + src: asset('aliyun.webp'), + mark: 'QW', + background: '#ffffff', + color: '#ff6a00', + borderColor: '#dcdfe6', + source: 'https://www.alibabacloud.com/', +}; + +const volcengineLogo: AgentProviderLogo = { + src: asset('volcengine.png'), + mark: 'AR', + background: '#ffffff', + color: '#1664ff', + borderColor: '#dcdfe6', + source: 'https://www.volcengine.com/', +}; + const providerLogos: Record = { ollama: { src: asset('ollama.webp'), @@ -34,22 +52,10 @@ const providerLogos: Record = { borderColor: '#dcdfe6', source: 'https://cdn.deepseek.com/logo.png', }, - 'bailian-coding-plan': { - src: asset('aliyun.webp'), - mark: 'QW', - background: '#ffffff', - color: '#ff6a00', - borderColor: '#dcdfe6', - source: 'https://www.alibabacloud.com/', - }, - 'ark-coding-plan': { - src: asset('volcengine.png'), - mark: 'AR', - background: '#ffffff', - color: '#1664ff', - borderColor: '#dcdfe6', - source: 'https://www.volcengine.com/', - }, + bailian: aliyunLogo, + 'bailian-coding-plan': aliyunLogo, + ark: volcengineLogo, + 'ark-coding-plan': volcengineLogo, zai: { src: asset('zai.webp'), mark: 'Z', diff --git a/frontend/src/views/ai/agents/agent/add/index.vue b/frontend/src/views/ai/agents/agent/add/index.vue index 1ac4803a8..9255fcc9a 100644 --- a/frontend/src/views/ai/agents/agent/add/index.vue +++ b/frontend/src/views/ai/agents/agent/add/index.vue @@ -370,6 +370,7 @@ const loadAccounts = async () => { page: 1, pageSize: 200, provider: form.provider, + textOnly: true, name: '', }; const res = await pageAgentAccounts(req); diff --git a/frontend/src/views/ai/agents/agent/config/tabs/agents/create/index.vue b/frontend/src/views/ai/agents/agent/config/tabs/agents/create/index.vue index 3e413a69b..6ce5eaf3e 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/agents/create/index.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/agents/create/index.vue @@ -168,6 +168,7 @@ const loadAccounts = async () => { page: 1, pageSize: 200, provider: '', + textOnly: true, name: '', }); const items = res.data.items || []; diff --git a/frontend/src/views/ai/agents/agent/config/tabs/model.vue b/frontend/src/views/ai/agents/agent/config/tabs/model.vue index f4cfe4f71..32f0cac81 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/model.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/model.vue @@ -187,6 +187,7 @@ const load = async (params: AgentModelLoadParams) => { page: 1, pageSize: 200, provider: '', + textOnly: true, name: '', }), getAgentModelConfig({ agentId: params.agentId }), diff --git a/frontend/src/views/ai/agents/model/add/index.vue b/frontend/src/views/ai/agents/model/add/index.vue index bb7eeba6b..d1f1ea836 100644 --- a/frontend/src/views/ai/agents/model/add/index.vue +++ b/frontend/src/views/ai/agents/model/add/index.vue @@ -20,7 +20,11 @@ - + @@ -66,20 +70,37 @@ {{ $t('terminal.rememberPassword') }} + + + {{ $t('aiTools.agents.validateAvailability') }} + + {{ + isImageAPIType + ? $t('aiTools.agents.validateImageAvailabilityHelper') + : $t('aiTools.agents.validateAvailabilityHelper') + }} + + {{ $t('aiTools.agents.modelPool') }} - {{ $t('aiTools.agents.automaticModelDiscovery') }} + {{ + $t( + supportsModelDiscovery + ? 'aiTools.agents.automaticModelDiscovery' + : 'aiTools.agents.defaultModel', + ) + }} {{ $t('aiTools.agents.manualModelConfiguration') }} - + {{ $t('aiTools.agents.discoverModelsHelper') }} - + {{ $t('aiTools.agents.verifyModelHelper') }} - + @@ -115,6 +153,45 @@ + + + + + + + + + + + + + + + + + + + + + {{ $t('commons.button.delete') }} + + + + + + {{ $t('commons.button.add') }} + + @@ -189,6 +266,8 @@ const providers = ref>({}); const loading = ref(false); const discovering = ref(false); const discoveredModels = ref([]); +const manualModels = ref([]); +const manualVerifyIndex = ref(-1); const editModels = ref([]); const modelConfigMode = ref<'discover' | 'manual'>('discover'); const modelDiscoveryFailed = ref(false); @@ -205,6 +284,7 @@ const form = reactive({ apiKey: '', rememberApiKey: false, verifyModel: '', + validateAvailability: true, initialModel: {} as AI.AgentAccountModel, remark: '', syncAgents: false, @@ -218,22 +298,28 @@ const selectedProvider = computed(() => providers.value[form.provider]); const selectedAPIConfig = computed(() => selectedProvider.value?.apiTypes.find((item) => item.apiType === form.apiType), ); -const showModelDiscovery = computed( - () => - !form.id && - form.provider === 'custom' && - (form.apiType === 'openai-completions' || form.apiType === 'openai-responses'), -); +const isImageAPIType = computed(() => form.apiType.endsWith('-images')); +const supportsModelDiscovery = computed(() => !form.id && Boolean(selectedAPIConfig.value?.supportsModelDiscovery)); +const defaultModels = computed(() => { + if (selectedAPIConfig.value?.models.length) { + return selectedAPIConfig.value.models; + } + return isImageAPIType.value ? [] : selectedProvider.value?.models || []; +}); +const showModelDiscovery = computed(() => !form.id && (supportsModelDiscovery.value || defaultModels.value.length > 0)); const showInitialModel = computed( () => !form.id && - initialModelProviders.includes(form.provider) && - (!showModelDiscovery.value || modelConfigMode.value === 'manual'), + (initialModelProviders.includes(form.provider) || (isImageAPIType.value && defaultModels.value.length === 0)) && + !showModelDiscovery.value, ); -const verifyModelOptions = computed(() => (form.id ? editModels.value : selectedProvider.value?.models || [])); +const requiresVerifyModel = computed( + () => form.validateAvailability && !isAgentAccountVerificationSkipped(form.provider), +); +const verifyModelOptions = computed(() => (form.id ? editModels.value : defaultModels.value)); const showVerifyModelSelect = computed( () => - !isAgentAccountVerificationSkipped(form.provider) && + requiresVerifyModel.value && verifyModelOptions.value.length > 0 && (Boolean(form.id) || (!showModelDiscovery.value && !showInitialModel.value)), ); @@ -255,6 +341,26 @@ const apiTypeURLHints: Record Boolean(selectedAPIConfig.value?.editableBaseUrl)); const apiTypeBaseURLHelper = computed(() => { @@ -262,6 +368,11 @@ const apiTypeBaseURLHelper = computed(() => { if (!showAPITypeBaseURLTips.value || !hint) { return ''; } + if (form.provider === 'custom' && isImageAPIType.value) { + return i18n.global.t('aiTools.agents.customImageURLHelper', [ + `${hint.example.replace(/\/+$/, '')}${hint.requestPath}`, + ]); + } return i18n.global.t('aiTools.agents.apiTypeBaseURLHelper', [hint.requestPath, hint.example]); }); @@ -330,23 +441,74 @@ const normalizeInitialModel = () => { }; const resetInitialModel = () => { - form.initialModel = isInitialModelProvider(form.provider) ? buildInitialModel() : ({} as AI.AgentAccountModel); + form.initialModel = + isInitialModelProvider(form.provider) || isImageAPIType.value + ? buildInitialModel() + : ({} as AI.AgentAccountModel); +}; + +const resetManualModels = () => { + manualModels.value = [buildInitialModel()]; + manualVerifyIndex.value = -1; +}; + +const loadDefaultModels = () => { + discoveredModels.value = defaultModels.value.map((item) => ({ + recordId: 0, + id: item.id, + name: item.name, + })); + modelDiscoveryFailed.value = false; + form.verifyModel = ''; }; const resetModelDiscovery = () => { discovering.value = false; - discoveredModels.value = []; modelConfigMode.value = 'discover'; - modelDiscoveryFailed.value = false; - form.verifyModel = ''; + resetManualModels(); + loadDefaultModels(); }; const handleModelConfigModeChange = () => { modelDiscoveryFailed.value = false; form.verifyModel = ''; - if (modelConfigMode.value === 'manual') { - discoveredModels.value = []; +}; + +const addManualModel = () => { + manualModels.value.push(buildInitialModel()); +}; + +const removeManualModel = (index: number) => { + if (manualModels.value.length === 1) { + return; } + manualModels.value.splice(index, 1); + if (manualVerifyIndex.value === index) { + manualVerifyIndex.value = -1; + } else if (manualVerifyIndex.value > index) { + manualVerifyIndex.value -= 1; + } +}; + +const normalizeManualModels = () => { + const models = manualModels.value.map((item) => ({ + recordId: 0, + id: String(item.id || '').trim(), + name: String(item.name || '').trim(), + })); + if (models.some((item) => !item.id)) { + MsgError(i18n.global.t('aiTools.agents.accountModelsRequired')); + return null; + } + if (models.some((item) => item.id.includes(' '))) { + MsgError(i18n.global.t('setting.noSpace')); + return null; + } + if (new Set(models.map((item) => item.id)).size !== models.length) { + MsgError(i18n.global.t('aiTools.agents.accountModelsDuplicate')); + return null; + } + return models; }; const discoverModels = async () => { @@ -369,14 +531,23 @@ const discoverModels = async () => { discoveredModels.value = res.data || []; form.verifyModel = ''; if (discoveredModels.value.length === 0) { - modelConfigMode.value = 'manual'; + if (defaultModels.value.length > 0) { + loadDefaultModels(); + } else { + modelConfigMode.value = 'manual'; + } modelDiscoveryFailed.value = true; } else { modelDiscoveryFailed.value = false; } } catch (error: any) { - discoveredModels.value = []; - modelConfigMode.value = 'manual'; + if (defaultModels.value.length > 0) { + loadDefaultModels(); + modelDiscoveryFailed.value = true; + } else { + discoveredModels.value = []; + modelConfigMode.value = 'manual'; + } modelDiscoveryFailed.value = true; MsgError(String(error?.message || i18n.global.t('commons.res.commonError'))); } finally { @@ -394,12 +565,32 @@ const submit = async () => { MsgError(i18n.global.t('aiTools.agents.accountModelsRequired')); return; } - if (showModelDiscovery.value && modelConfigMode.value === 'discover' && discoveredModels.value.length === 0) { - MsgError(i18n.global.t('aiTools.agents.accountModelsRequired')); - return; + let accountModels = initialModel ? [initialModel] : []; + if (!form.id) { + if (showModelDiscovery.value && modelConfigMode.value === 'manual') { + const models = normalizeManualModels(); + if (!models) { + return; + } + accountModels = models; + } else if (showModelDiscovery.value) { + accountModels = discoveredModels.value; + } + if (accountModels.length === 0) { + MsgError(i18n.global.t('aiTools.agents.accountModelsRequired')); + return; + } } - const verifyModel = showInitialModel.value ? initialModel?.id || '' : form.verifyModel; - if (!isAgentAccountVerificationSkipped(form.provider) && !verifyModel) { + let verifyModel = ''; + if (requiresVerifyModel.value) { + verifyModel = form.verifyModel; + if (modelConfigMode.value === 'manual' && showModelDiscovery.value) { + verifyModel = accountModels[manualVerifyIndex.value]?.id || ''; + } else if (showInitialModel.value) { + verifyModel = initialModel?.id || ''; + } + } + if (requiresVerifyModel.value && !verifyModel) { MsgError(i18n.global.t('aiTools.agents.verifyModelRequired')); return; } @@ -415,6 +606,7 @@ const submit = async () => { apiType: form.apiType, authMode: form.authMode, verifyModel, + validateAvailability: form.validateAvailability, remark: form.remark, syncAgents: form.syncAgents, }); @@ -428,7 +620,8 @@ const submit = async () => { apiType: form.apiType, authMode: form.authMode, verifyModel, - models: discoveredModels.value.length ? discoveredModels.value : initialModel ? [initialModel] : [], + validateAvailability: form.validateAvailability, + models: accountModels, remark: form.remark, }); } @@ -452,6 +645,7 @@ const handleClose = () => { form.authMode = ''; form.apiKey = ''; form.rememberApiKey = false; + form.validateAvailability = true; form.verifyModel = ''; form.initialModel = {} as AI.AgentAccountModel; editModels.value = []; @@ -487,6 +681,7 @@ const openDrawer = async (params?: OpenParams) => { form.baseURL = params.baseURL || ''; form.apiKey = params.apiKey || ''; form.rememberApiKey = params.rememberApiKey || false; + form.validateAvailability = true; editModels.value = params.models || []; const provider = providers.value[form.provider]; form.apiType = provider?.apiTypes.some((item) => item.apiType === params.apiType) @@ -503,6 +698,7 @@ const openDrawer = async (params?: OpenParams) => { form.baseURL = ''; form.apiKey = ''; form.rememberApiKey = false; + form.validateAvailability = true; form.verifyModel = ''; editModels.value = []; form.apiType = 'openai-completions'; @@ -536,7 +732,6 @@ const loadProviders = async () => { }; const handleProviderChange = () => { - resetModelDiscovery(); const provider = selectedProvider.value; form.apiType = provider?.defaultApiType || ''; const config = provider?.apiTypes.find((item) => item.apiType === form.apiType); @@ -545,10 +740,10 @@ const handleProviderChange = () => { if (!form.id) { resetInitialModel(); } + resetModelDiscovery(); }; const handleAPITypeChange = () => { - resetModelDiscovery(); const config = selectedAPIConfig.value; if (!config) { return; @@ -559,12 +754,12 @@ const handleAPITypeChange = () => { form.baseURL = config.baseUrl || ''; } resetInitialModel(); + resetModelDiscovery(); }; watch([() => form.apiKey, () => form.baseURL], () => { if (showModelDiscovery.value) { - discoveredModels.value = []; - modelDiscoveryFailed.value = false; + loadDefaultModels(); } }); @@ -615,4 +810,8 @@ defineExpose({ .model-discovery .el-alert { margin-bottom: 12px; } + +.model-discovery__add { + margin-top: 12px; +} diff --git a/frontend/src/views/ai/agents/model/index.vue b/frontend/src/views/ai/agents/model/index.vue index f45952322..cdd4c58cd 100644 --- a/frontend/src/views/ai/agents/model/index.vue +++ b/frontend/src/views/ai/agents/model/index.vue @@ -7,6 +7,18 @@ + + + + + @@ -15,13 +27,12 @@ - - - {{ getAgentProviderDisplayName(row.provider, row.providerName) }} - + + + + + + @@ -59,20 +70,23 @@ - diff --git a/frontend/src/views/host/file-management/ai-search/file-ai-search-drawer.vue b/frontend/src/views/host/file-management/ai-search/file-ai-search-drawer.vue index fa37ba55e..2b96c06e2 100644 --- a/frontend/src/views/host/file-management/ai-search/file-ai-search-drawer.vue +++ b/frontend/src/views/host/file-management/ai-search/file-ai-search-drawer.vue @@ -657,7 +657,7 @@ const loadFileAiManageSettings = async () => { }; const loadFileAiAgentAccounts = async () => { - const res = await pageAgentAccounts({ page: 1, pageSize: 1000, provider: '', name: '' }); + const res = await pageAgentAccounts({ page: 1, pageSize: 1000, provider: '', textOnly: true, name: '' }); fileAiAgentAccountOptions.value = res.data?.items || []; }; diff --git a/frontend/src/views/terminal/setting/ai/index.vue b/frontend/src/views/terminal/setting/ai/index.vue index 5fed011f4..43f71bd07 100644 --- a/frontend/src/views/terminal/setting/ai/index.vue +++ b/frontend/src/views/terminal/setting/ai/index.vue @@ -145,6 +145,7 @@ const loadAgentAccounts = async () => { page: 1, pageSize: 1000, provider: '', + textOnly: true, name: '', }).then((res) => { agentAccountOptions.value = res.data?.items || [];