mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-01 08:00:06 +00:00
feat: OpenClaw supports configuring backup models. (#12386)
This commit is contained in:
@@ -111,6 +111,27 @@ func (b *BaseApi) UpdateAgentRemark(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent model config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentModelConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/model/get [post]
|
||||
func (b *BaseApi) GetAgentModelConfig(c *gin.Context) {
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.GetModelConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent model config
|
||||
// @Accept json
|
||||
|
||||
+10
-3
@@ -71,9 +71,16 @@ type AgentRemarkUpdateReq struct {
|
||||
}
|
||||
|
||||
type AgentModelConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model string `json:"model" validate:"required"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
}
|
||||
|
||||
type AgentModelConfig struct {
|
||||
AccountID uint `json:"accountId"`
|
||||
Model string `json:"model"`
|
||||
Fallbacks []string `json:"fallbacks"`
|
||||
}
|
||||
|
||||
type AgentOverviewReq struct {
|
||||
|
||||
@@ -30,6 +30,7 @@ type IAgentService interface {
|
||||
Delete(req dto.AgentDeleteReq) error
|
||||
ResetToken(req dto.AgentTokenResetReq) error
|
||||
UpdateRemark(req dto.AgentRemarkUpdateReq) error
|
||||
GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, error)
|
||||
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error)
|
||||
GetProviders() ([]dto.ProviderInfo, error)
|
||||
@@ -345,6 +346,30 @@ func (a AgentService) UpdateRemark(req dto.AgentRemarkUpdateReq) error {
|
||||
return agentRepo.Save(agent)
|
||||
}
|
||||
|
||||
func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, error) {
|
||||
agent, _, conf, err := a.loadOpenclawAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model := extractOpenclawPrimaryModelID(conf, account, models)
|
||||
if model == "" {
|
||||
model = agent.Model
|
||||
}
|
||||
return &dto.AgentModelConfig{
|
||||
AccountID: agent.AccountID,
|
||||
Model: model,
|
||||
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error {
|
||||
agent, err := loadOpenclawAgentByID(req.AgentID)
|
||||
if err != nil {
|
||||
@@ -361,7 +386,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
modelName := resolvedRuntime.StoredModel
|
||||
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
||||
confDir := path.Dir(agent.ConfigPath)
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
||||
return err
|
||||
}
|
||||
agent.Provider = account.Provider
|
||||
@@ -875,13 +900,18 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
||||
} else {
|
||||
selectedAccountModel = accountModels[0]
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
||||
resolvedRuntime, err := buildOpenclawAccountModelRuntime(account, selectedAccountModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modelName = resolvedRuntime.StoredModel
|
||||
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
||||
return err
|
||||
}
|
||||
agent.BaseURL = baseURL
|
||||
|
||||
@@ -339,10 +339,7 @@ func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
agentType := agent.AgentType
|
||||
item := dto.AgentItem{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
@@ -660,7 +657,8 @@ type agentDefaults struct {
|
||||
}
|
||||
|
||||
type modelRef struct {
|
||||
Primary string `json:"primary"`
|
||||
Primary string `json:"primary"`
|
||||
Fallbacks []string `json:"fallbacks,omitempty"`
|
||||
}
|
||||
|
||||
type modelsConfig struct {
|
||||
@@ -701,7 +699,7 @@ type browserConfig struct {
|
||||
DefaultProfile string `json:"defaultProfile"`
|
||||
}
|
||||
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string) error {
|
||||
func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, token string, allowedOrigins []string, fallbacks []string) error {
|
||||
if strings.TrimSpace(confDir) == "" {
|
||||
return fmt.Errorf("config dir is required")
|
||||
}
|
||||
@@ -724,6 +722,10 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resolvedFallbacks, err := resolveOpenclawFallbackModels(account, modelName, fallbacks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := openclawConfig{
|
||||
Gateway: gatewayConfig{
|
||||
@@ -743,7 +745,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
Agents: agentsConfig{
|
||||
Defaults: agentDefaults{
|
||||
UserTimezone: resolveServerTimezone(),
|
||||
Model: modelRef{Primary: primaryModel},
|
||||
Model: modelRef{Primary: primaryModel, Fallbacks: resolvedFallbacks},
|
||||
Models: defaultsModels,
|
||||
},
|
||||
},
|
||||
@@ -811,6 +813,11 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
}
|
||||
modelMap := ensureChildMap(defaultsMap, "model")
|
||||
modelMap["primary"] = cfg.Agents.Defaults.Model.Primary
|
||||
if len(cfg.Agents.Defaults.Model.Fallbacks) > 0 {
|
||||
modelMap["fallbacks"] = cfg.Agents.Defaults.Model.Fallbacks
|
||||
} else {
|
||||
delete(modelMap, "fallbacks")
|
||||
}
|
||||
if cfg.Agents.Defaults.Models != nil {
|
||||
defaultsMap["models"] = cfg.Agents.Defaults.Models
|
||||
}
|
||||
@@ -847,12 +854,128 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName,
|
||||
return fileOp.SaveFile(envPath, content, 0600)
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModels(account *model.AgentAccount, primaryModel string, fallbackIDs []string) ([]string, error) {
|
||||
accountModels, err := loadAgentAccountModels(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resolveOpenclawFallbackModelsFromPool(account, accountModels, primaryModel, fallbackIDs, true)
|
||||
}
|
||||
|
||||
func resolveOpenclawFallbackModelsFromPool(account *model.AgentAccount, accountModels []dto.AgentAccountModel, primaryModel string, fallbackIDs []string, strict bool) ([]string, error) {
|
||||
if len(fallbackIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
result := make([]string, 0, len(fallbackIDs))
|
||||
seen := make(map[string]struct{}, len(fallbackIDs))
|
||||
for _, item := range fallbackIDs {
|
||||
target := strings.TrimSpace(item)
|
||||
if target == "" {
|
||||
continue
|
||||
}
|
||||
accountModel, err := requireAgentAccountModelForProvider(account.Provider, accountModels, target)
|
||||
if err != nil {
|
||||
if strict {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if sameProviderModelID(account.Provider, accountModel.ID, primaryModel) {
|
||||
if strict {
|
||||
return nil, buserr.New("ErrAgentFallbackModelPrimary")
|
||||
}
|
||||
continue
|
||||
}
|
||||
resolvedPrimary, _, _, _, err := buildOpenclawCatalogModel(account, accountModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := seen[resolvedPrimary]; ok {
|
||||
if strict {
|
||||
return nil, buserr.New("ErrAgentFallbackModelDuplicate")
|
||||
}
|
||||
continue
|
||||
}
|
||||
seen[resolvedPrimary] = struct{}{}
|
||||
result = append(result, resolvedPrimary)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func extractOpenclawPrimaryModelID(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel) string {
|
||||
modelMap := openclawModelRefMap(conf)
|
||||
primary := strings.TrimSpace(fmt.Sprintf("%v", modelMap["primary"]))
|
||||
if primary == "" {
|
||||
return ""
|
||||
}
|
||||
resolved, err := buildOpenclawResolvedModelIDMap(account, accountModels)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resolved[primary]
|
||||
}
|
||||
|
||||
func extractOpenclawFallbackModelIDs(conf map[string]interface{}, account *model.AgentAccount, accountModels []dto.AgentAccountModel, primaryModel string) []string {
|
||||
modelMap := openclawModelRefMap(conf)
|
||||
raw, ok := modelMap["fallbacks"].([]interface{})
|
||||
if !ok || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
resolved, err := buildOpenclawResolvedModelIDMap(account, accountModels)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]string, 0, len(raw))
|
||||
seen := make(map[string]struct{}, len(raw))
|
||||
for _, item := range raw {
|
||||
target := strings.TrimSpace(fmt.Sprintf("%v", item))
|
||||
modelID, ok := resolved[target]
|
||||
if !ok || sameProviderModelID(account.Provider, modelID, primaryModel) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[modelID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[modelID] = struct{}{}
|
||||
result = append(result, modelID)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func openclawModelRefMap(conf map[string]interface{}) map[string]interface{} {
|
||||
agentsMap, ok := conf["agents"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
defaultsMap, ok := agentsMap["defaults"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
modelMap, ok := defaultsMap["model"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return modelMap
|
||||
}
|
||||
|
||||
func buildOpenclawResolvedModelIDMap(account *model.AgentAccount, accountModels []dto.AgentAccountModel) (map[string]string, error) {
|
||||
result := make(map[string]string, len(accountModels))
|
||||
for _, item := range accountModels {
|
||||
resolvedPrimary, _, _, _, err := buildOpenclawCatalogModel(account, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[resolvedPrimary] = item.ID
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func prepareOpenclawInstallFiles(appInstall *model.AppInstall, account *model.AgentAccount, modelName, token string, allowedOrigins []string) error {
|
||||
if appInstall == nil {
|
||||
return fmt.Errorf("app install is required")
|
||||
}
|
||||
confDir := path.Join(appInstall.GetPath(), "data", "conf")
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins); err != nil {
|
||||
if err := writeOpenclawConfig(confDir, account, modelName, token, allowedOrigins, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
dataDir := path.Join(appInstall.GetPath(), "data")
|
||||
@@ -1176,12 +1299,6 @@ func replacePersistedAgentAccountModelsWithTx(tx *gorm.DB, accountID uint, model
|
||||
return nil
|
||||
}
|
||||
|
||||
func replacePersistedAgentAccountModels(accountID uint, models []dto.AgentAccountModel) error {
|
||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
return replacePersistedAgentAccountModelsWithTx(tx, accountID, models)
|
||||
})
|
||||
}
|
||||
|
||||
func normalizeAgentAccountModels(account *model.AgentAccount, models []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||
requested := append([]dto.AgentAccountModel(nil), models...)
|
||||
if len(requested) == 0 {
|
||||
@@ -1534,24 +1651,6 @@ func normalizeCustomModel(modelName string) string {
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeAgentType(agentType string) string {
|
||||
trim := agentType
|
||||
if trim == "" {
|
||||
return constant.AppOpenclaw
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func modelMatchesProvider(provider, modelName string) bool {
|
||||
target := strings.TrimSpace(modelName)
|
||||
for _, prefix := range supportedProviderModelPrefixes(provider) {
|
||||
if prefix != "" && strings.HasPrefix(target, prefix+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runtimeProviderModelPrefix(provider string) string {
|
||||
switch provider {
|
||||
case "gemini":
|
||||
|
||||
@@ -49,6 +49,8 @@ ErrAgentAccountNotVerified: 'Agent account not verified'
|
||||
ErrAgentAccountModelsRequired: 'Please configure at least one model'
|
||||
ErrAgentAccountSingleInitialModel: 'Only one initial model can be configured during account creation'
|
||||
ErrAgentModelNotInAccount: 'Model is not configured in the selected account'
|
||||
ErrAgentFallbackModelDuplicate: 'Fallback models cannot be duplicated'
|
||||
ErrAgentFallbackModelPrimary: 'Fallback model cannot match the primary model'
|
||||
ErrAgentModelInUse: 'A bound agent is still using this model: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Base URL is required'
|
||||
ErrAgentApiKeyRequired: 'API key is required'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'Cuenta de agente no verificada'
|
||||
ErrAgentAccountModelsRequired: 'Configure al menos un modelo'
|
||||
ErrAgentAccountSingleInitialModel: 'Solo se puede configurar un modelo inicial al crear la cuenta'
|
||||
ErrAgentModelNotInAccount: 'El modelo no está configurado en la cuenta seleccionada'
|
||||
ErrAgentFallbackModelDuplicate: 'Los modelos de respaldo no pueden duplicarse'
|
||||
ErrAgentFallbackModelPrimary: 'El modelo de respaldo no puede ser igual al modelo principal'
|
||||
ErrAgentModelInUse: 'Un agente vinculado todavía usa este modelo: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL base requerida'
|
||||
ErrAgentApiKeyRequired: 'Clave API requerida'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'アカウント未検証です'
|
||||
ErrAgentAccountModelsRequired: '少なくとも 1 つのモデルを設定してください'
|
||||
ErrAgentAccountSingleInitialModel: 'アカウント作成時に設定できる初期モデルは 1 つだけです'
|
||||
ErrAgentModelNotInAccount: '選択したモデルはこのアカウントに設定されていません'
|
||||
ErrAgentFallbackModelDuplicate: 'フォールバックモデルは重複できません'
|
||||
ErrAgentFallbackModelPrimary: 'フォールバックモデルは主モデルと同じにできません'
|
||||
ErrAgentModelInUse: 'このモデルはまだエージェントで使用中です: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'ベースURLが必要です'
|
||||
ErrAgentApiKeyRequired: 'APIキーが必要です'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: '에이전트 계정 미확인'
|
||||
ErrAgentAccountModelsRequired: '최소 한 개의 모델을 구성해 주세요'
|
||||
ErrAgentAccountSingleInitialModel: '계정 생성 시 초기 모델은 하나만 구성할 수 있습니다'
|
||||
ErrAgentModelNotInAccount: '선택한 모델이 현재 계정에 구성되어 있지 않습니다'
|
||||
ErrAgentFallbackModelDuplicate: '폴백 모델은 중복될 수 없습니다'
|
||||
ErrAgentFallbackModelPrimary: '폴백 모델은 기본 모델과 같을 수 없습니다'
|
||||
ErrAgentModelInUse: '이 모델을 아직 사용 중인 에이전트가 있습니다: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: '기본 URL 필요'
|
||||
ErrAgentApiKeyRequired: 'API 키 필요'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'Akaun ejen belum disahkan'
|
||||
ErrAgentAccountModelsRequired: 'Sila konfigur sekurang-kurangnya satu model'
|
||||
ErrAgentAccountSingleInitialModel: 'Hanya satu model awal boleh dikonfigurasi semasa mencipta akaun'
|
||||
ErrAgentModelNotInAccount: 'Model tidak dikonfigurasikan dalam akaun yang dipilih'
|
||||
ErrAgentFallbackModelDuplicate: 'Model fallback tidak boleh berulang'
|
||||
ErrAgentFallbackModelPrimary: 'Model fallback tidak boleh sama dengan model utama'
|
||||
ErrAgentModelInUse: 'Masih ada ejen terikat yang menggunakan model ini: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL asas diperlukan'
|
||||
ErrAgentApiKeyRequired: 'Kunci API diperlukan'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'Conta de agente não verificada'
|
||||
ErrAgentAccountModelsRequired: 'Configure pelo menos um modelo'
|
||||
ErrAgentAccountSingleInitialModel: 'Apenas um modelo inicial pode ser configurado ao criar a conta'
|
||||
ErrAgentModelNotInAccount: 'O modelo não está configurado na conta selecionada'
|
||||
ErrAgentFallbackModelDuplicate: 'Os modelos de fallback não podem ser duplicados'
|
||||
ErrAgentFallbackModelPrimary: 'O modelo de fallback não pode ser igual ao modelo principal'
|
||||
ErrAgentModelInUse: 'Ainda há um agente vinculado usando este modelo: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL base obrigatória'
|
||||
ErrAgentApiKeyRequired: 'Chave API obrigatória'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'Аккаунт не подтверждён'
|
||||
ErrAgentAccountModelsRequired: 'Настройте хотя бы одну модель'
|
||||
ErrAgentAccountSingleInitialModel: 'При создании аккаунта можно указать только одну начальную модель'
|
||||
ErrAgentModelNotInAccount: 'Модель не настроена в выбранном аккаунте'
|
||||
ErrAgentFallbackModelDuplicate: 'Резервные модели не могут дублироваться'
|
||||
ErrAgentFallbackModelPrimary: 'Резервная модель не может совпадать с основной'
|
||||
ErrAgentModelInUse: 'Эта модель всё ещё используется агентом: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Нужен базовый URL'
|
||||
ErrAgentApiKeyRequired: 'Нужен API-ключ'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: 'Ajans hesabı doğrulanmadı'
|
||||
ErrAgentAccountModelsRequired: 'Lütfen en az bir model yapılandırın'
|
||||
ErrAgentAccountSingleInitialModel: 'Hesap oluştururken yalnızca bir başlangıç modeli yapılandırılabilir'
|
||||
ErrAgentModelNotInAccount: 'Model seçilen hesapta yapılandırılmadı'
|
||||
ErrAgentFallbackModelDuplicate: 'Yedek modeller tekrar edemez'
|
||||
ErrAgentFallbackModelPrimary: 'Yedek model birincil modelle aynı olamaz'
|
||||
ErrAgentModelInUse: 'Bu modeli hâlâ kullanan bağlı bir ajan var: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Temel URL gerekli'
|
||||
ErrAgentApiKeyRequired: 'API anahtarı gerekli'
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrAgentAccountNotVerified: '帳號未驗證通過,請重試'
|
||||
ErrAgentAccountModelsRequired: '請至少配置一個模型'
|
||||
ErrAgentAccountSingleInitialModel: '建立帳號時只能配置一個初始模型'
|
||||
ErrAgentModelNotInAccount: '所選模型未配置在當前帳號中,請重試'
|
||||
ErrAgentFallbackModelDuplicate: '備用模型不能重複'
|
||||
ErrAgentFallbackModelPrimary: '備用模型不能與主模型相同'
|
||||
ErrAgentModelInUse: '仍有智能體正在使用該模型:{{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Base URL 不可為空,請重試'
|
||||
ErrAgentApiKeyRequired: 'API Key 不可為空,請重試'
|
||||
|
||||
@@ -49,6 +49,8 @@ ErrAgentAccountNotVerified: "账号未通过验证"
|
||||
ErrAgentAccountModelsRequired: "请至少配置一个模型"
|
||||
ErrAgentAccountSingleInitialModel: "创建账号时只能配置一个初始模型"
|
||||
ErrAgentModelNotInAccount: "所选模型未配置在当前账号中"
|
||||
ErrAgentFallbackModelDuplicate: "备用模型不能重复"
|
||||
ErrAgentFallbackModelPrimary: "备用模型不能与主模型相同"
|
||||
ErrAgentModelInUse: "仍有智能体正在使用该模型: {{ .name }}"
|
||||
ErrAgentBaseURLRequired: "Base URL 不能为空"
|
||||
ErrAgentApiKeyRequired: "API Key 不能为空"
|
||||
|
||||
@@ -45,6 +45,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
|
||||
aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
|
||||
aiToolsRouter.POST("/agents/remark", baseApi.UpdateAgentRemark)
|
||||
aiToolsRouter.POST("/agents/model/get", baseApi.GetAgentModelConfig)
|
||||
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
|
||||
aiToolsRouter.POST("/agents/overview", baseApi.GetAgentOverview)
|
||||
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
|
||||
|
||||
@@ -308,12 +308,23 @@ export namespace AI {
|
||||
agentId: number;
|
||||
accountId: number;
|
||||
model: string;
|
||||
fallbacks: string[];
|
||||
}
|
||||
|
||||
export interface AgentModelConfig {
|
||||
accountId: number;
|
||||
model: string;
|
||||
fallbacks: string[];
|
||||
}
|
||||
|
||||
export interface AgentOverviewReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentIDReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentRoleCreateReq {
|
||||
agentId: number;
|
||||
name: string;
|
||||
|
||||
@@ -113,6 +113,10 @@ export const updateAgentRemark = (req: AI.AgentRemarkUpdateReq) => {
|
||||
return http.post(`/ai/agents/remark`, req);
|
||||
};
|
||||
|
||||
export const getAgentModelConfig = (req: AI.AgentIDReq) => {
|
||||
return http.post<AI.AgentModelConfig>(`/ai/agents/model/get`, req);
|
||||
};
|
||||
|
||||
export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/model/update`, req);
|
||||
};
|
||||
|
||||
@@ -688,6 +688,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
fallbackModels: 'Fallback Models',
|
||||
addFallbackModel: 'Add Fallback Model',
|
||||
fallbackModelsEmpty: 'No fallback models configured',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
manualModel: 'Manual input',
|
||||
@@ -760,6 +763,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
primaryModel: 'Primary Model',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
|
||||
@@ -697,6 +697,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure al menos un modelo',
|
||||
accountModelsDuplicate: 'Existen modelos duplicados en el catálogo',
|
||||
modelPool: 'Pool de modelos',
|
||||
fallbackModels: 'Modelos de respaldo',
|
||||
addFallbackModel: 'Agregar modelo de respaldo',
|
||||
fallbackModelsEmpty: 'No hay modelos de respaldo configurados',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
reasoning: 'Modelo de razonamiento',
|
||||
manualModel: 'Entrada manual de modelo',
|
||||
@@ -772,6 +775,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Guardar el archivo de configuración requiere reiniciar inmediatamente el contenedor para surtir efecto.',
|
||||
overviewSnapshot: 'Resumen',
|
||||
primaryModel: 'Modelo principal',
|
||||
defaultModel: 'Modelo predeterminado',
|
||||
channelCount: 'Canales configurados',
|
||||
skillCount: 'Habilidades',
|
||||
|
||||
@@ -689,6 +689,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'カタログに重複したモデルがあります',
|
||||
modelPool: 'モデルプール',
|
||||
fallbackModels: 'フォールバックモデル',
|
||||
addFallbackModel: 'フォールバックモデルを追加',
|
||||
fallbackModelsEmpty: 'フォールバックモデルは未設定です',
|
||||
modelInputTypes: '入力タイプ',
|
||||
reasoning: '推論モデル',
|
||||
manualModel: '手動入力',
|
||||
@@ -763,6 +766,7 @@ const message = {
|
||||
'現在の markdown ファイルをすべて保存するには、反映のためコンテナの再起動が必要です。今すぐ再起動するか後で再起動するかを選択してください。',
|
||||
configFileRestartHelper: '設定ファイルを保存するには、反映のため直ちにコンテナを再起動する必要があります。',
|
||||
overviewSnapshot: '概要',
|
||||
primaryModel: 'メインモデル',
|
||||
defaultModel: 'デフォルトモデル',
|
||||
channelCount: '設定済みチャンネル数',
|
||||
skillCount: '技能数',
|
||||
|
||||
@@ -681,6 +681,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다',
|
||||
modelPool: '모델 풀',
|
||||
fallbackModels: '폴백 모델',
|
||||
addFallbackModel: '폴백 모델 추가',
|
||||
fallbackModelsEmpty: '구성된 폴백 모델이 없습니다',
|
||||
modelInputTypes: '입력 유형',
|
||||
reasoning: '추론 모델',
|
||||
manualModel: '수동 입력',
|
||||
@@ -749,6 +752,7 @@ const message = {
|
||||
'현재 markdown 파일을 모두 저장하려면 적용을 위해 컨테이너를 재시작해야 합니다. 지금 재시작할지 나중에 재시작할지 선택하세요.',
|
||||
configFileRestartHelper: '설정 파일을 저장하려면 즉시 컨테이너를 재시작해야 적용됩니다.',
|
||||
overviewSnapshot: '개요',
|
||||
primaryModel: '주 모델',
|
||||
defaultModel: '기본 모델',
|
||||
channelCount: '설정된 채널 수',
|
||||
skillCount: '기술 수',
|
||||
|
||||
@@ -696,6 +696,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
fallbackModels: 'Model fallback',
|
||||
addFallbackModel: 'Tambah model fallback',
|
||||
fallbackModelsEmpty: 'Tiada model fallback dikonfigurasikan',
|
||||
modelInputTypes: 'Jenis input',
|
||||
reasoning: 'Model penaakulan',
|
||||
manualModel: 'Input manual',
|
||||
@@ -768,6 +771,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Menyimpan fail konfigurasi memerlukan kontena dimulakan semula serta-merta untuk berkuat kuasa.',
|
||||
overviewSnapshot: 'Ringkasan',
|
||||
primaryModel: 'Model utama',
|
||||
defaultModel: 'Model lalai',
|
||||
channelCount: 'Bilangan saluran dikonfigurasi',
|
||||
skillCount: 'Bilangan kemahiran',
|
||||
|
||||
@@ -691,6 +691,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
fallbackModels: 'Modelos de fallback',
|
||||
addFallbackModel: 'Adicionar modelo de fallback',
|
||||
fallbackModelsEmpty: 'Nenhum modelo de fallback configurado',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
reasoning: 'Modelo de raciocínio',
|
||||
manualModel: 'Entrada manual',
|
||||
@@ -766,6 +769,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Salvar o arquivo de configuração exige reiniciar imediatamente o contêiner para entrar em vigor.',
|
||||
overviewSnapshot: 'Visão geral',
|
||||
primaryModel: 'Modelo principal',
|
||||
defaultModel: 'Modelo padrão',
|
||||
channelCount: 'Canais configurados',
|
||||
skillCount: 'Habilidades',
|
||||
|
||||
@@ -688,6 +688,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
fallbackModels: 'Резервные модели',
|
||||
addFallbackModel: 'Добавить резервную модель',
|
||||
fallbackModelsEmpty: 'Резервные модели не настроены',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
manualModel: 'Ручной ввод',
|
||||
@@ -760,6 +763,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Сохранение файла конфигурации требует немедленного перезапуска контейнера для применения.',
|
||||
overviewSnapshot: 'Сводка',
|
||||
primaryModel: 'Основная модель',
|
||||
defaultModel: 'Модель по умолчанию',
|
||||
channelCount: 'Настроенные каналы',
|
||||
skillCount: 'Навыки',
|
||||
|
||||
@@ -692,6 +692,9 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
fallbackModels: 'Yedek modeller',
|
||||
addFallbackModel: 'Yedek model ekle',
|
||||
fallbackModelsEmpty: 'Yapılandırılmış yedek model yok',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
manualModel: 'Manuel giriş',
|
||||
@@ -767,6 +770,7 @@ const message = {
|
||||
configFileRestartHelper:
|
||||
'Yapılandırma dosyasını kaydetmek, etkin olması için konteynerin hemen yeniden başlatılmasını gerektirir.',
|
||||
overviewSnapshot: 'Özet',
|
||||
primaryModel: 'Birincil model',
|
||||
defaultModel: 'Varsayılan model',
|
||||
channelCount: 'Yapılandırılmış kanallar',
|
||||
skillCount: 'Yetenekler',
|
||||
|
||||
@@ -656,6 +656,9 @@ const message = {
|
||||
accountModelsRequired: '請至少配置一個模型',
|
||||
accountModelsDuplicate: '模型池中存在重複模型,請檢查後重試',
|
||||
modelPool: '模型池',
|
||||
fallbackModels: '備用模型',
|
||||
addFallbackModel: '新增備用模型',
|
||||
fallbackModelsEmpty: '尚未配置備用模型',
|
||||
modelInputTypes: '輸入類型',
|
||||
reasoning: '推理模型',
|
||||
manualModel: '手動輸入模型',
|
||||
@@ -714,6 +717,7 @@ const message = {
|
||||
roleMarkdownRestartHelper: '保存當前全部 MD 檔案後,需要重新啟動容器才能生效。請選擇立即重啟或稍後重啟。',
|
||||
configFileRestartHelper: '保存配置檔後需要立即重新啟動容器才能生效。',
|
||||
overviewSnapshot: '狀態概覽',
|
||||
primaryModel: '主模型',
|
||||
defaultModel: '預設模型',
|
||||
channelCount: '已配置頻道數量',
|
||||
skillCount: '技能數量',
|
||||
|
||||
@@ -655,6 +655,9 @@ const message = {
|
||||
accountModelsRequired: '请至少配置一个模型',
|
||||
accountModelsDuplicate: '模型池中存在重复模型,请检查后重试',
|
||||
modelPool: '模型池',
|
||||
fallbackModels: '备用模型',
|
||||
addFallbackModel: '新增备用模型',
|
||||
fallbackModelsEmpty: '尚未配置备用模型',
|
||||
modelInputTypes: '输入类型',
|
||||
reasoning: '推理模型',
|
||||
manualModel: '手动输入模型',
|
||||
@@ -709,6 +712,7 @@ const message = {
|
||||
roleMarkdownRestartHelper: '保存当前全部 MD 文件后,需要重启容器才能生效。请选择立即重启或稍后重启。',
|
||||
configFileRestartHelper: '保存配置文件后需要重启容器才能生效。',
|
||||
overviewSnapshot: '状态概览',
|
||||
primaryModel: '主模型',
|
||||
defaultModel: '默认模型',
|
||||
channelCount: '已配置频道数量',
|
||||
skillCount: '技能数量',
|
||||
|
||||
@@ -68,8 +68,6 @@ const loadModel = async () => {
|
||||
await nextTick();
|
||||
await modelRef.value?.load({
|
||||
agentId: agentId.value,
|
||||
accountId: accountId.value,
|
||||
model: model.value,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ const props = defineProps({
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:bots', bots: ChannelBotItem[]): void;
|
||||
(e: 'approve', bot: ChannelBotItem): void;
|
||||
(e: 'save'): void;
|
||||
(e: 'save', action?: 'delete' | 'save'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -306,7 +306,7 @@ const saveBot = async () => {
|
||||
}
|
||||
emitBots(nextBots);
|
||||
dialogVisible.value = false;
|
||||
emit('save');
|
||||
emit('save', 'save');
|
||||
};
|
||||
|
||||
const removeBot = (index: number) => {
|
||||
@@ -316,7 +316,7 @@ const removeBot = (index: number) => {
|
||||
return;
|
||||
}
|
||||
emitBots(nextBots);
|
||||
emit('save');
|
||||
emit('save', 'delete');
|
||||
};
|
||||
|
||||
const setDefault = (accountId: string) => {
|
||||
@@ -325,14 +325,14 @@ const setDefault = (accountId: string) => {
|
||||
isDefault: bot.accountId === accountId,
|
||||
}));
|
||||
emitBots(nextBots);
|
||||
emit('save');
|
||||
emit('save', 'save');
|
||||
};
|
||||
|
||||
const updateEnabled = (index: number, enabled: boolean | string | number) => {
|
||||
const nextBots = props.bots.map((bot) => ({ ...bot }));
|
||||
nextBots[index].enabled = Boolean(enabled);
|
||||
emitBots(nextBots);
|
||||
emit('save');
|
||||
emit('save', 'save');
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ const reload = async () => {
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
const saveChannel = async (action: 'delete' | 'save' = 'save') => {
|
||||
if (!supported.value || !agentId.value || !formRef.value) {
|
||||
return;
|
||||
}
|
||||
@@ -278,7 +278,7 @@ const saveChannel = async () => {
|
||||
ackText: form.ackText,
|
||||
bots: form.bots,
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
MsgSuccess(action === 'delete' ? t('commons.msg.deleteSuccess') : t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ const load = async (id: number) => {
|
||||
form.bots = res.data?.bots || [];
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
const saveChannel = async (action: 'delete' | 'save' = 'save') => {
|
||||
if (!agentId.value || !formRef.value) {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ const saveChannel = async () => {
|
||||
defaultAccount: form.defaultAccount,
|
||||
bots: form.bots,
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
MsgSuccess(action === 'delete' ? t('commons.msg.deleteSuccess') : t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ const validateBots = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
const saveChannel = async (action: 'delete' | 'save' = 'save') => {
|
||||
if (!agentId.value || !formRef.value) {
|
||||
return;
|
||||
}
|
||||
@@ -308,7 +308,7 @@ const saveChannel = async () => {
|
||||
groupAllowFrom: parseTextList(form.groupAllowFromText),
|
||||
bots: buildPayloadBots(),
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
MsgSuccess(action === 'delete' ? t('commons.msg.deleteSuccess') : t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ const reload = async () => {
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
const saveChannel = async (action: 'delete' | 'save' = 'save') => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
@@ -186,7 +186,7 @@ const saveChannel = async () => {
|
||||
systemPrompt: bot.systemPrompt,
|
||||
})),
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
MsgSuccess(action === 'delete' ? t('commons.msg.deleteSuccess') : t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ const load = async (id: number) => {
|
||||
form.bots = res.data?.bots || [];
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
const saveChannel = async (action: 'delete' | 'save' = 'save') => {
|
||||
if (!agentId.value || !formRef.value) {
|
||||
return;
|
||||
}
|
||||
@@ -267,7 +267,7 @@ const saveChannel = async () => {
|
||||
defaultAccount: form.defaultAccount,
|
||||
bots: form.bots,
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
MsgSuccess(action === 'delete' ? t('commons.msg.deleteSuccess') : t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,56 @@
|
||||
<el-option v-for="item in accountOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.model.model')" prop="model">
|
||||
<el-select v-model="form.model" filterable>
|
||||
<el-form-item :label="t('aiTools.agents.primaryModel')" prop="model">
|
||||
<el-select v-model="form.model" filterable @change="handleModelChange">
|
||||
<el-option v-for="item in modelOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<span class="input-help">{{ t('aiTools.agents.accountModelsHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.fallbackModels')">
|
||||
<div class="fallback-editor">
|
||||
<div class="fallback-toolbar">
|
||||
<el-select v-model="fallbackCandidate" filterable clearable class="fallback-select">
|
||||
<el-option
|
||||
v-for="item in fallbackCandidateOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" plain :disabled="!fallbackCandidate" @click="addFallback">
|
||||
{{ t('aiTools.agents.addFallbackModel') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="fallbackRows" size="small">
|
||||
<el-table-column :label="t('aiTools.model.model')" min-width="220">
|
||||
<template #default="{ row }">
|
||||
{{ row.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('commons.table.operate')" width="180" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
link
|
||||
:icon="ArrowUp"
|
||||
:disabled="$index === 0"
|
||||
@click="moveFallback($index, -1)"
|
||||
/>
|
||||
<el-button
|
||||
link
|
||||
:icon="ArrowDown"
|
||||
:disabled="$index === fallbackRows.length - 1"
|
||||
@click="moveFallback($index, 1)"
|
||||
/>
|
||||
<el-button link :icon="Delete" @click="removeFallback($index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<div class="fallback-empty">{{ t('aiTools.agents.fallbackModelsEmpty') }}</div>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveModel">
|
||||
{{ t('commons.button.save') }}
|
||||
@@ -20,11 +64,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import { ArrowDown, ArrowUp, Delete } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { pageAgentAccounts, updateAgentModelConfig } from '@/api/modules/ai';
|
||||
import { getAgentModelConfig, pageAgentAccounts, updateAgentModelConfig } from '@/api/modules/ai';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
@@ -44,13 +89,12 @@ const modelOptions = ref<AI.AgentAccountModel[]>([]);
|
||||
|
||||
interface AgentModelLoadParams {
|
||||
agentId: number;
|
||||
accountId: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
accountId: undefined as unknown as number,
|
||||
model: '',
|
||||
fallbacks: [] as string[],
|
||||
});
|
||||
|
||||
const rules = reactive({
|
||||
@@ -58,45 +102,107 @@ const rules = reactive({
|
||||
model: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const loadAccounts = async () => {
|
||||
const res = await pageAgentAccounts({
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
provider: '',
|
||||
name: '',
|
||||
});
|
||||
const items = res.data.items || [];
|
||||
accountOptions.value = isIntl.value ? items.filter((item) => !blockedProviders.has(item.provider)) : items;
|
||||
};
|
||||
const fallbackCandidate = ref('');
|
||||
|
||||
const fallbackRows = computed(() =>
|
||||
form.fallbacks
|
||||
.map((id) => modelOptions.value.find((item) => item.id === id))
|
||||
.filter((item): item is AI.AgentAccountModel => item !== undefined),
|
||||
);
|
||||
|
||||
const fallbackCandidateOptions = computed(() =>
|
||||
modelOptions.value.filter((item) => item.id !== form.model && !form.fallbacks.includes(item.id)),
|
||||
);
|
||||
|
||||
const setModelOptionsByAccount = (accountId: number) => {
|
||||
const selected = accountOptions.value.find((item) => item.id === accountId);
|
||||
modelOptions.value = selected?.models || [];
|
||||
};
|
||||
|
||||
const ensureSelectedModel = (preferred?: string) => {
|
||||
if (preferred && modelOptions.value.some((item) => item.id === preferred)) {
|
||||
form.model = preferred;
|
||||
return;
|
||||
}
|
||||
if (!modelOptions.value.some((item) => item.id === form.model)) {
|
||||
form.model = modelOptions.value[0]?.id || '';
|
||||
}
|
||||
};
|
||||
|
||||
const syncFallbacks = (fallbacks?: string[]) => {
|
||||
const current = fallbacks || form.fallbacks;
|
||||
form.fallbacks = current.filter(
|
||||
(id, index) =>
|
||||
id !== form.model &&
|
||||
modelOptions.value.some((item) => item.id === id) &&
|
||||
current.findIndex((item) => item === id) === index,
|
||||
);
|
||||
if (!fallbackCandidateOptions.value.some((item) => item.id === fallbackCandidate.value)) {
|
||||
fallbackCandidate.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleAccountChange = () => {
|
||||
setModelOptionsByAccount(form.accountId);
|
||||
ensureSelectedModel();
|
||||
form.fallbacks = [];
|
||||
fallbackCandidate.value = '';
|
||||
};
|
||||
|
||||
const handleModelChange = () => {
|
||||
syncFallbacks();
|
||||
};
|
||||
|
||||
const addFallback = () => {
|
||||
if (!fallbackCandidate.value) {
|
||||
return;
|
||||
}
|
||||
syncFallbacks([...form.fallbacks, fallbackCandidate.value]);
|
||||
};
|
||||
|
||||
const moveFallback = (index: number, offset: number) => {
|
||||
const target = index + offset;
|
||||
if (target < 0 || target >= form.fallbacks.length) {
|
||||
return;
|
||||
}
|
||||
const next = [...form.fallbacks];
|
||||
[next[index], next[target]] = [next[target], next[index]];
|
||||
form.fallbacks = next;
|
||||
};
|
||||
|
||||
const removeFallback = (index: number) => {
|
||||
form.fallbacks = form.fallbacks.filter((_, current) => current !== index);
|
||||
};
|
||||
|
||||
const load = async (params: AgentModelLoadParams) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
agentId.value = params.agentId;
|
||||
await loadAccounts();
|
||||
const [accountsRes, configRes] = await Promise.all([
|
||||
pageAgentAccounts({
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
provider: '',
|
||||
name: '',
|
||||
}),
|
||||
getAgentModelConfig({ agentId: params.agentId }),
|
||||
]);
|
||||
const items = accountsRes.data.items || [];
|
||||
accountOptions.value = isIntl.value ? items.filter((item) => !blockedProviders.has(item.provider)) : items;
|
||||
if (accountOptions.value.length === 0) {
|
||||
form.accountId = undefined as unknown as number;
|
||||
form.model = '';
|
||||
form.fallbacks = [];
|
||||
modelOptions.value = [];
|
||||
return;
|
||||
}
|
||||
const config = configRes.data;
|
||||
const currentAccount =
|
||||
accountOptions.value.find((item) => item.id === params.accountId) || accountOptions.value[0];
|
||||
accountOptions.value.find((item) => item.id === config.accountId) || accountOptions.value[0];
|
||||
form.accountId = currentAccount.id;
|
||||
form.model = params.model || currentAccount.models?.[0]?.id || '';
|
||||
setModelOptionsByAccount(currentAccount.id);
|
||||
ensureSelectedModel(config.model);
|
||||
syncFallbacks(config.fallbacks || []);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -113,6 +219,7 @@ const saveModel = async () => {
|
||||
agentId: agentId.value,
|
||||
accountId: form.accountId,
|
||||
model: form.model,
|
||||
fallbacks: form.fallbacks,
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.switchModelSuccess'));
|
||||
emit('updated');
|
||||
@@ -125,3 +232,24 @@ defineExpose({
|
||||
load,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fallback-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fallback-toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.fallback-select {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.fallback-empty {
|
||||
padding: 12px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user