feat: Add support for text-to-image APIs. (#13380)

* feat: Add support for text-to-image APIs.

* feat: Add support for text-to-image APIs.
This commit is contained in:
CityFun
2026-07-28 09:42:23 +08:00
committed by GitHub
parent c04848a92a
commit e2754b447d
57 changed files with 1124 additions and 163 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: '模型映射不能為空'
ErrAIProxyModelMapEmptyExternalModel: '模型映射中存在空的請求模型'
ErrAIProxyModelMapEmptyUpstreamModel: '模型 {{ .model }} 的上游模型不能為空'
AIProxyUserFallback: '已刪除使用者 (ID: {{ .id }})'
AIProviderBailian: '阿里雲百煉'
AIProviderBailianCodingPlan: '阿里雲百煉 Coding Plan'
AIProviderArk: '火山方舟'
AIProviderArkCodingPlan: '火山方舟 Coding Plan'

View File

@@ -649,3 +649,7 @@ ErrAIProxyModelMapEmpty: "模型映射不能为空"
ErrAIProxyModelMapEmptyExternalModel: "模型映射中存在空的请求模型"
ErrAIProxyModelMapEmptyUpstreamModel: "模型 {{ .model }} 的上游模型不能为空"
AIProxyUserFallback: "已删除用户 (ID: {{ .id }})"
AIProviderBailian: "阿里云百炼"
AIProviderBailianCodingPlan: "阿里云百炼 Coding Plan"
AIProviderArk: "火山方舟"
AIProviderArkCodingPlan: "火山方舟 Coding Plan"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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: "ارسال وظایف نصب عامل"

View File

@@ -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: "エージェントインストールタスクを配信"

View File

@@ -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: "에이전트 설치 작업 배포"

View File

@@ -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: "ຈັດສົ່ງວຽກຕິດຕັ້ງເອເຈນ"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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: "Отправить задачи установки агентов"

View File

@@ -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"

View File

@@ -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: "下發智能體安裝任務"

View File

@@ -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: "下发智能体安装任务"

View File

@@ -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;
}

View File

@@ -0,0 +1,43 @@
<template>
<span class="agent-provider-label">
<ProviderLogo :provider="provider" :display-name="name" :size="size" />
<span class="agent-provider-label__name" :title="name">{{ name }}</span>
</span>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import ProviderLogo from '@/components/agent-provider-logo/index.vue';
import { getAgentProviderDisplayName } from '@/utils/agent';
const props = withDefaults(
defineProps<{
provider: string;
displayName?: string;
size?: number;
}>(),
{
displayName: '',
size: 16,
},
);
const name = computed(() => getAgentProviderDisplayName(props.provider, props.displayName));
</script>
<style scoped lang="scss">
.agent-provider-label {
display: inline-flex;
align-items: center;
max-width: 100%;
min-width: 0;
gap: 6px;
white-space: nowrap;
}
.agent-provider-label__name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<el-tag
size="small"
effect="dark"
:type="tagType"
:class="{ 'api-type-tag--responses': apiType === 'openai-responses' }"
>
{{ apiType || '-' }}
</el-tag>
</template>
<script setup lang="ts">
import { computed } from 'vue';
type TagType = 'primary' | 'success' | 'warning' | 'info';
const props = defineProps<{
apiType: string;
}>();
const tagType = computed<TagType>(() => {
if (props.apiType === 'openai-completions') {
return 'primary';
}
if (props.apiType === 'openai-responses') {
return 'info';
}
if (props.apiType === 'anthropic-messages') {
return 'warning';
}
if (props.apiType.endsWith('-images')) {
return 'success';
}
return 'info';
});
</script>
<style scoped lang="scss">
.api-type-tag--responses {
--el-tag-bg-color: #7c3aed;
--el-tag-border-color: #7c3aed;
--el-tag-text-color: #ffffff;
}
</style>

View File

@@ -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',

View File

@@ -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',

View File

@@ -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: 'اقدام',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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: 'ການດຳເນີນການ',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -740,6 +740,7 @@ const message = {
discoverModelsHelper:
'Sağlayıcının /v1/models APIsinden 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ı URLsini 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 APIyi 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',

View File

@@ -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: '處理動作',

View File

@@ -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: '处理动作',

View File

@@ -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<string, AgentProviderLogo> = {
ollama: {
src: asset('ollama.webp'),
@@ -34,22 +52,10 @@ const providerLogos: Record<string, AgentProviderLogo> = {
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',

View File

@@ -370,6 +370,7 @@ const loadAccounts = async () => {
page: 1,
pageSize: 200,
provider: form.provider,
textOnly: true,
name: '',
};
const res = await pageAgentAccounts(req);

View File

@@ -168,6 +168,7 @@ const loadAccounts = async () => {
page: 1,
pageSize: 200,
provider: '',
textOnly: true,
name: '',
});
const items = res.data.items || [];

View File

@@ -187,6 +187,7 @@ const load = async (params: AgentModelLoadParams) => {
page: 1,
pageSize: 200,
provider: '',
textOnly: true,
name: '',
}),
getAgentModelConfig({ agentId: params.agentId }),

View File

@@ -20,7 +20,11 @@
</el-select>
</el-form-item>
<el-form-item :label="'API ' + $t('commons.table.type')" prop="apiType">
<el-select v-model="form.apiType" :disabled="apiTypeOptions.length === 1" @change="handleAPITypeChange">
<el-select
v-model="form.apiType"
:disabled="form.id > 0 || apiTypeOptions.length === 1"
@change="handleAPITypeChange"
>
<el-option v-for="item in apiTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -66,20 +70,37 @@
<el-form-item>
<el-checkbox v-model="form.rememberApiKey">{{ $t('terminal.rememberPassword') }}</el-checkbox>
</el-form-item>
<el-form-item>
<el-switch v-model="form.validateAvailability" />
<span class="ml-2">{{ $t('aiTools.agents.validateAvailability') }}</span>
<span class="input-help">
{{
isImageAPIType
? $t('aiTools.agents.validateImageAvailabilityHelper')
: $t('aiTools.agents.validateAvailabilityHelper')
}}
</span>
</el-form-item>
<template v-if="showModelDiscovery">
<el-divider content-position="left">{{ $t('aiTools.agents.modelPool') }}</el-divider>
<div class="model-discovery">
<div class="model-discovery__header">
<el-radio-group v-model="modelConfigMode" size="small" @change="handleModelConfigModeChange">
<el-radio-button value="discover">
{{ $t('aiTools.agents.automaticModelDiscovery') }}
{{
$t(
supportsModelDiscovery
? 'aiTools.agents.automaticModelDiscovery'
: 'aiTools.agents.defaultModel',
)
}}
</el-radio-button>
<el-radio-button value="manual">
{{ $t('aiTools.agents.manualModelConfiguration') }}
</el-radio-button>
</el-radio-group>
<el-button
v-if="modelConfigMode === 'discover'"
v-if="supportsModelDiscovery && modelConfigMode === 'discover'"
type="primary"
plain
:loading="discovering"
@@ -88,10 +109,16 @@
{{ $t('aiTools.agents.discoverModels') }}
</el-button>
</div>
<div v-if="modelConfigMode === 'discover'" class="input-help model-discovery__help">
<div
v-if="supportsModelDiscovery && modelConfigMode === 'discover'"
class="input-help model-discovery__help"
>
{{ $t('aiTools.agents.discoverModelsHelper') }}
</div>
<div v-if="discoveredModels.length" class="input-help model-discovery__help">
<div
v-if="requiresVerifyModel && modelConfigMode === 'discover' && discoveredModels.length"
class="input-help model-discovery__help"
>
{{ $t('aiTools.agents.verifyModelHelper') }}
</div>
<el-alert
@@ -99,7 +126,13 @@
type="warning"
:closable="false"
show-icon
:title="$t('aiTools.agents.discoverModelsFailedFallback')"
:title="
$t(
defaultModels.length
? 'aiTools.agents.discoverModelsFailedUseDefaults'
: 'aiTools.agents.discoverModelsFailedFallback',
)
"
/>
<el-table
v-if="modelConfigMode === 'discover' && discoveredModels.length"
@@ -107,7 +140,12 @@
border
max-height="320"
>
<el-table-column :label="$t('aiTools.agents.verifyModel')" width="110" align="center">
<el-table-column
v-if="requiresVerifyModel"
:label="$t('aiTools.agents.verifyModel')"
width="110"
align="center"
>
<template #default="{ row }">
<el-radio v-model="form.verifyModel" :value="row.id" />
</template>
@@ -115,6 +153,45 @@
<el-table-column :label="$t('aiTools.model.model')" prop="id" min-width="220" />
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="180" />
</el-table>
<template v-if="modelConfigMode === 'manual'">
<el-table :data="manualModels" border max-height="320">
<el-table-column
v-if="requiresVerifyModel"
:label="$t('aiTools.agents.verifyModel')"
width="110"
align="center"
>
<template #default="{ row, $index }">
<el-radio v-model="manualVerifyIndex" :value="$index" :disabled="!row.id.trim()" />
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.model.model')" min-width="220">
<template #default="{ row }">
<el-input v-model="row.id" />
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.name')" min-width="180">
<template #default="{ row }">
<el-input v-model="row.name" />
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.operate')" width="90" align="center">
<template #default="{ $index }">
<el-button
type="primary"
link
:disabled="manualModels.length === 1"
@click="removeManualModel($index)"
>
{{ $t('commons.button.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="model-discovery__add" type="primary" plain @click="addManualModel">
{{ $t('commons.button.add') }}
</el-button>
</template>
</div>
</template>
<template v-if="showInitialModel">
@@ -189,6 +266,8 @@ const providers = ref<Record<string, AI.ProviderInfo>>({});
const loading = ref(false);
const discovering = ref(false);
const discoveredModels = ref<AI.AgentAccountModel[]>([]);
const manualModels = ref<AI.AgentAccountModel[]>([]);
const manualVerifyIndex = ref(-1);
const editModels = ref<AI.AgentAccountModel[]>([]);
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<string, { requestPath: string; example: string; en
example: 'http://127.0.0.1:8000',
endpointSuffixes: ['/v1/messages', '/messages'],
},
'openai-images': {
requestPath: '/images/generations',
example: 'http://127.0.0.1:8000/v1',
endpointSuffixes: ['/v1/images/generations', '/images/generations'],
},
'dashscope-images': {
requestPath: '/api/v1/services/aigc/multimodal-generation/generation',
example: 'https://dashscope.aliyuncs.com',
endpointSuffixes: ['/api/v1/services/aigc/multimodal-generation/generation'],
},
'minimax-images': {
requestPath: '/v1/image_generation',
example: 'https://api.minimaxi.com',
endpointSuffixes: ['/v1/image_generation'],
},
'openrouter-images': {
requestPath: '/api/v1/images',
example: 'https://openrouter.ai',
endpointSuffixes: ['/api/v1/images'],
},
};
const showAPITypeBaseURLTips = computed(() => 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;
}
</style>

View File

@@ -7,6 +7,18 @@
</el-button>
</template>
<template #rightToolBar>
<el-select
v-model="apiType"
clearable
filterable
class="api-type-filter"
:placeholder="'API ' + $t('commons.table.type')"
@change="handleAPITypeChange"
>
<el-option v-for="item in apiTypeOptions" :key="item" :label="item" :value="item">
<ApiTypeTag :api-type="item" />
</el-option>
</el-select>
<TableSearch v-model:searchName="searchName" @search="search" />
<TableRefresh @search="search" />
</template>
@@ -15,13 +27,12 @@
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="200" />
<el-table-column :label="$t('aiTools.agents.provider')" prop="provider" width="200">
<template #default="{ row }">
<div class="provider-cell">
<ProviderLogo
:provider="row.provider"
:display-name="getAgentProviderDisplayName(row.provider, row.providerName)"
/>
<span>{{ getAgentProviderDisplayName(row.provider, row.providerName) }}</span>
</div>
<ProviderLabel :provider="row.provider" :display-name="row.providerName" />
</template>
</el-table-column>
<el-table-column :label="'API ' + $t('commons.table.type')" prop="apiType" min-width="170">
<template #default="{ row }">
<ApiTypeTag :api-type="row.apiType" />
</template>
</el-table-column>
<el-table-column label="Base URL" prop="baseUrl" min-width="200" />
@@ -59,20 +70,23 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { deleteAgentAccount, pageAgentAccounts } from '@/api/modules/ai';
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import AddDialog from '@/views/ai/agents/model/add/index.vue';
import ModelPoolDialog from '@/views/ai/agents/model/pool/index.vue';
import ProviderLogo from '@/components/agent-provider-logo/index.vue';
import ProviderLabel from '@/components/agent-provider-label/index.vue';
import ApiTypeTag from '@/components/api-type-tag/index.vue';
import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import { dateFormat } from '@/utils/date';
import { getAgentProviderDisplayName, isAgentAccountVerificationSkipped } from '@/utils/agent';
import { isAgentAccountVerificationSkipped } from '@/utils/agent';
const items = ref<AI.AgentAccountItem[]>([]);
const addRef = ref();
const modelPoolRef = ref();
const searchName = ref('');
const apiType = ref('');
const apiTypeOptions = ref<string[]>([]);
const buttons = [
{
@@ -102,6 +116,7 @@ const search = async () => {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
provider: '',
apiType: apiType.value,
name: searchName.value || '',
};
const res = await pageAgentAccounts(req);
@@ -109,6 +124,18 @@ const search = async () => {
paginationConfig.total = res.data.total || 0;
};
const handleAPITypeChange = () => {
paginationConfig.currentPage = 1;
search();
};
const loadAPITypes = async () => {
const res = await getAgentProviders();
apiTypeOptions.value = Array.from(
new Set((res.data || []).flatMap((provider) => provider.apiTypes.map((item) => item.apiType))),
).sort();
};
const openCreate = () => {
if (addRef.value?.open) {
addRef.value.open();
@@ -177,22 +204,12 @@ const onDelete = async (row: AI.AgentAccountItem) => {
};
onMounted(async () => {
await search();
await Promise.all([search(), loadAPITypes()]);
});
</script>
<style scoped lang="scss">
.provider-cell {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 0;
white-space: nowrap;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
<style scoped>
.api-type-filter {
width: 220px;
}
</style>

View File

@@ -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 || [];
};

View File

@@ -145,6 +145,7 @@ const loadAgentAccounts = async () => {
page: 1,
pageSize: 1000,
provider: '',
textOnly: true,
name: '',
}).then((res) => {
agentAccountOptions.value = res.data?.items || [];