mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8644579eda |
+58
-40
@@ -9,6 +9,9 @@ type AgentCreateReq struct {
|
||||
BridgePort int `json:"bridgePort" validate:"required"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
AccountID uint `json:"accountId"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
@@ -28,26 +31,29 @@ type AgentCreateReq struct {
|
||||
}
|
||||
|
||||
type AgentItem struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
Container string `json:"containerName"`
|
||||
WebUIPort int `json:"webUIPort"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
Path string `json:"path"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
Upgradable bool `json:"upgradable"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
Container string `json:"containerName"`
|
||||
WebUIPort int `json:"webUIPort"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
Path string `json:"path"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
Upgradable bool `json:"upgradable"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type AgentDeleteReq struct {
|
||||
@@ -67,20 +73,28 @@ type AgentModelConfigUpdateReq struct {
|
||||
}
|
||||
|
||||
type AgentAccountCreateReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Remark string `json:"remark"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
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"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
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"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Remark string `json:"remark"`
|
||||
SyncAgents bool `json:"syncAgents"`
|
||||
}
|
||||
|
||||
type AgentAccountVerifyReq struct {
|
||||
@@ -100,15 +114,19 @@ type AgentAccountSearch struct {
|
||||
}
|
||||
|
||||
type AgentAccountInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProviderModelInfo struct {
|
||||
|
||||
+14
-11
@@ -2,15 +2,18 @@ package model
|
||||
|
||||
type Agent struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
AppInstallID uint `json:"appInstallId"`
|
||||
AccountID uint `json:"accountId"`
|
||||
ConfigPath string `json:"configPath"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ package model
|
||||
|
||||
type AgentAccount struct {
|
||||
BaseModel
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
Provider string `json:"provider"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func (AgentAccount) TableName() string {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package provider
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ID string
|
||||
@@ -21,13 +23,13 @@ var catalog = map[string]Meta{
|
||||
"ollama": {
|
||||
Key: "ollama",
|
||||
DisplayName: "Ollama",
|
||||
Sort: 1,
|
||||
Sort: 10,
|
||||
Enabled: true,
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
DisplayName: "DeepSeek",
|
||||
Sort: 2,
|
||||
Sort: 20,
|
||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||
EnvKey: "DEEPSEEK_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -37,10 +39,19 @@ var catalog = map[string]Meta{
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
|
||||
},
|
||||
},
|
||||
"custom": {
|
||||
Key: "custom",
|
||||
DisplayName: "Custom",
|
||||
Sort: 30,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "CUSTOM_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{},
|
||||
},
|
||||
"openai": {
|
||||
Key: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
Sort: 3,
|
||||
Sort: 40,
|
||||
DefaultBaseURL: "https://api.openai.com/v1",
|
||||
EnvKey: "OPENAI_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -56,7 +67,7 @@ var catalog = map[string]Meta{
|
||||
"anthropic": {
|
||||
Key: "anthropic",
|
||||
DisplayName: "Anthropic",
|
||||
Sort: 4,
|
||||
Sort: 50,
|
||||
DefaultBaseURL: "https://api.anthropic.com",
|
||||
EnvKey: "ANTHROPIC_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -71,7 +82,7 @@ var catalog = map[string]Meta{
|
||||
"gemini": {
|
||||
Key: "gemini",
|
||||
DisplayName: "Gemini",
|
||||
Sort: 5,
|
||||
Sort: 60,
|
||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
||||
EnvKey: "GEMINI_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -87,7 +98,7 @@ var catalog = map[string]Meta{
|
||||
"minimax": {
|
||||
Key: "minimax",
|
||||
DisplayName: "MiniMax (CN)",
|
||||
Sort: 6,
|
||||
Sort: 70,
|
||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||
EnvKey: "MINIMAX_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -99,7 +110,7 @@ var catalog = map[string]Meta{
|
||||
"moonshot": {
|
||||
Key: "moonshot",
|
||||
DisplayName: "Moonshot (Global)",
|
||||
Sort: 7,
|
||||
Sort: 80,
|
||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||
EnvKey: "MOONSHOT_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -112,7 +123,7 @@ var catalog = map[string]Meta{
|
||||
"kimi": {
|
||||
Key: "kimi",
|
||||
DisplayName: "Kimi (CN)",
|
||||
Sort: 8,
|
||||
Sort: 90,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -125,7 +136,7 @@ var catalog = map[string]Meta{
|
||||
"kimi-coding": {
|
||||
Key: "kimi-coding",
|
||||
DisplayName: "Kimi Coding",
|
||||
Sort: 9,
|
||||
Sort: 100,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/anthropic/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
@@ -136,7 +147,7 @@ var catalog = map[string]Meta{
|
||||
"qwen": {
|
||||
Key: "qwen",
|
||||
DisplayName: "Qwen",
|
||||
Sort: 10,
|
||||
Sort: 110,
|
||||
DefaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Enabled: false,
|
||||
|
||||
+215
-50
@@ -81,6 +81,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
if provider != "ollama" && strings.TrimSpace(account.APIKey) == "" {
|
||||
return nil, buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
apiType, maxTokens, contextWindow := resolveRuntimeParams(provider, account.APIType, account.MaxTokens, account.ContextWindow)
|
||||
if err := checkPortExist(req.WebUIPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,6 +110,9 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
params := map[string]interface{}{
|
||||
"PROVIDER": provider,
|
||||
"MODEL": req.Model,
|
||||
"API_TYPE": apiType,
|
||||
"MAX_TOKENS": maxTokens,
|
||||
"CONTEXT_WINDOW": contextWindow,
|
||||
"BASE_URL": baseURL,
|
||||
"API_KEY": account.APIKey,
|
||||
"OPENCLAW_GATEWAY_TOKEN": token,
|
||||
@@ -147,22 +151,25 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
}
|
||||
configPath := path.Join(appInstall.GetPath(), "data", "conf", "openclaw.json")
|
||||
agent := &model.Agent{
|
||||
Name: req.Name,
|
||||
Provider: provider,
|
||||
Model: req.Model,
|
||||
BaseURL: baseURL,
|
||||
APIKey: account.APIKey,
|
||||
Token: token,
|
||||
Status: appInstall.Status,
|
||||
Message: appInstall.Message,
|
||||
AppInstallID: appInstall.ID,
|
||||
AccountID: account.ID,
|
||||
ConfigPath: configPath,
|
||||
Name: req.Name,
|
||||
Provider: provider,
|
||||
Model: req.Model,
|
||||
APIType: apiType,
|
||||
MaxTokens: maxTokens,
|
||||
ContextWindow: contextWindow,
|
||||
BaseURL: baseURL,
|
||||
APIKey: account.APIKey,
|
||||
Token: token,
|
||||
Status: appInstall.Status,
|
||||
Message: appInstall.Message,
|
||||
AppInstallID: appInstall.ID,
|
||||
AccountID: account.ID,
|
||||
ConfigPath: configPath,
|
||||
}
|
||||
if err := agentRepo.Create(agent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go a.writeConfigWithRetry(appInstall, provider, req.Model, baseURL, req.APIKey, token, agent.ID)
|
||||
go a.writeConfigWithRetry(appInstall, provider, req.Model, apiType, maxTokens, contextWindow, baseURL, account.APIKey, token, agent.ID)
|
||||
|
||||
item := buildAgentItem(agent, appInstall, nil)
|
||||
return &item, nil
|
||||
@@ -287,6 +294,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if provider != "ollama" && strings.TrimSpace(account.APIKey) == "" {
|
||||
return buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
apiType, maxTokens, contextWindow := resolveRuntimeParams(provider, account.APIType, account.MaxTokens, account.ContextWindow)
|
||||
confDir := ""
|
||||
if agent.ConfigPath != "" {
|
||||
confDir = path.Dir(agent.ConfigPath)
|
||||
@@ -299,11 +307,14 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if confDir == "" {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if err := writeOpenclawConfig(confDir, provider, modelName, baseURL, account.APIKey, agent.Token); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
agent.Provider = provider
|
||||
agent.Model = modelName
|
||||
agent.APIType = apiType
|
||||
agent.MaxTokens = maxTokens
|
||||
agent.ContextWindow = contextWindow
|
||||
agent.BaseURL = baseURL
|
||||
agent.APIKey = account.APIKey
|
||||
agent.AccountID = account.ID
|
||||
@@ -338,7 +349,10 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||
return buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
baseURL := strings.TrimSpace(req.BaseURL)
|
||||
if baseURL == "" {
|
||||
if provider == "custom" && baseURL == "" {
|
||||
return buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if provider != "custom" && baseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
@@ -349,16 +363,36 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||
if exist, _ := agentAccountRepo.GetFirst(repo.WithByProvider(provider), repo.WithByName(req.Name)); exist != nil && exist.ID > 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
modelName := strings.TrimSpace(req.Model)
|
||||
apiType := normalizeAPIType(req.APIType)
|
||||
if provider == "custom" {
|
||||
if modelName == "" {
|
||||
return fmt.Errorf("model is required")
|
||||
}
|
||||
if !isSupportedAPIType(apiType) {
|
||||
return fmt.Errorf("apiType is invalid")
|
||||
}
|
||||
}
|
||||
if err := a.VerifyAccount(dto.AgentAccountVerifyReq{Provider: provider, BaseURL: baseURL, APIKey: apiKey}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, maxTokens, contextWindow := resolveRuntimeParams(provider, apiType, req.MaxTokens, req.ContextWindow)
|
||||
account := &model.AgentAccount{
|
||||
Provider: provider,
|
||||
Name: req.Name,
|
||||
APIKey: apiKey,
|
||||
BaseURL: baseURL,
|
||||
Verified: true,
|
||||
Remark: req.Remark,
|
||||
Provider: provider,
|
||||
Name: req.Name,
|
||||
APIKey: apiKey,
|
||||
BaseURL: baseURL,
|
||||
Model: "",
|
||||
APIType: apiType,
|
||||
MaxTokens: 0,
|
||||
ContextWindow: 0,
|
||||
Verified: true,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
if provider == "custom" {
|
||||
account.Model = normalizeCustomModel(modelName)
|
||||
account.MaxTokens = maxTokens
|
||||
account.ContextWindow = contextWindow
|
||||
}
|
||||
return agentAccountRepo.Create(account)
|
||||
}
|
||||
@@ -370,7 +404,10 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
}
|
||||
provider := strings.ToLower(strings.TrimSpace(account.Provider))
|
||||
baseURL := strings.TrimSpace(req.BaseURL)
|
||||
if baseURL == "" {
|
||||
if provider == "custom" && baseURL == "" {
|
||||
return buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if provider != "custom" && baseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
@@ -378,12 +415,31 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
if provider == "ollama" && baseURL == "" {
|
||||
return buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
apiType := normalizeAPIType(req.APIType)
|
||||
if provider == "custom" && strings.TrimSpace(req.Model) == "" {
|
||||
return fmt.Errorf("model is required")
|
||||
}
|
||||
if provider == "custom" && !isSupportedAPIType(apiType) {
|
||||
return fmt.Errorf("apiType is invalid")
|
||||
}
|
||||
if provider != "custom" {
|
||||
apiType = normalizeAPIType(account.APIType)
|
||||
}
|
||||
_, maxTokens, contextWindow := resolveRuntimeParams(provider, apiType, req.MaxTokens, req.ContextWindow)
|
||||
if err := a.VerifyAccount(dto.AgentAccountVerifyReq{Provider: provider, BaseURL: baseURL, APIKey: req.APIKey}); err != nil {
|
||||
return err
|
||||
}
|
||||
account.Name = req.Name
|
||||
account.APIKey = req.APIKey
|
||||
account.BaseURL = baseURL
|
||||
if provider == "custom" {
|
||||
account.Model = normalizeCustomModel(req.Model)
|
||||
}
|
||||
account.APIType = apiType
|
||||
if provider == "custom" {
|
||||
account.MaxTokens = maxTokens
|
||||
account.ContextWindow = contextWindow
|
||||
}
|
||||
account.Remark = req.Remark
|
||||
account.Verified = true
|
||||
if err := agentAccountRepo.Save(account); err != nil {
|
||||
@@ -412,15 +468,19 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
||||
items := make([]dto.AgentAccountInfo, 0, len(list))
|
||||
for _, item := range list {
|
||||
items = append(items, dto.AgentAccountInfo{
|
||||
ID: item.ID,
|
||||
Provider: item.Provider,
|
||||
ProviderName: providerDisplayName(item.Provider),
|
||||
Name: item.Name,
|
||||
APIKey: item.APIKey,
|
||||
BaseURL: item.BaseURL,
|
||||
Verified: item.Verified,
|
||||
Remark: item.Remark,
|
||||
CreatedAt: item.CreatedAt,
|
||||
ID: item.ID,
|
||||
Provider: item.Provider,
|
||||
ProviderName: providerDisplayName(item.Provider),
|
||||
Name: item.Name,
|
||||
APIKey: item.APIKey,
|
||||
BaseURL: item.BaseURL,
|
||||
Model: item.Model,
|
||||
APIType: item.APIType,
|
||||
MaxTokens: item.MaxTokens,
|
||||
ContextWindow: item.ContextWindow,
|
||||
Verified: item.Verified,
|
||||
Remark: item.Remark,
|
||||
CreatedAt: item.CreatedAt,
|
||||
})
|
||||
}
|
||||
return count, items, nil
|
||||
@@ -447,6 +507,9 @@ func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
||||
if provider == "ollama" {
|
||||
return nil
|
||||
}
|
||||
if provider == "custom" {
|
||||
return nil
|
||||
}
|
||||
return verifyProvider(provider, baseURL, apiKey)
|
||||
}
|
||||
|
||||
@@ -642,12 +705,16 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
||||
if confDir == "" {
|
||||
continue
|
||||
}
|
||||
if err := writeOpenclawConfig(confDir, account.Provider, agent.Model, baseURL, account.APIKey, agent.Token); err != nil {
|
||||
apiType, maxTokens, contextWindow := resolveRuntimeParams(account.Provider, account.APIType, account.MaxTokens, account.ContextWindow)
|
||||
if err := writeOpenclawConfig(confDir, account.Provider, agent.Model, apiType, maxTokens, contextWindow, baseURL, account.APIKey, agent.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
agent.BaseURL = baseURL
|
||||
agent.APIKey = account.APIKey
|
||||
agent.Provider = account.Provider
|
||||
agent.APIType = apiType
|
||||
agent.MaxTokens = maxTokens
|
||||
agent.ContextWindow = contextWindow
|
||||
_ = agentRepo.Save(&agent)
|
||||
}
|
||||
return nil
|
||||
@@ -713,20 +780,23 @@ func verifyMinimax(baseURL, apiKey string) error {
|
||||
|
||||
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
||||
item := dto.AgentItem{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providerDisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
BaseURL: agent.BaseURL,
|
||||
APIKey: maskKey(agent.APIKey),
|
||||
Token: agent.Token,
|
||||
Status: agent.Status,
|
||||
Message: agent.Message,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
AccountID: agent.AccountID,
|
||||
ConfigPath: agent.ConfigPath,
|
||||
CreatedAt: agent.CreatedAt,
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providerDisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
APIType: agent.APIType,
|
||||
MaxTokens: agent.MaxTokens,
|
||||
ContextWindow: agent.ContextWindow,
|
||||
BaseURL: agent.BaseURL,
|
||||
APIKey: maskKey(agent.APIKey),
|
||||
Token: agent.Token,
|
||||
Status: agent.Status,
|
||||
Message: agent.Message,
|
||||
AppInstallID: agent.AppInstallID,
|
||||
AccountID: agent.AccountID,
|
||||
ConfigPath: agent.ConfigPath,
|
||||
CreatedAt: agent.CreatedAt,
|
||||
}
|
||||
if appInstall != nil && appInstall.ID > 0 {
|
||||
item.Container = appInstall.ContainerName
|
||||
@@ -792,7 +862,7 @@ func (a AgentService) waitAndDeleteAgent(agentID uint, appInstallID uint) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provider, modelName, baseURL, apiKey, token string, agentID uint) {
|
||||
func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string, agentID uint) {
|
||||
if appInstall == nil {
|
||||
return
|
||||
}
|
||||
@@ -805,7 +875,7 @@ func (a AgentService) writeConfigWithRetry(appInstall *model.AppInstall, provide
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||
if err := writeOpenclawConfig(confDir, provider, modelName, baseURL, apiKey, token); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey, token); err != nil {
|
||||
global.LOG.Errorf("write openclaw config failed: %v", err)
|
||||
agent, errGet := agentRepo.GetFirst(repo.WithByID(agentID))
|
||||
if errGet == nil && agent != nil {
|
||||
@@ -848,7 +918,8 @@ type gatewayConfig struct {
|
||||
}
|
||||
|
||||
type gatewayControlUi struct {
|
||||
AllowInsecureAuth bool `json:"allowInsecureAuth"`
|
||||
DangerouslyDisableDeviceAuth bool `json:"dangerouslyDisableDeviceAuth"`
|
||||
DangerouslyAllowHostHeaderOriginFallback bool `json:"dangerouslyAllowHostHeaderOriginFallback"`
|
||||
}
|
||||
|
||||
type gatewayAuth struct {
|
||||
@@ -897,7 +968,7 @@ type modelCost struct {
|
||||
CacheWrite float64 `json:"cacheWrite"`
|
||||
}
|
||||
|
||||
func writeOpenclawConfig(confDir, provider, modelName, baseURL, apiKey, token string) error {
|
||||
func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey, token string) error {
|
||||
if strings.TrimSpace(confDir) == "" {
|
||||
return fmt.Errorf("config dir is required")
|
||||
}
|
||||
@@ -924,7 +995,8 @@ func writeOpenclawConfig(confDir, provider, modelName, baseURL, apiKey, token st
|
||||
Token: token,
|
||||
},
|
||||
ControlUi: gatewayControlUi{
|
||||
AllowInsecureAuth: true,
|
||||
DangerouslyDisableDeviceAuth: true,
|
||||
DangerouslyAllowHostHeaderOriginFallback: true,
|
||||
},
|
||||
},
|
||||
Agents: agentsConfig{
|
||||
@@ -1038,6 +1110,43 @@ func writeOpenclawConfig(confDir, provider, modelName, baseURL, apiKey, token st
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if provider == "custom" {
|
||||
primary := modelName
|
||||
if !strings.Contains(primary, "/") {
|
||||
primary = "custom/" + strings.TrimSpace(primary)
|
||||
}
|
||||
cfg.Agents.Defaults.Model.Primary = primary
|
||||
base := strings.TrimSpace(baseURL)
|
||||
plainKey := strings.TrimSpace(apiKey)
|
||||
if !strings.Contains(modelName, "/") {
|
||||
modelName = primary
|
||||
}
|
||||
customModelID := modelID
|
||||
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
|
||||
customModelID = parts[1]
|
||||
}
|
||||
useAPIType, useMaxTokens, useContextWindow := resolveRuntimeParams(provider, apiType, maxTokens, contextWindow)
|
||||
cfg.Models = &modelsConfig{
|
||||
Mode: "merge",
|
||||
Providers: map[string]modelProvider{
|
||||
"custom": {
|
||||
ApiKey: plainKey,
|
||||
BaseUrl: base,
|
||||
Api: useAPIType,
|
||||
Models: []modelEntry{
|
||||
{
|
||||
ID: customModelID,
|
||||
Name: customModelID,
|
||||
Reasoning: strings.Contains(strings.ToLower(customModelID), "reason") || strings.Contains(strings.ToLower(customModelID), "thinking"),
|
||||
Input: []string{"text"},
|
||||
ContextWindow: useContextWindow,
|
||||
MaxTokens: useMaxTokens,
|
||||
Cost: modelCost{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if provider == "ollama" {
|
||||
cfg.Agents.Defaults.Model.Primary = modelName
|
||||
cfg.Models = &modelsConfig{
|
||||
@@ -1275,6 +1384,62 @@ func toInt(value interface{}) int {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeCustomModel(modelName string) string {
|
||||
trim := strings.TrimSpace(modelName)
|
||||
if parts := strings.SplitN(trim, "/", 2); len(parts) == 2 {
|
||||
if strings.EqualFold(parts[0], "custom") {
|
||||
return strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeAPIType(apiType string) string {
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func isSupportedAPIType(apiType string) bool {
|
||||
switch normalizeAPIType(apiType) {
|
||||
case "openai-completions", "openai-responses":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func resolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
||||
resolvedAPI := normalizeAPIType(apiType)
|
||||
resolvedMaxTokens := maxTokens
|
||||
resolvedContextWindow := contextWindow
|
||||
if resolvedMaxTokens <= 0 {
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
resolvedMaxTokens = 8192
|
||||
case "minimax", "kimi-coding", "custom":
|
||||
resolvedMaxTokens = 8192
|
||||
default:
|
||||
resolvedMaxTokens = 8192
|
||||
}
|
||||
}
|
||||
if resolvedContextWindow <= 0 {
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
resolvedContextWindow = 128000
|
||||
case "minimax", "kimi-coding":
|
||||
resolvedContextWindow = 200000
|
||||
case "custom":
|
||||
resolvedContextWindow = 128000
|
||||
default:
|
||||
resolvedContextWindow = 256000
|
||||
}
|
||||
}
|
||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
||||
}
|
||||
|
||||
func generateToken() string {
|
||||
bytes := make([]byte, 24)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
||||
@@ -65,6 +65,7 @@ func InitAgentDB() {
|
||||
migrations.AddWebsiteAcmeAccountColumn,
|
||||
migrations.AddAgentTables,
|
||||
migrations.MigrateOpenclawAgents,
|
||||
migrations.AddAgentCustomModelFields,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -941,3 +941,19 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAgentCustomModelFields = &gormigrate.Migration{
|
||||
ID: "20260224-add-agent-custom-model-fields",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Agent{}, &model.AgentAccount{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.AgentAccount{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Agent{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,6 +243,9 @@ export namespace AI {
|
||||
bridgePort: number;
|
||||
provider: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
accountId: number;
|
||||
apiKey: string;
|
||||
baseURL: string;
|
||||
@@ -267,6 +270,9 @@ export namespace AI {
|
||||
provider: string;
|
||||
providerName: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
token: string;
|
||||
@@ -317,6 +323,10 @@ export namespace AI {
|
||||
name: string;
|
||||
apiKey: string;
|
||||
baseURL: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
@@ -325,6 +335,10 @@ export namespace AI {
|
||||
name: string;
|
||||
apiKey: string;
|
||||
baseURL: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
remark: string;
|
||||
syncAgents: boolean;
|
||||
}
|
||||
@@ -343,6 +357,10 @@ export namespace AI {
|
||||
name: string;
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
verified: boolean;
|
||||
remark: string;
|
||||
createdAt: string;
|
||||
|
||||
@@ -713,6 +713,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'For custom model accounts, model names must start with custom/.',
|
||||
customProviderHelper: 'Custom model providers do not validate whether the account is available.',
|
||||
},
|
||||
model: {
|
||||
model: 'Models',
|
||||
|
||||
@@ -709,6 +709,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'En la cuenta de modelo personalizada, el nombre del modelo debe empezar por custom/',
|
||||
customProviderHelper: 'En el proveedor de modelo personalizado no se valida si la cuenta está disponible',
|
||||
},
|
||||
model: {
|
||||
model: 'Modelo',
|
||||
|
||||
@@ -698,6 +698,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'カスタムモデルアカウントでは、モデル名は必ず custom/ で始めてください',
|
||||
customProviderHelper: 'カスタムモデルプロバイダーでは、アカウントの有効性を検証しません',
|
||||
},
|
||||
model: {
|
||||
model: 'モデル',
|
||||
|
||||
@@ -695,6 +695,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: '사용자 정의 모델 계정의 모델명은 반드시 custom/ 로 시작해야 합니다',
|
||||
customProviderHelper: '사용자 정의 모델 공급자는 계정 사용 가능 여부를 검증하지 않습니다',
|
||||
},
|
||||
model: {
|
||||
model: '모델',
|
||||
|
||||
@@ -710,6 +710,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'Akaun model tersuai, nama model mesti bermula dengan custom/',
|
||||
customProviderHelper: 'Penyedia model tersuai tidak mengesahkan sama ada akaun boleh digunakan',
|
||||
},
|
||||
model: {
|
||||
model: 'Model',
|
||||
|
||||
@@ -707,6 +707,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'Conta de modelo personalizada: o nome do modelo deve começar com custom/',
|
||||
customProviderHelper: 'Provedores de modelo personalizados não validam se a conta está disponível',
|
||||
},
|
||||
model: {
|
||||
model: 'Modelo',
|
||||
|
||||
@@ -703,6 +703,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'Для пользовательской учетной записи модели имя модели должно начинаться с custom/',
|
||||
customProviderHelper: 'Для пользовательского провайдера модели доступность учетной записи не проверяется',
|
||||
},
|
||||
model: {
|
||||
model: 'Модель',
|
||||
|
||||
@@ -717,6 +717,8 @@ const message = {
|
||||
feishuSaveSuccess: 'Saved successfully',
|
||||
pairingCodeRequired: 'Please enter pairing code',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
customModelHelper: 'Özel model hesabında model adı custom/ ile başlamalıdır',
|
||||
customProviderHelper: 'Özel model sağlayıcısında hesabın kullanılabilirliği doğrulanmaz',
|
||||
},
|
||||
model: {
|
||||
model: 'Model',
|
||||
|
||||
@@ -685,6 +685,8 @@ const message = {
|
||||
feishuSaveSuccess: '保存成功',
|
||||
pairingCodeRequired: '請輸入配對碼',
|
||||
pairingApproveSuccess: '配對成功',
|
||||
customModelHelper: '自訂模型帳號,模型需固定以 custom/ 開頭',
|
||||
customProviderHelper: '自訂模型供應商不驗證帳號是否可用',
|
||||
},
|
||||
model: {
|
||||
model: '模型',
|
||||
|
||||
@@ -687,6 +687,8 @@ const message = {
|
||||
feishuSaveSuccess: '保存成功',
|
||||
pairingCodeRequired: '请输入配对码',
|
||||
pairingApproveSuccess: '配对成功',
|
||||
customModelHelper: '自定义模型账号,模型固定以 custom/ 开头',
|
||||
customProviderHelper: '自定义模型供应商不验证账号是否可用',
|
||||
},
|
||||
model: {
|
||||
model: '模型',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import i18n from '@/lang';
|
||||
|
||||
export const getAgentProviderDisplayName = (provider: string, displayName?: string): string => {
|
||||
if (provider === 'custom' || displayName === 'Custom') {
|
||||
return i18n.global.t('container.custom');
|
||||
}
|
||||
return displayName || provider;
|
||||
};
|
||||
@@ -44,9 +44,13 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.model.model')" prop="model">
|
||||
<el-input v-if="manualModel" v-model="form.model" />
|
||||
|
||||
<el-select v-else v-model="form.model" filterable @change="handleModelChange">
|
||||
<el-option v-for="item in filteredModels" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<span class="input-help" v-if="form.provider == 'custom'">
|
||||
{{ $t('aiTools.agents.customModelHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.account')" prop="accountId">
|
||||
<el-select v-model="form.accountId" @change="handleAccountChange">
|
||||
@@ -95,6 +99,7 @@ import { createAgent, getAgentProviders, pageAgentAccounts } from '@/api/modules
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { getAppByKey, getAppDetail } from '@/api/modules/app';
|
||||
import { getRandomStr, newUUID } from '@/utils/util';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
import { App } from '@/api/interface/app';
|
||||
import AdvancedSetting from '@/components/advanced-setting/index.vue';
|
||||
import AccountAddDialog from '@/views/ai/agents/model/add/index.vue';
|
||||
@@ -120,6 +125,9 @@ const form = reactive({
|
||||
provider: 'deepseek',
|
||||
accountId: undefined as unknown as number,
|
||||
model: '',
|
||||
apiType: 'openai-completions',
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
apiKey: '',
|
||||
baseURL: '',
|
||||
token: '',
|
||||
@@ -175,7 +183,7 @@ const loadProviders = async () => {
|
||||
const data = res.data || [];
|
||||
providerOptions.value = data.map((item) => ({
|
||||
value: item.provider,
|
||||
label: item.displayName || item.provider,
|
||||
label: getAgentProviderDisplayName(item.provider, item.displayName),
|
||||
}));
|
||||
providerModels.value = data.reduce((acc, item) => {
|
||||
acc[item.provider] = item.models || [];
|
||||
@@ -246,6 +254,12 @@ const handleAccountChange = () => {
|
||||
if (selected) {
|
||||
form.baseURL = selected.baseUrl || '';
|
||||
form.apiKey = selected.apiKey || '';
|
||||
form.apiType = selected.apiType || 'openai-completions';
|
||||
form.maxTokens = selected.maxTokens || 8192;
|
||||
form.contextWindow = selected.contextWindow || 128000;
|
||||
if (selected.provider === 'custom' && selected.model && !manualModel.value) {
|
||||
form.model = `custom/${selected.model}`;
|
||||
}
|
||||
}
|
||||
setDefaultModel();
|
||||
};
|
||||
@@ -257,6 +271,13 @@ const setDefaultModel = () => {
|
||||
const models = filteredModels.value;
|
||||
if (models.length > 0 && !form.model) {
|
||||
form.model = models[0].id;
|
||||
return;
|
||||
}
|
||||
if (form.provider === 'custom') {
|
||||
const selected = accountOptions.value.find((item) => item.id === form.accountId);
|
||||
if (selected?.model && !form.model) {
|
||||
form.model = `custom/${selected.model}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -276,6 +297,9 @@ const submit = async () => {
|
||||
bridgePort: form.bridgePort,
|
||||
provider: form.provider,
|
||||
model: form.model,
|
||||
apiType: form.apiType,
|
||||
maxTokens: form.maxTokens,
|
||||
contextWindow: form.contextWindow,
|
||||
accountId: form.accountId,
|
||||
apiKey: form.apiKey,
|
||||
baseURL: form.baseURL,
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<el-select v-else v-model="form.model" filterable>
|
||||
<el-option v-for="item in modelOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<span class="input-help" v-if="provdier == 'custom'">
|
||||
{{ $t('aiTools.agents.customModelHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveModel">
|
||||
@@ -44,6 +47,7 @@ const agentId = ref(0);
|
||||
const providerModels = ref<Record<string, AI.ProviderModelInfo[]>>({});
|
||||
const accountOptions = ref<AI.AgentAccountItem[]>([]);
|
||||
const modelOptions = ref<AI.ProviderModelInfo[]>([]);
|
||||
const provdier = ref('');
|
||||
|
||||
const form = reactive({
|
||||
accountId: undefined as unknown as number,
|
||||
@@ -89,6 +93,13 @@ const handleAccountChange = () => {
|
||||
form.model = '';
|
||||
return;
|
||||
}
|
||||
provdier.value = selected.provider;
|
||||
if (selected.provider === 'custom') {
|
||||
form.manualModel = true;
|
||||
form.model = selected.model ? `custom/${selected.model}` : form.model;
|
||||
modelOptions.value = [];
|
||||
return;
|
||||
}
|
||||
setModelsByProvider(selected.provider);
|
||||
if (!form.manualModel && (!form.model || !form.model.startsWith(`${selected.provider}/`))) {
|
||||
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
|
||||
@@ -96,10 +107,14 @@ const handleAccountChange = () => {
|
||||
};
|
||||
|
||||
const handleManualModelChange = (val: unknown) => {
|
||||
const selected = accountOptions.value.find((item) => item.id === form.accountId);
|
||||
if (selected?.provider === 'custom' && !Boolean(val)) {
|
||||
form.manualModel = true;
|
||||
return;
|
||||
}
|
||||
if (Boolean(val)) {
|
||||
return;
|
||||
}
|
||||
const selected = accountOptions.value.find((item) => item.id === form.accountId);
|
||||
if (!selected) {
|
||||
form.model = '';
|
||||
return;
|
||||
@@ -126,12 +141,15 @@ const load = async (agent: AI.AgentItem) => {
|
||||
form.accountId = currentAccount.id;
|
||||
setModelsByProvider(currentAccount.provider);
|
||||
const inProviderModels = modelOptions.value.some((item) => item.id === agent.model);
|
||||
form.manualModel = !inProviderModels;
|
||||
form.manualModel = currentAccount.provider === 'custom' || !inProviderModels;
|
||||
if (agent.model && (form.manualModel || agent.model.startsWith(`${currentAccount.provider}/`))) {
|
||||
form.model = agent.model;
|
||||
} else {
|
||||
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
|
||||
}
|
||||
if (currentAccount.provider === 'custom' && currentAccount.model && !form.model) {
|
||||
form.model = `custom/${currentAccount.model}`;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.providerName || row.provider }}
|
||||
<span>{{ getAgentProviderDisplayName(row.provider, row.providerName) }}</span>
|
||||
<div>
|
||||
<span>{{ row.model }}</span>
|
||||
</div>
|
||||
@@ -115,6 +115,7 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
|
||||
const items = ref<AI.AgentItem[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -16,9 +16,25 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.apiKey')" prop="apiKey">
|
||||
<el-input v-model="form.apiKey" type="password" show-password />
|
||||
<span class="input-help">{{ $t('aiTools.agents.customProviderHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.baseUrl')" prop="baseURL">
|
||||
<el-input v-model="form.baseURL" :disabled="form.provider !== 'ollama'" />
|
||||
<el-input v-model="form.baseURL" :disabled="form.provider !== 'ollama' && form.provider !== 'custom'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.model.model')" prop="model" v-if="form.provider === 'custom'">
|
||||
<el-input v-model="form.model" placeholder="gpt-4o-mini" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="'API ' + $t('commons.table.type')" prop="apiType" v-if="form.provider === 'custom'">
|
||||
<el-select v-model="form.apiType">
|
||||
<el-option label="openai-completions" value="openai-completions" />
|
||||
<el-option label="openai-responses" value="openai-responses" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Tokens" prop="maxTokens" v-if="form.provider === 'custom'">
|
||||
<el-input-number v-model="form.maxTokens" :min="1" :max="2000000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Context Window" prop="contextWindow" v-if="form.provider === 'custom'">
|
||||
<el-input-number v-model="form.contextWindow" :min="1" :max="2000000" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.remark')" prop="remark">
|
||||
<el-input v-model="form.remark" />
|
||||
@@ -45,6 +61,7 @@ import { FormInstance } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { createAgentAccount, getAgentProviders, updateAgentAccount } from '@/api/modules/ai';
|
||||
import i18n from '@/lang';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
@@ -59,6 +76,10 @@ const form = reactive({
|
||||
provider: '',
|
||||
name: '',
|
||||
baseURL: '',
|
||||
model: '',
|
||||
apiType: 'openai-completions',
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
apiKey: '',
|
||||
remark: '',
|
||||
syncAgents: false,
|
||||
@@ -73,6 +94,8 @@ const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
apiKey: [Rules.requiredInput],
|
||||
baseURL: [Rules.requiredInput],
|
||||
model: [Rules.requiredInput],
|
||||
apiType: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
@@ -88,6 +111,10 @@ const submit = async () => {
|
||||
name: form.name,
|
||||
baseURL: form.baseURL,
|
||||
apiKey: form.apiKey,
|
||||
model: form.model,
|
||||
apiType: form.apiType,
|
||||
maxTokens: form.maxTokens,
|
||||
contextWindow: form.contextWindow,
|
||||
remark: form.remark,
|
||||
syncAgents: form.syncAgents,
|
||||
});
|
||||
@@ -97,6 +124,10 @@ const submit = async () => {
|
||||
name: form.name,
|
||||
baseURL: form.baseURL,
|
||||
apiKey: form.apiKey,
|
||||
model: form.model,
|
||||
apiType: form.apiType,
|
||||
maxTokens: form.maxTokens,
|
||||
contextWindow: form.contextWindow,
|
||||
remark: form.remark,
|
||||
});
|
||||
}
|
||||
@@ -111,6 +142,10 @@ const handleClose = () => {
|
||||
formRef.value?.resetFields();
|
||||
loading.value = false;
|
||||
form.id = 0;
|
||||
form.model = '';
|
||||
form.apiType = 'openai-completions';
|
||||
form.maxTokens = 8192;
|
||||
form.contextWindow = 128000;
|
||||
form.syncAgents = false;
|
||||
};
|
||||
|
||||
@@ -120,6 +155,10 @@ interface OpenParams {
|
||||
name?: string;
|
||||
baseURL?: string;
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
apiType?: string;
|
||||
maxTokens?: number;
|
||||
contextWindow?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
@@ -132,6 +171,10 @@ const openDrawer = async (params?: OpenParams) => {
|
||||
form.name = params.name || '';
|
||||
form.baseURL = params.baseURL || '';
|
||||
form.apiKey = params.apiKey || '';
|
||||
form.model = params.model || '';
|
||||
form.apiType = params.apiType || 'openai-completions';
|
||||
form.maxTokens = params.maxTokens || 8192;
|
||||
form.contextWindow = params.contextWindow || 128000;
|
||||
form.remark = params.remark || '';
|
||||
form.syncAgents = false;
|
||||
return;
|
||||
@@ -140,6 +183,10 @@ const openDrawer = async (params?: OpenParams) => {
|
||||
form.name = '';
|
||||
form.baseURL = '';
|
||||
form.apiKey = '';
|
||||
form.model = '';
|
||||
form.apiType = 'openai-completions';
|
||||
form.maxTokens = 8192;
|
||||
form.contextWindow = 128000;
|
||||
form.remark = '';
|
||||
form.syncAgents = false;
|
||||
if (providerOptions.value.length === 0) {
|
||||
@@ -158,7 +205,7 @@ const loadProviders = async () => {
|
||||
const data = res.data || [];
|
||||
providerOptions.value = data.map((item) => ({
|
||||
value: item.provider,
|
||||
label: item.displayName || item.provider,
|
||||
label: getAgentProviderDisplayName(item.provider, item.displayName),
|
||||
}));
|
||||
providerBaseURL.value = data.reduce((acc, item) => {
|
||||
acc[item.provider] = item.baseUrl || '';
|
||||
@@ -171,6 +218,14 @@ const loadProviders = async () => {
|
||||
};
|
||||
|
||||
const handleProviderChange = () => {
|
||||
if (form.provider === 'custom') {
|
||||
form.baseURL = '';
|
||||
form.apiType = form.apiType || 'openai-completions';
|
||||
form.maxTokens = form.maxTokens || 8192;
|
||||
form.contextWindow = form.contextWindow || 128000;
|
||||
form.model = form.model || '';
|
||||
return;
|
||||
}
|
||||
if (form.provider !== 'ollama') {
|
||||
form.baseURL = providerBaseURL.value[form.provider] || '';
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="200" />
|
||||
<el-table-column :label="$t('aiTools.agents.provider')" prop="provider" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.providerName || row.provider }}
|
||||
{{ getAgentProviderDisplayName(row.provider, row.providerName) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('aiTools.agents.baseUrl')" prop="baseUrl" min-width="200" />
|
||||
@@ -53,6 +53,7 @@ import AddDialog from '@/views/ai/agents/model/add/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
|
||||
const items = ref<AI.AgentAccountItem[]>([]);
|
||||
const addRef = ref();
|
||||
@@ -103,6 +104,10 @@ const onEdit = (row: AI.AgentAccountItem) => {
|
||||
name: row.name,
|
||||
baseURL: row.baseUrl,
|
||||
apiKey: row.apiKey,
|
||||
model: row.model,
|
||||
apiType: row.apiType,
|
||||
maxTokens: row.maxTokens,
|
||||
contextWindow: row.contextWindow,
|
||||
remark: row.remark,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user