mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat(ai-agents): Add copaw type for agent (#12017)
This commit is contained in:
@@ -6,9 +6,10 @@ type AgentCreateReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
AppVersion string `json:"appVersion" validate:"required"`
|
||||
WebUIPort int `json:"webUIPort" validate:"required"`
|
||||
BridgePort int `json:"bridgePort" validate:"required"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AgentType string `json:"agentType"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
@@ -33,6 +34,7 @@ type AgentCreateReq struct {
|
||||
type AgentItem struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AgentType string `json:"agentType"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Model string `json:"model"`
|
||||
|
||||
@@ -3,6 +3,7 @@ package model
|
||||
type Agent struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;unique"`
|
||||
AgentType string `json:"agentType" gorm:"default:openclaw"`
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
|
||||
@@ -147,7 +147,7 @@ var catalog = map[string]Meta{
|
||||
"bailian-coding-plan": {
|
||||
Key: "bailian-coding-plan",
|
||||
DisplayName: "阿里云百炼 Coding Plan",
|
||||
Sort: 105,
|
||||
Sort: 11,
|
||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Enabled: true,
|
||||
|
||||
+131
-62
@@ -63,50 +63,20 @@ const (
|
||||
)
|
||||
|
||||
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
provider := strings.ToLower(strings.TrimSpace(req.Provider))
|
||||
if !isSupportedAgentProvider(provider) {
|
||||
return nil, buserr.New("ErrAgentProviderNotSupported")
|
||||
}
|
||||
if req.AccountID == 0 {
|
||||
return nil, buserr.New("ErrAgentAccountRequired")
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !account.Verified {
|
||||
return nil, buserr.New("ErrAgentAccountNotVerified")
|
||||
}
|
||||
if account.Provider != "" && provider != "" && account.Provider != provider {
|
||||
return nil, buserr.New("ErrAgentProviderMismatch")
|
||||
}
|
||||
provider = strings.ToLower(strings.TrimSpace(account.Provider))
|
||||
baseURL := strings.TrimSpace(account.BaseURL)
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
if provider == "ollama" && baseURL == "" {
|
||||
return nil, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if provider != "ollama" && strings.TrimSpace(account.APIKey) == "" {
|
||||
return nil, buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
apiType, maxTokens, contextWindow := resolveRuntimeParams(provider, account.APIType, account.MaxTokens, account.ContextWindow)
|
||||
runtimeModel := strings.TrimSpace(req.Model)
|
||||
if provider == "custom" {
|
||||
primaryID := customPrimaryModelID(req.Model)
|
||||
if primaryID == "" {
|
||||
primaryID = normalizeCustomModel(req.Model)
|
||||
}
|
||||
runtimeModel = "custom/" + primaryID
|
||||
agentType := normalizeAgentType(req.AgentType)
|
||||
if !isSupportedAgentType(agentType) {
|
||||
return nil, fmt.Errorf("agent type is invalid")
|
||||
}
|
||||
if err := checkPortExist(req.WebUIPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkPortExist(req.BridgePort); err != nil {
|
||||
return nil, err
|
||||
if agentType == constant.AppOpenclaw {
|
||||
if req.BridgePort <= 0 {
|
||||
return nil, fmt.Errorf("bridge port is required")
|
||||
}
|
||||
if err := checkPortExist(req.BridgePort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if exist, _ := agentRepo.GetFirst(repo.WithByLowerName(req.Name)); exist != nil && exist.ID > 0 {
|
||||
return nil, buserr.New("ErrNameIsExist")
|
||||
@@ -114,7 +84,11 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
||||
return nil, buserr.New("ErrNameIsExist")
|
||||
}
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenclaw))
|
||||
appKey := constant.AppOpenclaw
|
||||
if agentType == constant.AppCopaw {
|
||||
appKey = constant.AppCopaw
|
||||
}
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(appKey))
|
||||
if err != nil || app.ID == 0 {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
@@ -123,24 +97,86 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(req.Token)
|
||||
if token == "" {
|
||||
token = generateToken()
|
||||
provider := ""
|
||||
baseURL := ""
|
||||
apiType := ""
|
||||
maxTokens := 0
|
||||
contextWindow := 0
|
||||
apiKey := ""
|
||||
runtimeModel := ""
|
||||
accountID := uint(0)
|
||||
token := ""
|
||||
configPath := ""
|
||||
storedModel := ""
|
||||
|
||||
if agentType == constant.AppOpenclaw {
|
||||
provider = strings.ToLower(strings.TrimSpace(req.Provider))
|
||||
if !isSupportedAgentProvider(provider) {
|
||||
return nil, buserr.New("ErrAgentProviderNotSupported")
|
||||
}
|
||||
if req.AccountID == 0 {
|
||||
return nil, buserr.New("ErrAgentAccountRequired")
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !account.Verified {
|
||||
return nil, buserr.New("ErrAgentAccountNotVerified")
|
||||
}
|
||||
if account.Provider != "" && provider != "" && account.Provider != provider {
|
||||
return nil, buserr.New("ErrAgentProviderMismatch")
|
||||
}
|
||||
provider = strings.ToLower(strings.TrimSpace(account.Provider))
|
||||
baseURL = strings.TrimSpace(account.BaseURL)
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
if provider == "ollama" && baseURL == "" {
|
||||
return nil, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if provider != "ollama" && strings.TrimSpace(account.APIKey) == "" {
|
||||
return nil, buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
apiType, maxTokens, contextWindow = resolveRuntimeParams(provider, account.APIType, account.MaxTokens, account.ContextWindow)
|
||||
runtimeModel = strings.TrimSpace(req.Model)
|
||||
if runtimeModel == "" {
|
||||
return nil, buserr.New("ErrAgentProviderMismatch")
|
||||
}
|
||||
if provider == "custom" {
|
||||
primaryID := customPrimaryModelID(req.Model)
|
||||
if primaryID == "" {
|
||||
primaryID = normalizeCustomModel(req.Model)
|
||||
}
|
||||
runtimeModel = "custom/" + primaryID
|
||||
}
|
||||
storedModel = req.Model
|
||||
apiKey = account.APIKey
|
||||
accountID = account.ID
|
||||
token = strings.TrimSpace(req.Token)
|
||||
if token == "" {
|
||||
token = generateToken()
|
||||
}
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"PROVIDER": provider,
|
||||
"MODEL": runtimeModel,
|
||||
"API_TYPE": apiType,
|
||||
"MAX_TOKENS": maxTokens,
|
||||
"CONTEXT_WINDOW": contextWindow,
|
||||
"BASE_URL": baseURL,
|
||||
"API_KEY": account.APIKey,
|
||||
"OPENCLAW_GATEWAY_TOKEN": token,
|
||||
"PANEL_APP_PORT_HTTP": req.WebUIPort,
|
||||
"PANEL_APP_PORT_BRIDGE": req.BridgePort,
|
||||
constant.CPUS: "0",
|
||||
constant.MemoryLimit: "0",
|
||||
constant.HostIP: "",
|
||||
"PANEL_APP_PORT_HTTP": req.WebUIPort,
|
||||
constant.CPUS: "0",
|
||||
constant.MemoryLimit: "0",
|
||||
constant.HostIP: "",
|
||||
}
|
||||
if agentType == constant.AppOpenclaw {
|
||||
params["PROVIDER"] = provider
|
||||
params["MODEL"] = runtimeModel
|
||||
params["API_TYPE"] = apiType
|
||||
params["MAX_TOKENS"] = maxTokens
|
||||
params["CONTEXT_WINDOW"] = contextWindow
|
||||
params["BASE_URL"] = baseURL
|
||||
params["API_KEY"] = apiKey
|
||||
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
||||
params["PANEL_APP_PORT_BRIDGE"] = req.BridgePort
|
||||
}
|
||||
|
||||
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
|
||||
@@ -169,27 +205,32 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configPath := path.Join(appInstall.GetPath(), "data", "conf", "openclaw.json")
|
||||
if agentType == constant.AppOpenclaw {
|
||||
configPath = path.Join(appInstall.GetPath(), "data", "conf", "openclaw.json")
|
||||
}
|
||||
agent := &model.Agent{
|
||||
Name: req.Name,
|
||||
AgentType: agentType,
|
||||
Provider: provider,
|
||||
Model: req.Model,
|
||||
Model: storedModel,
|
||||
APIType: apiType,
|
||||
MaxTokens: maxTokens,
|
||||
ContextWindow: contextWindow,
|
||||
BaseURL: baseURL,
|
||||
APIKey: account.APIKey,
|
||||
APIKey: apiKey,
|
||||
Token: token,
|
||||
Status: appInstall.Status,
|
||||
Message: appInstall.Message,
|
||||
AppInstallID: appInstall.ID,
|
||||
AccountID: account.ID,
|
||||
AccountID: accountID,
|
||||
ConfigPath: configPath,
|
||||
}
|
||||
if err := agentRepo.Create(agent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go a.writeConfigWithRetry(appInstall, provider, req.Model, apiType, maxTokens, contextWindow, baseURL, account.APIKey, token, agent.ID)
|
||||
if agentType == constant.AppOpenclaw {
|
||||
go a.writeConfigWithRetry(appInstall, provider, req.Model, apiType, maxTokens, contextWindow, baseURL, apiKey, token, agent.ID)
|
||||
}
|
||||
|
||||
item := buildAgentItem(agent, appInstall, nil)
|
||||
return &item, nil
|
||||
@@ -244,6 +285,9 @@ func (a AgentService) ResetToken(req dto.AgentTokenResetReq) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
|
||||
return fmt.Errorf("copaw does not support token")
|
||||
}
|
||||
configPath := strings.TrimSpace(agent.ConfigPath)
|
||||
if configPath == "" && agent.AppInstallID > 0 {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
|
||||
@@ -284,6 +328,9 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
|
||||
return fmt.Errorf("copaw does not support model config")
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1119,9 +1166,14 @@ func verifyMinimax(baseURL, apiKey string) error {
|
||||
}
|
||||
|
||||
func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map[string]interface{}) dto.AgentItem {
|
||||
agentType := normalizeAgentType(agent.AgentType)
|
||||
if appInstall != nil && appInstall.ID > 0 && appInstall.App.Key == constant.AppCopaw {
|
||||
agentType = constant.AppCopaw
|
||||
}
|
||||
item := dto.AgentItem{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
AgentType: agentType,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providerDisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
@@ -1807,6 +1859,23 @@ func customPrimaryModelID(modelName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeAgentType(agentType string) string {
|
||||
trim := strings.ToLower(strings.TrimSpace(agentType))
|
||||
if trim == "" {
|
||||
return constant.AppOpenclaw
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func isSupportedAgentType(agentType string) bool {
|
||||
switch normalizeAgentType(agentType) {
|
||||
case constant.AppOpenclaw, constant.AppCopaw:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAPIType(apiType string) string {
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "" {
|
||||
|
||||
@@ -393,7 +393,7 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
|
||||
return err
|
||||
}
|
||||
appKey := install.App.Key
|
||||
if appKey == constant.AppOpenclaw {
|
||||
if appKey == constant.AppOpenclaw || appKey == constant.AppCopaw {
|
||||
_ = agentRepo.DeleteByAppInstallIDWithCtx(ctx, install.ID)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ const (
|
||||
|
||||
AppOpenresty = "openresty"
|
||||
AppOpenclaw = "openclaw"
|
||||
AppCopaw = "copaw"
|
||||
AppMysql = "mysql"
|
||||
AppMariaDB = "mariadb"
|
||||
AppPostgresql = "postgresql"
|
||||
|
||||
@@ -69,6 +69,7 @@ func InitAgentDB() {
|
||||
migrations.AddAppInstallSortOrder,
|
||||
migrations.AddAgentAccountRememberAPIKey,
|
||||
migrations.AddEditionSetting,
|
||||
migrations.AddAgentTypeForAgents,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -993,3 +993,20 @@ var AddEditionSetting = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAgentTypeForAgents = &gormigrate.Migration{
|
||||
ID: "20260302-add-agent-type-for-agents",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Agent{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Agent{}).Where("agent_type = '' OR agent_type IS NULL").Update("agent_type", constant.AppOpenclaw).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Exec(
|
||||
"UPDATE agents SET agent_type = ? WHERE app_install_id IN (SELECT ai.id FROM app_installs ai JOIN apps a ON ai.app_id = a.id WHERE a.key = ?)",
|
||||
constant.AppCopaw,
|
||||
constant.AppCopaw,
|
||||
).Error
|
||||
},
|
||||
}
|
||||
|
||||
@@ -240,16 +240,17 @@ export namespace AI {
|
||||
name: string;
|
||||
appVersion: string;
|
||||
webUIPort: number;
|
||||
bridgePort: number;
|
||||
provider: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
accountId: number;
|
||||
apiKey: string;
|
||||
baseURL: string;
|
||||
token: string;
|
||||
bridgePort?: number;
|
||||
agentType: 'openclaw' | 'copaw';
|
||||
provider?: string;
|
||||
model?: string;
|
||||
apiType?: string;
|
||||
maxTokens?: number;
|
||||
contextWindow?: number;
|
||||
accountId?: number;
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
token?: string;
|
||||
taskID: string;
|
||||
advanced: boolean;
|
||||
containerName: string;
|
||||
@@ -267,6 +268,7 @@ export namespace AI {
|
||||
export interface AgentItem {
|
||||
id: number;
|
||||
name: string;
|
||||
agentType: 'openclaw' | 'copaw';
|
||||
provider: string;
|
||||
providerName: string;
|
||||
model: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import http from '@/api';
|
||||
import { ResPage, SearchWithPage } from '../interface';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
export const createOllamaModel = (name: string, taskID: string) => {
|
||||
return http.post(`/ai/ollama/model`, { name: name, taskID: taskID });
|
||||
@@ -93,7 +94,7 @@ export const operateTensorRTLLM = (req: AI.TensorRTLLMOperate) => {
|
||||
};
|
||||
|
||||
export const createAgent = (req: AI.AgentCreateReq) => {
|
||||
return http.post<AI.AgentItem>(`/ai/agents`, req);
|
||||
return http.post<AI.AgentItem>(`/ai/agents`, req, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
||||
export const pageAgents = (req: SearchWithPage) => {
|
||||
|
||||
@@ -684,6 +684,8 @@ const message = {
|
||||
accountCount: '{0} model accounts',
|
||||
syncAgents: 'Sync related agents',
|
||||
syncAgentsHelper: 'Update openclaw.json for agents using this model account',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'App Version',
|
||||
webuiPort: 'WebUI Port',
|
||||
bridgePort: 'Bridge Port',
|
||||
|
||||
@@ -680,6 +680,8 @@ const message = {
|
||||
accountCount: '{0} cuentas de modelo',
|
||||
syncAgents: 'Sincronizar agentes vinculados',
|
||||
syncAgentsHelper: 'Actualiza openclaw.json para los agentes que usan esta cuenta de modelo',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'Versión de la app',
|
||||
webuiPort: 'Puerto WebUI',
|
||||
bridgePort: 'Puerto Bridge',
|
||||
|
||||
@@ -672,6 +672,8 @@ const message = {
|
||||
accountCount: 'モデルアカウント {0} 件',
|
||||
syncAgents: '関連エージェントを同期',
|
||||
syncAgentsHelper: 'このモデルアカウントを使用するエージェントの openclaw.json を更新',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'アプリバージョン',
|
||||
webuiPort: 'WebUI ポート',
|
||||
bridgePort: 'Bridge ポート',
|
||||
|
||||
@@ -667,6 +667,8 @@ const message = {
|
||||
accountCount: '모델 계정 {0}개',
|
||||
syncAgents: '관련 에이전트 동기화',
|
||||
syncAgentsHelper: '이 모델 계정을 사용하는 에이전트의 openclaw.json 업데이트',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: '앱 버전',
|
||||
webuiPort: 'WebUI 포트',
|
||||
bridgePort: 'Bridge 포트',
|
||||
|
||||
@@ -683,6 +683,8 @@ const message = {
|
||||
accountCount: 'Akaun model {0}',
|
||||
syncAgents: 'Segerakkan agen berkaitan',
|
||||
syncAgentsHelper: 'Kemas kini openclaw.json untuk agen yang menggunakan akaun model ini',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'Versi aplikasi',
|
||||
webuiPort: 'Port WebUI',
|
||||
bridgePort: 'Port Bridge',
|
||||
|
||||
@@ -680,6 +680,8 @@ const message = {
|
||||
accountCount: '{0} contas de modelo',
|
||||
syncAgents: 'Sincronizar agentes vinculados',
|
||||
syncAgentsHelper: 'Atualize o openclaw.json para agentes que usam esta conta de modelo',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'Versão do app',
|
||||
webuiPort: 'Porta WebUI',
|
||||
bridgePort: 'Porta Bridge',
|
||||
|
||||
@@ -675,6 +675,8 @@ const message = {
|
||||
accountCount: '{0} аккаунтов модели',
|
||||
syncAgents: 'Синхронизировать связанные агенты',
|
||||
syncAgentsHelper: 'Обновите openclaw.json для агентов, использующих этот аккаунт модели',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'Версия приложения',
|
||||
webuiPort: 'Порт WebUI',
|
||||
bridgePort: 'Порт Bridge',
|
||||
|
||||
@@ -689,6 +689,8 @@ const message = {
|
||||
accountCount: '{0} model hesabı',
|
||||
syncAgents: 'İlişkili ajanları senkronize et',
|
||||
syncAgentsHelper: 'Bu model hesabını kullanan ajanlar için openclaw.json dosyasını güncelleyin',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: 'Uygulama sürümü',
|
||||
webuiPort: 'WebUI portu',
|
||||
bridgePort: 'Bridge portu',
|
||||
|
||||
@@ -658,6 +658,8 @@ const message = {
|
||||
accountCount: '模型帳號 {0} 個',
|
||||
syncAgents: '同步關聯智能體',
|
||||
syncAgentsHelper: '更新使用該模型帳號的智能體 openclaw.json',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: '應用版本',
|
||||
webuiPort: 'WebUI 埠',
|
||||
bridgePort: 'Bridge 埠',
|
||||
|
||||
@@ -642,6 +642,8 @@ const message = {
|
||||
accountCount: '模型账号 {0} 个',
|
||||
syncAgents: '同步关联智能体',
|
||||
syncAgentsHelper: '更新使用该模型账号的智能体 openclaw.json',
|
||||
openclawType: 'OpenClaw',
|
||||
copawType: 'CoPaw',
|
||||
appVersion: '应用版本',
|
||||
webuiPort: 'WebUI 端口',
|
||||
bridgePort: 'Bridge 端口',
|
||||
|
||||
@@ -16,6 +16,16 @@ export const jumpToInstall = (type: string, key: string) => {
|
||||
case 'openclaw':
|
||||
jumpToPath(router, '/ai/agents/agent');
|
||||
return true;
|
||||
case 'copaw':
|
||||
router.push({
|
||||
path: '/ai/agents/agent',
|
||||
query: {
|
||||
uncached: 'true',
|
||||
open: 'create',
|
||||
agentType: 'copaw',
|
||||
},
|
||||
});
|
||||
return true;
|
||||
case 'mysql-cluster':
|
||||
jumpToPath(router, '/xpack/cluster/mysql');
|
||||
return true;
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('aiTools.agents.agents')}${$t('commons.table.type')}`" prop="agentType">
|
||||
<el-select v-model="form.agentType" @change="handleAgentTypeChange">
|
||||
<el-option :label="$t('aiTools.agents.openclawType')" value="openclaw" />
|
||||
<el-option :label="$t('aiTools.agents.copawType')" value="copaw" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.appVersion')" prop="appVersion">
|
||||
<el-select v-model="form.appVersion" filterable>
|
||||
<el-option v-for="item in versions" :key="item" :label="item" :value="item" />
|
||||
@@ -13,11 +19,15 @@
|
||||
<el-form-item :label="$t('aiTools.agents.webuiPort')" prop="webUIPort">
|
||||
<el-input-number v-model="form.webUIPort" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.bridgePort')" prop="bridgePort">
|
||||
<el-form-item
|
||||
v-if="form.agentType === 'openclaw'"
|
||||
:label="$t('aiTools.agents.bridgePort')"
|
||||
prop="bridgePort"
|
||||
>
|
||||
<el-input-number v-model="form.bridgePort" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="form-card">
|
||||
<el-card class="form-card" v-if="form.agentType === 'openclaw'">
|
||||
<el-form-item :label="$t('aiTools.agents.provider')" prop="provider">
|
||||
<el-select v-model="form.provider" @change="handleProviderChange">
|
||||
<el-option
|
||||
@@ -114,6 +124,7 @@ const accountAddRef = ref();
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
agentType: 'openclaw' as 'openclaw' | 'copaw',
|
||||
appVersion: '',
|
||||
webUIPort: 18789,
|
||||
bridgePort: 18790,
|
||||
@@ -139,14 +150,37 @@ const form = reactive({
|
||||
dockerCompose: '',
|
||||
});
|
||||
|
||||
const validateOpenclawOnly = (field: 'provider' | 'accountId' | 'model' | 'bridgePort') => {
|
||||
return (_rule: any, value: any, callback: (error?: Error) => void) => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (field === 'bridgePort') {
|
||||
if (!value || Number(value) <= 0) {
|
||||
callback(new Error('bridge port is required'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (value === undefined || value === null || value === '') {
|
||||
callback(new Error(`${field} is required`));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
agentType: [Rules.requiredSelect],
|
||||
appVersion: [Rules.requiredSelect],
|
||||
webUIPort: [Rules.requiredInput],
|
||||
bridgePort: [Rules.requiredInput],
|
||||
provider: [Rules.requiredSelect],
|
||||
accountId: [Rules.requiredSelect],
|
||||
model: [Rules.requiredSelect],
|
||||
bridgePort: [{ validator: validateOpenclawOnly('bridgePort'), trigger: 'blur' }],
|
||||
provider: [{ validator: validateOpenclawOnly('provider'), trigger: 'change' }],
|
||||
accountId: [{ validator: validateOpenclawOnly('accountId'), trigger: 'change' }],
|
||||
model: [{ validator: validateOpenclawOnly('model'), trigger: 'change' }],
|
||||
containerName: [Rules.containerName],
|
||||
restartPolicy: [Rules.requiredSelect],
|
||||
cpuQuota: [checkNumberRange(0, 99999)],
|
||||
@@ -156,8 +190,8 @@ const rules = reactive({
|
||||
|
||||
const filteredModels = computed(() => providerModels.value[form.provider] || []);
|
||||
|
||||
const loadVersions = async () => {
|
||||
const res = await getAppByKey('openclaw');
|
||||
const loadVersions = async (appKey: 'openclaw' | 'copaw') => {
|
||||
const res = await getAppByKey(appKey);
|
||||
appInfo.value = res.data;
|
||||
versions.value = res.data.versions || [];
|
||||
if (!form.appVersion && versions.value.length > 0) {
|
||||
@@ -174,6 +208,11 @@ const loadCompose = async () => {
|
||||
};
|
||||
|
||||
const loadProviders = async () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
providerOptions.value = [];
|
||||
providerModels.value = {};
|
||||
return;
|
||||
}
|
||||
const res = await getAgentProviders();
|
||||
const data = res.data || [];
|
||||
providerOptions.value = data.map((item) => ({
|
||||
@@ -206,6 +245,10 @@ const loadProviderAccountCounts = async (providers: string[]) => {
|
||||
};
|
||||
|
||||
const loadAccounts = async () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
accountOptions.value = [];
|
||||
return;
|
||||
}
|
||||
if (!form.provider) {
|
||||
accountOptions.value = [];
|
||||
return;
|
||||
@@ -230,6 +273,9 @@ const loadAccounts = async () => {
|
||||
};
|
||||
|
||||
const handleProviderChange = () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
return;
|
||||
}
|
||||
form.model = '';
|
||||
form.apiKey = '';
|
||||
form.baseURL = '';
|
||||
@@ -238,6 +284,27 @@ const handleProviderChange = () => {
|
||||
setDefaultModel();
|
||||
};
|
||||
|
||||
const handleAgentTypeChange = async () => {
|
||||
if (form.name === '' || form.name === 'OpenClaw' || form.name === 'CoPaw') {
|
||||
form.name = form.agentType === 'copaw' ? 'CoPaw' : 'OpenClaw';
|
||||
}
|
||||
form.appVersion = '';
|
||||
form.model = '';
|
||||
form.provider = 'deepseek';
|
||||
form.accountId = undefined as unknown as number;
|
||||
form.apiKey = '';
|
||||
form.baseURL = '';
|
||||
form.apiType = 'openai-completions';
|
||||
if (form.agentType === 'openclaw') {
|
||||
form.bridgePort = form.bridgePort || 18790;
|
||||
await loadVersions('openclaw');
|
||||
await loadProviders();
|
||||
await loadAccounts();
|
||||
return;
|
||||
}
|
||||
await loadVersions('copaw');
|
||||
};
|
||||
|
||||
const handleModelChange = () => {
|
||||
if (manualModel.value) {
|
||||
return;
|
||||
@@ -245,6 +312,9 @@ const handleModelChange = () => {
|
||||
};
|
||||
|
||||
const handleAccountChange = () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
return;
|
||||
}
|
||||
const selected = accountOptions.value.find((item) => item.id === form.accountId);
|
||||
if (selected) {
|
||||
form.baseURL = selected.baseUrl || '';
|
||||
@@ -260,6 +330,9 @@ const handleAccountChange = () => {
|
||||
};
|
||||
|
||||
const setDefaultModel = () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
return;
|
||||
}
|
||||
if (manualModel.value) {
|
||||
return;
|
||||
}
|
||||
@@ -282,40 +355,50 @@ const submit = async () => {
|
||||
}
|
||||
await formRef.value.validate();
|
||||
const taskID = newUUID();
|
||||
if (!form.token) {
|
||||
if (form.agentType === 'openclaw' && !form.token) {
|
||||
form.token = getRandomStr(32).toLowerCase();
|
||||
}
|
||||
const res = await createAgent({
|
||||
name: form.name,
|
||||
appVersion: form.appVersion,
|
||||
webUIPort: form.webUIPort,
|
||||
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,
|
||||
token: form.token,
|
||||
taskID: taskID,
|
||||
advanced: form.advanced,
|
||||
containerName: form.containerName,
|
||||
allowPort: form.allowPort,
|
||||
specifyIP: form.specifyIP,
|
||||
restartPolicy: form.restartPolicy,
|
||||
cpuQuota: form.cpuQuota,
|
||||
memoryLimit: form.memoryLimit,
|
||||
memoryUnit: form.memoryUnit,
|
||||
pullImage: form.pullImage,
|
||||
editCompose: form.editCompose,
|
||||
dockerCompose: form.dockerCompose,
|
||||
});
|
||||
form.token = res.data.token || form.token;
|
||||
emit('search');
|
||||
emit('task', taskID);
|
||||
open.value = false;
|
||||
try {
|
||||
const res = await createAgent({
|
||||
name: form.name,
|
||||
appVersion: form.appVersion,
|
||||
webUIPort: form.webUIPort,
|
||||
bridgePort: form.agentType === 'openclaw' ? form.bridgePort : undefined,
|
||||
agentType: form.agentType,
|
||||
provider: form.agentType === 'openclaw' ? form.provider : undefined,
|
||||
model: form.agentType === 'openclaw' ? form.model : undefined,
|
||||
apiType: form.agentType === 'openclaw' ? form.apiType : undefined,
|
||||
maxTokens: form.agentType === 'openclaw' ? form.maxTokens : undefined,
|
||||
contextWindow: form.agentType === 'openclaw' ? form.contextWindow : undefined,
|
||||
accountId: form.agentType === 'openclaw' ? form.accountId : undefined,
|
||||
apiKey: form.agentType === 'openclaw' ? form.apiKey : undefined,
|
||||
baseURL: form.agentType === 'openclaw' ? form.baseURL : undefined,
|
||||
token: form.agentType === 'openclaw' ? form.token : undefined,
|
||||
taskID: taskID,
|
||||
advanced: form.advanced,
|
||||
containerName: form.containerName,
|
||||
allowPort: form.allowPort,
|
||||
specifyIP: form.specifyIP,
|
||||
restartPolicy: form.restartPolicy,
|
||||
cpuQuota: form.cpuQuota,
|
||||
memoryLimit: form.memoryLimit,
|
||||
memoryUnit: form.memoryUnit,
|
||||
pullImage: form.pullImage,
|
||||
editCompose: form.editCompose,
|
||||
dockerCompose: form.dockerCompose,
|
||||
});
|
||||
form.token = res.data.token || form.token;
|
||||
emit('search');
|
||||
emit('task', taskID);
|
||||
open.value = false;
|
||||
} catch (error: any) {
|
||||
const message = String(error?.message || '').toLowerCase();
|
||||
const isTimeout = message.includes('timeout') || error?.code === 'ECONNABORTED';
|
||||
if (isTimeout) {
|
||||
emit('task', taskID);
|
||||
open.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -324,17 +407,29 @@ const handleClose = () => {
|
||||
form.dockerCompose = '';
|
||||
};
|
||||
|
||||
const openDrawer = async () => {
|
||||
form.name = 'openclaw';
|
||||
const openDrawer = async (agentType?: 'openclaw' | 'copaw') => {
|
||||
const targetType = agentType === 'copaw' ? 'copaw' : 'openclaw';
|
||||
form.name = targetType === 'copaw' ? 'CoPaw' : 'OpenClaw';
|
||||
open.value = true;
|
||||
manualModel.value = false;
|
||||
form.agentType = targetType;
|
||||
form.token = getRandomStr(32).toLowerCase();
|
||||
await loadVersions();
|
||||
if (form.agentType === 'copaw') {
|
||||
await loadVersions('copaw');
|
||||
providerOptions.value = [];
|
||||
providerModels.value = {};
|
||||
accountOptions.value = [];
|
||||
return;
|
||||
}
|
||||
await loadVersions('openclaw');
|
||||
await loadProviders();
|
||||
await loadAccounts();
|
||||
};
|
||||
|
||||
const openAccountCreate = () => {
|
||||
if (form.agentType !== 'openclaw') {
|
||||
return;
|
||||
}
|
||||
if (accountAddRef.value?.open) {
|
||||
accountAddRef.value.open({ provider: form.provider });
|
||||
}
|
||||
|
||||
@@ -18,6 +18,19 @@
|
||||
prop="name"
|
||||
min-width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
:label="`${$t('aiTools.agents.agents')}${$t('commons.table.type')}`"
|
||||
prop="agentType"
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.agentType === 'copaw'
|
||||
? $t('aiTools.agents.copawType')
|
||||
: $t('aiTools.agents.openclawType')
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status" width="120">
|
||||
<template #default="{ row }">
|
||||
<Status :status="row.status" />
|
||||
@@ -38,10 +51,13 @@
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span>{{ getAgentProviderDisplayName(row.provider, row.providerName) }}</span>
|
||||
<div>
|
||||
<span>{{ row.model }}</span>
|
||||
</div>
|
||||
<template v-if="row.agentType !== 'copaw'">
|
||||
<span>{{ getAgentProviderDisplayName(row.provider, row.providerName) }}</span>
|
||||
<div>
|
||||
<span>{{ row.model }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.port')" prop="webUIPort" min-width="150">
|
||||
@@ -49,7 +65,7 @@
|
||||
<el-button icon="Position" plain size="small" @click="jumpWebUI(row)">
|
||||
{{ $t('aiTools.agents.webuiPort') }}: {{ row.webUIPort }}
|
||||
</el-button>
|
||||
<div class="mt-0.5">
|
||||
<div v-if="row.agentType !== 'copaw'" class="mt-0.5">
|
||||
<el-button plain size="small">
|
||||
{{ $t('aiTools.agents.bridgePort') }} {{ row.bridgePort }}
|
||||
</el-button>
|
||||
@@ -58,12 +74,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('aiTools.agents.token')" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-space>
|
||||
<el-space v-if="row.agentType !== 'copaw'">
|
||||
<CopyButton :content="row.token" />
|
||||
<el-button link type="primary" @click="onResetToken(row)">
|
||||
{{ $t('commons.button.reset') }}
|
||||
</el-button>
|
||||
</el-space>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -96,6 +113,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { pageAgents, resetAgentToken } from '@/api/modules/ai';
|
||||
import { installedOp, searchAppInstalled } from '@/api/modules/app';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
@@ -127,6 +145,8 @@ const upgradeRef = ref();
|
||||
const composeLogRef = ref();
|
||||
const dialogTerminalRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const isActive = ref(false);
|
||||
const isExist = ref(false);
|
||||
const searchName = ref('');
|
||||
@@ -135,6 +155,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.config'),
|
||||
click: (row: AI.AgentItem) => openConfig(row),
|
||||
show: (row: AI.AgentItem) => row.agentType !== 'copaw',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('menu.terminal'),
|
||||
@@ -191,12 +212,26 @@ const search = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
const openCreate = (agentType?: 'openclaw' | 'copaw') => {
|
||||
const targetType = agentType === 'copaw' ? 'copaw' : 'openclaw';
|
||||
if (addRef.value?.open) {
|
||||
addRef.value.open();
|
||||
addRef.value.open(targetType);
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateFromQuery = async () => {
|
||||
const shouldOpen = route.query.open === 'create';
|
||||
if (!shouldOpen) {
|
||||
return;
|
||||
}
|
||||
const agentType = route.query.agentType === 'copaw' ? 'copaw' : 'openclaw';
|
||||
openCreate(agentType);
|
||||
const nextQuery = { ...route.query };
|
||||
delete nextQuery.open;
|
||||
delete nextQuery.agentType;
|
||||
await router.replace({ path: route.path, query: nextQuery });
|
||||
};
|
||||
|
||||
const openTaskLog = (taskID: string) => {
|
||||
if (taskLogRef.value?.openWithTaskID) {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
@@ -236,11 +271,12 @@ const openTerminal = (row: AI.AgentItem) => {
|
||||
};
|
||||
|
||||
const jumpWebUI = (row: AI.AgentItem) => {
|
||||
const query = row.agentType === 'copaw' ? '' : `token=${row.token}`;
|
||||
if (dialogPortJumpRef.value?.acceptParams) {
|
||||
dialogPortJumpRef.value.acceptParams({
|
||||
port: row.webUIPort,
|
||||
protocol: 'http',
|
||||
query: `token=${row.token}`,
|
||||
query,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -265,6 +301,9 @@ const onResetToken = async (row: AI.AgentItem) => {
|
||||
};
|
||||
|
||||
const openConfig = (row: AI.AgentItem) => {
|
||||
if (row.agentType === 'copaw') {
|
||||
return;
|
||||
}
|
||||
configRef.value?.open(row);
|
||||
};
|
||||
|
||||
@@ -279,5 +318,6 @@ const openUpgrade = async (row: AI.AgentItem) => {
|
||||
|
||||
onMounted(async () => {
|
||||
await search();
|
||||
await openCreateFromQuery();
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user