mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
Add plugin management support to OpenClaw. (#13388)
This commit is contained in:
@@ -1378,6 +1378,88 @@ func (b *BaseApi) UninstallAgentSkill(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List OpenClaw plugins
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginsReq true "request"
|
||||
// @Success 200 {array} dto.AgentPluginItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugins/list [post]
|
||||
func (b *BaseApi) ListAgentPlugins(c *gin.Context) {
|
||||
var req dto.AgentPluginsReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.ListPlugins(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Search OpenClaw plugins
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginSearchReq true "request"
|
||||
// @Success 200 {array} dto.AgentPluginSearchItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugins/search [post]
|
||||
func (b *BaseApi) SearchAgentPlugins(c *gin.Context) {
|
||||
var req dto.AgentPluginSearchReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.SearchPlugins(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Install an OpenClaw marketplace plugin
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginMarketInstallReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugins/install [post]
|
||||
func (b *BaseApi) InstallAgentMarketPlugin(c *gin.Context) {
|
||||
var req dto.AgentPluginMarketInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.InstallMarketPlugin(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Operate an OpenClaw plugin
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginOperateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugins/operate [post]
|
||||
func (b *BaseApi) OperateAgentPlugin(c *gin.Context) {
|
||||
var req dto.AgentPluginOperateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.OperatePlugin(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Login Agent Weixin channel
|
||||
// @Accept json
|
||||
|
||||
@@ -604,6 +604,52 @@ type AgentPluginStatus struct {
|
||||
Upgradable bool `json:"upgradable"`
|
||||
}
|
||||
|
||||
type AgentPluginsReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginSearchReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Keyword string `json:"keyword" validate:"required,max=100"`
|
||||
Limit int `json:"limit" validate:"omitempty,min=1,max=100"`
|
||||
}
|
||||
|
||||
type AgentPluginMarketInstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Package string `json:"package" validate:"required,max=200"`
|
||||
Version string `json:"version" validate:"required,max=100"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginOperateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
PluginID string `json:"pluginId" validate:"required,max=200"`
|
||||
Operate string `json:"operate" validate:"required,oneof=enable disable update uninstall"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Origin string `json:"origin"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type AgentPluginSearchItem struct {
|
||||
Package string `json:"package"`
|
||||
PluginID string `json:"pluginId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Version string `json:"version"`
|
||||
Channel string `json:"channel"`
|
||||
VerificationTier string `json:"verificationTier"`
|
||||
Categories []string `json:"categories"`
|
||||
Official bool `json:"official"`
|
||||
Downloads int64 `json:"downloads"`
|
||||
Score float64 `json:"score"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
@@ -105,6 +105,10 @@ type IAgentService interface {
|
||||
UpgradePlugin(req dto.AgentPluginUpgradeReq) error
|
||||
UninstallPlugin(req dto.AgentPluginUninstallReq) error
|
||||
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
||||
ListPlugins(req dto.AgentPluginsReq) ([]dto.AgentPluginItem, error)
|
||||
SearchPlugins(req dto.AgentPluginSearchReq) ([]dto.AgentPluginSearchItem, error)
|
||||
InstallMarketPlugin(req dto.AgentPluginMarketInstallReq) error
|
||||
OperatePlugin(req dto.AgentPluginOperateReq) error
|
||||
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
)
|
||||
|
||||
var (
|
||||
openclawPluginPackagePattern = regexp.MustCompile(`^(@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$`)
|
||||
openclawPluginVersionPattern = regexp.MustCompile(`^[0-9A-Za-z][0-9A-Za-z._-]*$`)
|
||||
openclawPluginIDPattern = regexp.MustCompile(`^(@[A-Za-z0-9][A-Za-z0-9._-]*/)?[A-Za-z0-9][A-Za-z0-9._-]*$`)
|
||||
)
|
||||
|
||||
type openclawPluginListOutput struct {
|
||||
Plugins []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Origin string `json:"origin"`
|
||||
Enabled bool `json:"enabled"`
|
||||
} `json:"plugins"`
|
||||
}
|
||||
|
||||
type openclawPluginIndexItem struct {
|
||||
PluginID string `json:"pluginId"`
|
||||
PackageName string `json:"packageName"`
|
||||
PackageVersion string `json:"packageVersion"`
|
||||
Origin string `json:"origin"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type openclawPluginSearchOutput struct {
|
||||
Results []struct {
|
||||
Score float64 `json:"score"`
|
||||
Package struct {
|
||||
Name string `json:"name"`
|
||||
RuntimeID string `json:"runtimeId"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Summary string `json:"summary"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
Categories []string `json:"categories"`
|
||||
Channel string `json:"channel"`
|
||||
IsOfficial bool `json:"isOfficial"`
|
||||
VerificationTier string `json:"verificationTier"`
|
||||
Stats struct {
|
||||
Downloads int64 `json:"downloads"`
|
||||
} `json:"stats"`
|
||||
} `json:"package"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
func (a AgentService) ListPlugins(req dto.AgentPluginsReq) ([]dto.AgentPluginItem, error) {
|
||||
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plugins, err := readOpenclawPluginIndex(filepath.Join(filepath.Dir(agent.ConfigPath), "state", "openclaw.sqlite")); err == nil {
|
||||
return plugins, nil
|
||||
}
|
||||
output, err := cmd.RunDockerExecWithStdout(2*time.Minute, install.ContainerName, "openclaw", "plugins", "list", "--json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseOpenclawPluginList([]byte(output))
|
||||
}
|
||||
|
||||
func (a AgentService) SearchPlugins(req dto.AgentPluginSearchReq) ([]dto.AgentPluginSearchItem, error) {
|
||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limit := req.Limit
|
||||
if limit == 0 {
|
||||
limit = 20
|
||||
}
|
||||
output, err := cmd.RunDockerExecWithStdout(
|
||||
2*time.Minute,
|
||||
install.ContainerName,
|
||||
"openclaw", "plugins", "search", strings.TrimSpace(req.Keyword), "--limit", fmt.Sprint(limit), "--json",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseOpenclawPluginSearch([]byte(output))
|
||||
}
|
||||
|
||||
func (a AgentService) InstallMarketPlugin(req dto.AgentPluginMarketInstallReq) error {
|
||||
spec, err := buildOpenclawPluginInstallSpec(req.Package, req.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil {
|
||||
return err
|
||||
}
|
||||
taskName := fmt.Sprintf("%s [%s]", i18n.GetMsgByKey("AgentPluginInstall"), req.Package)
|
||||
installTask, err := task.NewTask(taskName, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask(taskName, func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "plugins", "install", spec)
|
||||
}, nil)
|
||||
addOpenclawPluginRestartTask(installTask, install)
|
||||
go executeAgentPluginTask(installTask)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) OperatePlugin(req dto.AgentPluginOperateReq) error {
|
||||
if !openclawPluginIDPattern.MatchString(req.PluginID) {
|
||||
return buserr.New("ErrInvalidChar")
|
||||
}
|
||||
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operate == "update" || req.Operate == "uninstall" {
|
||||
plugins, err := a.ListPlugins(dto.AgentPluginsReq{AgentID: req.AgentID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, plugin := range plugins {
|
||||
if plugin.ID == req.PluginID && plugin.Origin == "bundled" {
|
||||
return buserr.WithName("ErrNotSupportType", req.Operate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taskType := map[string]string{
|
||||
"enable": task.TaskUpdate,
|
||||
"disable": task.TaskUpdate,
|
||||
"update": task.TaskUpgrade,
|
||||
"uninstall": task.TaskUninstall,
|
||||
}[req.Operate]
|
||||
taskName := fmt.Sprintf("%s [%s]", i18n.GetMsgByKey(map[string]string{
|
||||
"enable": "AgentPluginEnable",
|
||||
"disable": "AgentPluginDisable",
|
||||
"update": "AgentPluginUpdate",
|
||||
"uninstall": "AgentPluginUninstall",
|
||||
}[req.Operate]), req.PluginID)
|
||||
operateTask, err := task.NewTask(taskName, taskType, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
operateTask.AddSubTask(taskName, func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if req.Operate == "uninstall" {
|
||||
if err := uninstallOpenclawPlugin(mgr, install.ContainerName, req.PluginID); err != nil {
|
||||
return err
|
||||
}
|
||||
return cleanupManagedOpenclawPlugin(agent, req.PluginID)
|
||||
}
|
||||
return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "plugins", req.Operate, req.PluginID)
|
||||
}, nil)
|
||||
addOpenclawPluginRestartTask(operateTask, install)
|
||||
go executeAgentPluginTask(operateTask)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseOpenclawPluginList(raw []byte) ([]dto.AgentPluginItem, error) {
|
||||
payload, err := extractEmbeddedJSON(string(raw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return []dto.AgentPluginItem{}, nil
|
||||
}
|
||||
var output openclawPluginListOutput
|
||||
if err := json.Unmarshal(payload, &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]dto.AgentPluginItem, 0, len(output.Plugins))
|
||||
for _, plugin := range output.Plugins {
|
||||
items = append(items, dto.AgentPluginItem{
|
||||
ID: plugin.ID,
|
||||
Name: plugin.Name,
|
||||
Version: plugin.Version,
|
||||
Origin: plugin.Origin,
|
||||
Enabled: plugin.Enabled,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func readOpenclawPluginIndex(dbPath string) ([]dto.AgentPluginItem, error) {
|
||||
db, err := sql.Open("sqlite", "file:"+filepath.ToSlash(dbPath)+"?mode=ro")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var raw []byte
|
||||
if err := db.QueryRow(
|
||||
"SELECT plugins_json FROM installed_plugin_index WHERE index_key = ?",
|
||||
"installed-plugin-index",
|
||||
).Scan(&raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var plugins []openclawPluginIndexItem
|
||||
if err := json.Unmarshal(raw, &plugins); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]dto.AgentPluginItem, 0, len(plugins))
|
||||
for _, plugin := range plugins {
|
||||
name := plugin.PackageName
|
||||
if name == "" {
|
||||
name = plugin.PluginID
|
||||
}
|
||||
items = append(items, dto.AgentPluginItem{
|
||||
ID: plugin.PluginID,
|
||||
Name: name,
|
||||
Version: plugin.PackageVersion,
|
||||
Origin: plugin.Origin,
|
||||
Enabled: plugin.Enabled,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func parseOpenclawPluginSearch(raw []byte) ([]dto.AgentPluginSearchItem, error) {
|
||||
payload, err := extractEmbeddedJSON(string(raw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return []dto.AgentPluginSearchItem{}, nil
|
||||
}
|
||||
var output openclawPluginSearchOutput
|
||||
if err := json.Unmarshal(payload, &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]dto.AgentPluginSearchItem, 0, len(output.Results))
|
||||
for _, result := range output.Results {
|
||||
items = append(items, dto.AgentPluginSearchItem{
|
||||
Package: result.Package.Name,
|
||||
PluginID: result.Package.RuntimeID,
|
||||
Name: result.Package.DisplayName,
|
||||
Description: result.Package.Summary,
|
||||
Version: result.Package.LatestVersion,
|
||||
Channel: result.Package.Channel,
|
||||
VerificationTier: result.Package.VerificationTier,
|
||||
Categories: append([]string{}, result.Package.Categories...),
|
||||
Official: result.Package.IsOfficial,
|
||||
Downloads: result.Package.Stats.Downloads,
|
||||
Score: result.Score,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func buildOpenclawPluginInstallSpec(packageName, version string) (string, error) {
|
||||
packageName = strings.TrimSpace(packageName)
|
||||
version = strings.TrimSpace(version)
|
||||
if !openclawPluginPackagePattern.MatchString(packageName) || !openclawPluginVersionPattern.MatchString(version) {
|
||||
return "", buserr.New("ErrInvalidChar")
|
||||
}
|
||||
return "clawhub:" + packageName + "@" + version, nil
|
||||
}
|
||||
|
||||
func cleanupManagedOpenclawPlugin(agent *model.Agent, pluginID string) error {
|
||||
pluginType := map[string]string{
|
||||
"openclaw-lark": "feishu",
|
||||
"openclaw-qqbot": "qqbot",
|
||||
"wecom-openclaw-plugin": "wecom",
|
||||
"dingtalk-connector": "dingtalk",
|
||||
"openclaw-weixin": "weixin",
|
||||
}[pluginID]
|
||||
if pluginType == "" {
|
||||
return nil
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanupOpenclawPluginConfig(conf, pluginType)
|
||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
||||
}
|
||||
|
||||
func addOpenclawPluginRestartTask(t *task.Task, install *model.AppInstall) {
|
||||
t.AddSubTask(task.GetTaskName("OpenClaw", task.TaskRestart, task.TaskScopeAI), func(t *task.Task) error {
|
||||
output, err := compose.Restart(install.GetComposePath())
|
||||
if output != "" {
|
||||
t.Log(output)
|
||||
}
|
||||
return err
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func executeAgentPluginTask(t *task.Task) {
|
||||
if err := t.Execute(); err != nil {
|
||||
global.LOG.Errorf("operate openclaw plugin failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Create'
|
||||
TaskDelete: 'Delete'
|
||||
TaskUpgrade: 'Upgrade'
|
||||
TaskUpdate: 'Update'
|
||||
AgentPluginInstall: 'Install plugin'
|
||||
AgentPluginEnable: 'Enable plugin'
|
||||
AgentPluginDisable: 'Disable plugin'
|
||||
AgentPluginUpdate: 'Update plugin'
|
||||
AgentPluginUninstall: 'Uninstall plugin'
|
||||
TaskRestart: 'Restart'
|
||||
TaskProtect: "Protect"
|
||||
TaskBackup: 'Backup'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Crear'
|
||||
TaskDelete: 'Eliminar'
|
||||
TaskUpgrade: 'Actualizar'
|
||||
TaskUpdate: 'Actualizar'
|
||||
AgentPluginInstall: 'Instalar complemento'
|
||||
AgentPluginEnable: 'Activar complemento'
|
||||
AgentPluginDisable: 'Desactivar complemento'
|
||||
AgentPluginUpdate: 'Actualizar complemento'
|
||||
AgentPluginUninstall: 'Desinstalar complemento'
|
||||
TaskRestart: 'Reiniciar'
|
||||
TaskProtect: 'Proteger'
|
||||
TaskBackup: 'Respaldar'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'ایجاد'
|
||||
TaskDelete: 'حذف'
|
||||
TaskUpgrade: 'ارتقاء'
|
||||
TaskUpdate: 'بهروزرسانی'
|
||||
AgentPluginInstall: 'نصب افزونه'
|
||||
AgentPluginEnable: 'فعالکردن افزونه'
|
||||
AgentPluginDisable: 'غیرفعالکردن افزونه'
|
||||
AgentPluginUpdate: 'بهروزرسانی افزونه'
|
||||
AgentPluginUninstall: 'حذف افزونه'
|
||||
TaskRestart: 'راهاندازی مجدد'
|
||||
TaskProtect: "محافظت"
|
||||
TaskBackup: 'پشتیبانگیری'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: '作成'
|
||||
TaskDelete: '削除'
|
||||
TaskUpgrade: 'アップグレード'
|
||||
TaskUpdate: '更新'
|
||||
AgentPluginInstall: 'プラグインをインストール'
|
||||
AgentPluginEnable: 'プラグインを有効化'
|
||||
AgentPluginDisable: 'プラグインを無効化'
|
||||
AgentPluginUpdate: 'プラグインを更新'
|
||||
AgentPluginUninstall: 'プラグインをアンインストール'
|
||||
TaskRestart: '再起動'
|
||||
TaskProtect: '保護'
|
||||
TaskBackup: 'バックアップ'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: '생성'
|
||||
TaskDelete: '삭제'
|
||||
TaskUpgrade: '업그레이드'
|
||||
TaskUpdate: '업데이트'
|
||||
AgentPluginInstall: '플러그인 설치'
|
||||
AgentPluginEnable: '플러그인 활성화'
|
||||
AgentPluginDisable: '플러그인 비활성화'
|
||||
AgentPluginUpdate: '플러그인 업데이트'
|
||||
AgentPluginUninstall: '플러그인 제거'
|
||||
TaskRestart: '다시 시작'
|
||||
TaskProtect: '보호'
|
||||
TaskBackup: '백업'
|
||||
|
||||
@@ -439,6 +439,11 @@ TaskCreate: 'ສ້າງ'
|
||||
TaskDelete: 'ລຶບ'
|
||||
TaskUpgrade: 'ອັບເກຣດ'
|
||||
TaskUpdate: 'ອັບເດດ'
|
||||
AgentPluginInstall: 'ຕິດຕັ້ງປລັກອິນ'
|
||||
AgentPluginEnable: 'ເປີດໃຊ້ປລັກອິນ'
|
||||
AgentPluginDisable: 'ປິດໃຊ້ປລັກອິນ'
|
||||
AgentPluginUpdate: 'ອັບເດດປລັກອິນ'
|
||||
AgentPluginUninstall: 'ຖອນການຕິດຕັ້ງປລັກອິນ'
|
||||
TaskRestart: 'ເລີ່ມໃໝ່'
|
||||
TaskProtect: "ປ້ອງກັນ"
|
||||
TaskBackup: 'ສຳຮອງຂໍ້ມູນ'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Buat'
|
||||
TaskDelete: 'Padam'
|
||||
TaskUpgrade: 'Naik taraf'
|
||||
TaskUpdate: 'Kemas kini'
|
||||
AgentPluginInstall: 'Pasang pemalam'
|
||||
AgentPluginEnable: 'Aktifkan pemalam'
|
||||
AgentPluginDisable: 'Nyahaktifkan pemalam'
|
||||
AgentPluginUpdate: 'Kemas kini pemalam'
|
||||
AgentPluginUninstall: 'Nyahpasang pemalam'
|
||||
TaskRestart: 'Mulakan semula'
|
||||
TaskProtect: 'Lindungi'
|
||||
TaskBackup: 'Sandaran'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Criar'
|
||||
TaskDelete: 'Excluir'
|
||||
TaskUpgrade: 'Atualizar'
|
||||
TaskUpdate: 'Atualizar'
|
||||
AgentPluginInstall: 'Instalar plugin'
|
||||
AgentPluginEnable: 'Ativar plugin'
|
||||
AgentPluginDisable: 'Desativar plugin'
|
||||
AgentPluginUpdate: 'Atualizar plugin'
|
||||
AgentPluginUninstall: 'Desinstalar plugin'
|
||||
TaskRestart: 'Reiniciar'
|
||||
TaskProtect: 'Proteger'
|
||||
TaskBackup: 'Backup'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Создать'
|
||||
TaskDelete: 'Удалить'
|
||||
TaskUpgrade: 'Обновить'
|
||||
TaskUpdate: 'Обновить'
|
||||
AgentPluginInstall: 'Установить плагин'
|
||||
AgentPluginEnable: 'Включить плагин'
|
||||
AgentPluginDisable: 'Отключить плагин'
|
||||
AgentPluginUpdate: 'Обновить плагин'
|
||||
AgentPluginUninstall: 'Удалить плагин'
|
||||
TaskRestart: 'Перезапуск'
|
||||
TaskProtect: 'Защита'
|
||||
TaskBackup: 'Резервное копирование'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: 'Oluştur'
|
||||
TaskDelete: 'Sil'
|
||||
TaskUpgrade: 'Yükselt'
|
||||
TaskUpdate: 'Güncelle'
|
||||
AgentPluginInstall: 'Eklenti yükle'
|
||||
AgentPluginEnable: 'Eklentiyi etkinleştir'
|
||||
AgentPluginDisable: 'Eklentiyi devre dışı bırak'
|
||||
AgentPluginUpdate: 'Eklentiyi güncelle'
|
||||
AgentPluginUninstall: 'Eklentiyi kaldır'
|
||||
TaskRestart: 'Yeniden Başlat'
|
||||
TaskProtect: 'Koru'
|
||||
TaskBackup: 'Yedekle'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: '建立'
|
||||
TaskDelete: '刪除'
|
||||
TaskUpgrade: '升級'
|
||||
TaskUpdate: '更新'
|
||||
AgentPluginInstall: '安裝外掛程式'
|
||||
AgentPluginEnable: '啟用外掛程式'
|
||||
AgentPluginDisable: '停用外掛程式'
|
||||
AgentPluginUpdate: '更新外掛程式'
|
||||
AgentPluginUninstall: '解除安裝外掛程式'
|
||||
TaskRestart: '重新啟動'
|
||||
TaskProtect: '防護'
|
||||
TaskBackup: '備份'
|
||||
|
||||
@@ -449,6 +449,11 @@ TaskCreate: "创建"
|
||||
TaskDelete: "删除"
|
||||
TaskUpgrade: "升级"
|
||||
TaskUpdate: "更新"
|
||||
AgentPluginInstall: "安装插件"
|
||||
AgentPluginEnable: "启用插件"
|
||||
AgentPluginDisable: "禁用插件"
|
||||
AgentPluginUpdate: "更新插件"
|
||||
AgentPluginUninstall: "卸载插件"
|
||||
TaskRestart: "重启"
|
||||
TaskProtect: "防护"
|
||||
TaskBackup: "备份"
|
||||
|
||||
@@ -104,6 +104,10 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/plugin/upgrade", baseApi.UpgradeAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugin/uninstall", baseApi.UninstallAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugin/check", baseApi.CheckAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugins/list", baseApi.ListAgentPlugins)
|
||||
aiToolsRouter.POST("/agents/plugins/search", baseApi.SearchAgentPlugins)
|
||||
aiToolsRouter.POST("/agents/plugins/install", baseApi.InstallAgentMarketPlugin)
|
||||
aiToolsRouter.POST("/agents/plugins/operate", baseApi.OperateAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/security/get", baseApi.GetAgentSecurityConfig)
|
||||
aiToolsRouter.POST("/agents/security/update", baseApi.UpdateAgentSecurityConfig)
|
||||
aiToolsRouter.POST("/agents/other/get", baseApi.GetAgentOtherConfig)
|
||||
|
||||
@@ -853,6 +853,52 @@ export namespace AI {
|
||||
upgradable: boolean;
|
||||
}
|
||||
|
||||
export interface AgentPluginsReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentPluginSearchReq {
|
||||
agentId: number;
|
||||
keyword: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface AgentPluginMarketInstallReq {
|
||||
agentId: number;
|
||||
package: string;
|
||||
version: string;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginOperateReq {
|
||||
agentId: number;
|
||||
pluginId: string;
|
||||
operate: 'enable' | 'disable' | 'update' | 'uninstall';
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginItem {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
origin: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface AgentPluginSearchItem {
|
||||
package: string;
|
||||
pluginId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
channel: string;
|
||||
verificationTier: string;
|
||||
categories: string[];
|
||||
official: boolean;
|
||||
downloads: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface AgentDiscordConfigReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
@@ -330,6 +330,22 @@ export const checkAgentPlugin = (req: AI.AgentPluginCheckReq) => {
|
||||
return http.post<AI.AgentPluginStatus>(`/ai/agents/plugin/check`, req);
|
||||
};
|
||||
|
||||
export const listAgentPlugins = (req: AI.AgentPluginsReq) => {
|
||||
return http.post<AI.AgentPluginItem[]>(`/ai/agents/plugins/list`, req);
|
||||
};
|
||||
|
||||
export const searchAgentPlugins = (req: AI.AgentPluginSearchReq) => {
|
||||
return http.post<AI.AgentPluginSearchItem[]>(`/ai/agents/plugins/search`, req);
|
||||
};
|
||||
|
||||
export const installAgentMarketPlugin = (req: AI.AgentPluginMarketInstallReq) => {
|
||||
return http.post(`/ai/agents/plugins/install`, req);
|
||||
};
|
||||
|
||||
export const operateAgentPlugin = (req: AI.AgentPluginOperateReq) => {
|
||||
return http.post(`/ai/agents/plugins/operate`, req);
|
||||
};
|
||||
|
||||
export const getAgentSecurityConfig = (req: AI.AgentSecurityConfigReq) => {
|
||||
return http.post<AI.AgentSecurityConfig>(`/ai/agents/security/get`, req);
|
||||
};
|
||||
|
||||
@@ -753,6 +753,30 @@ const message = {
|
||||
verified: 'Verified',
|
||||
verifySkipped: 'No verification',
|
||||
skillsTab: 'Skills',
|
||||
pluginsTab: 'Plugins',
|
||||
pluginsInstalled: 'Installed',
|
||||
pluginsMarket: 'Plugin Market',
|
||||
pluginsMarketHint: 'Enter a keyword to search for plugins',
|
||||
pluginSearchPlaceholder: 'Search plugins...',
|
||||
pluginOrigin: 'Source',
|
||||
pluginOriginAll: 'All Sources',
|
||||
pluginOriginBundled: 'Bundled',
|
||||
pluginOriginExternal: 'External',
|
||||
pluginStatusAll: 'All Statuses',
|
||||
pluginEnabled: 'Enabled',
|
||||
pluginDisabled: 'Disabled',
|
||||
pluginCategory: 'Category',
|
||||
pluginVerification: 'Verification',
|
||||
pluginOfficial: 'Official',
|
||||
pluginCommunity: 'Community',
|
||||
pluginDownloads: 'Downloads',
|
||||
pluginEnable: 'Enable',
|
||||
pluginDisable: 'Disable',
|
||||
pluginListEmpty: 'No plugins',
|
||||
pluginSearchEmpty: 'No matching plugins found',
|
||||
pluginInstallConfirm: 'Install plugin {0}?',
|
||||
pluginDisableConfirm: 'Disable plugin {0}?',
|
||||
pluginUninstallConfirm: 'Uninstall plugin {0}?',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
timeZone: 'Time Zone',
|
||||
|
||||
@@ -754,6 +754,30 @@ const message = {
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sin verificación',
|
||||
skillsTab: 'Habilidades',
|
||||
pluginsTab: 'Complementos',
|
||||
pluginsInstalled: 'Instalados',
|
||||
pluginsMarket: 'Mercado de complementos',
|
||||
pluginsMarketHint: 'Introduce una palabra clave para buscar complementos',
|
||||
pluginSearchPlaceholder: 'Buscar complementos...',
|
||||
pluginOrigin: 'Origen',
|
||||
pluginOriginAll: 'Todos los orígenes',
|
||||
pluginOriginBundled: 'Integrado',
|
||||
pluginOriginExternal: 'Externo',
|
||||
pluginStatusAll: 'Todos los estados',
|
||||
pluginEnabled: 'Activado',
|
||||
pluginDisabled: 'Desactivado',
|
||||
pluginCategory: 'Categoría',
|
||||
pluginVerification: 'Verificación',
|
||||
pluginOfficial: 'Oficial',
|
||||
pluginCommunity: 'Comunidad',
|
||||
pluginDownloads: 'Descargas',
|
||||
pluginEnable: 'Activar',
|
||||
pluginDisable: 'Desactivar',
|
||||
pluginListEmpty: 'No hay complementos',
|
||||
pluginSearchEmpty: 'No se encontraron complementos coincidentes',
|
||||
pluginInstallConfirm: '¿Instalar el complemento {0}?',
|
||||
pluginDisableConfirm: '¿Desactivar el complemento {0}?',
|
||||
pluginUninstallConfirm: '¿Desinstalar el complemento {0}?',
|
||||
securityTab: 'Seguridad',
|
||||
otherTab: 'Otros',
|
||||
timeZone: 'Zona horaria',
|
||||
|
||||
@@ -741,6 +741,30 @@ const message = {
|
||||
verified: 'تأیید شده',
|
||||
verifySkipped: 'بدون تأیید',
|
||||
skillsTab: 'مهارتها',
|
||||
pluginsTab: 'افزونهها',
|
||||
pluginsInstalled: 'نصبشده',
|
||||
pluginsMarket: 'بازار افزونهها',
|
||||
pluginsMarketHint: 'برای جستجوی افزونه یک کلیدواژه وارد کنید',
|
||||
pluginSearchPlaceholder: 'جستجوی افزونهها...',
|
||||
pluginOrigin: 'منبع',
|
||||
pluginOriginAll: 'همه منابع',
|
||||
pluginOriginBundled: 'داخلی',
|
||||
pluginOriginExternal: 'خارجی',
|
||||
pluginStatusAll: 'همه وضعیتها',
|
||||
pluginEnabled: 'فعال',
|
||||
pluginDisabled: 'غیرفعال',
|
||||
pluginCategory: 'دستهبندی',
|
||||
pluginVerification: 'اعتبارسنجی',
|
||||
pluginOfficial: 'رسمی',
|
||||
pluginCommunity: 'جامعه',
|
||||
pluginDownloads: 'دانلودها',
|
||||
pluginEnable: 'فعالکردن',
|
||||
pluginDisable: 'غیرفعالکردن',
|
||||
pluginListEmpty: 'افزونهای وجود ندارد',
|
||||
pluginSearchEmpty: 'افزونه مرتبطی پیدا نشد',
|
||||
pluginInstallConfirm: 'افزونه {0} نصب شود؟',
|
||||
pluginDisableConfirm: 'افزونه {0} غیرفعال شود؟',
|
||||
pluginUninstallConfirm: 'افزونه {0} حذف شود؟',
|
||||
securityTab: 'امنیت',
|
||||
otherTab: 'سایر',
|
||||
timeZone: 'منطقه زمانی',
|
||||
|
||||
@@ -747,6 +747,30 @@ const message = {
|
||||
verified: '検証済み',
|
||||
verifySkipped: '検証なし',
|
||||
skillsTab: '技能',
|
||||
pluginsTab: 'プラグイン',
|
||||
pluginsInstalled: 'インストール済み',
|
||||
pluginsMarket: 'プラグイン市場',
|
||||
pluginsMarketHint: 'キーワードを入力してプラグインを検索してください',
|
||||
pluginSearchPlaceholder: 'プラグインを検索...',
|
||||
pluginOrigin: '提供元',
|
||||
pluginOriginAll: 'すべての提供元',
|
||||
pluginOriginBundled: '組み込み',
|
||||
pluginOriginExternal: '外部',
|
||||
pluginStatusAll: 'すべての状態',
|
||||
pluginEnabled: '有効',
|
||||
pluginDisabled: '無効',
|
||||
pluginCategory: 'カテゴリ',
|
||||
pluginVerification: '検証',
|
||||
pluginOfficial: '公式',
|
||||
pluginCommunity: 'コミュニティ',
|
||||
pluginDownloads: 'ダウンロード数',
|
||||
pluginEnable: '有効化',
|
||||
pluginDisable: '無効化',
|
||||
pluginListEmpty: 'プラグインがありません',
|
||||
pluginSearchEmpty: '一致するプラグインが見つかりません',
|
||||
pluginInstallConfirm: 'プラグイン {0} をインストールしますか?',
|
||||
pluginDisableConfirm: 'プラグイン {0} を無効にしますか?',
|
||||
pluginUninstallConfirm: 'プラグイン {0} をアンインストールしますか?',
|
||||
securityTab: 'セキュリティ',
|
||||
otherTab: 'その他',
|
||||
timeZone: 'タイムゾーン',
|
||||
|
||||
@@ -738,6 +738,30 @@ const message = {
|
||||
verified: '검증됨',
|
||||
verifySkipped: '검증 안 함',
|
||||
skillsTab: '기술',
|
||||
pluginsTab: '플러그인',
|
||||
pluginsInstalled: '설치됨',
|
||||
pluginsMarket: '플러그인 마켓',
|
||||
pluginsMarketHint: '키워드를 입력하여 플러그인을 검색하세요',
|
||||
pluginSearchPlaceholder: '플러그인 검색...',
|
||||
pluginOrigin: '출처',
|
||||
pluginOriginAll: '모든 출처',
|
||||
pluginOriginBundled: '내장',
|
||||
pluginOriginExternal: '외부',
|
||||
pluginStatusAll: '모든 상태',
|
||||
pluginEnabled: '활성화됨',
|
||||
pluginDisabled: '비활성화됨',
|
||||
pluginCategory: '분류',
|
||||
pluginVerification: '검증',
|
||||
pluginOfficial: '공식',
|
||||
pluginCommunity: '커뮤니티',
|
||||
pluginDownloads: '다운로드',
|
||||
pluginEnable: '활성화',
|
||||
pluginDisable: '비활성화',
|
||||
pluginListEmpty: '플러그인이 없습니다',
|
||||
pluginSearchEmpty: '일치하는 플러그인을 찾을 수 없습니다',
|
||||
pluginInstallConfirm: '플러그인 {0}을(를) 설치하시겠습니까?',
|
||||
pluginDisableConfirm: '플러그인 {0}을(를) 비활성화하시겠습니까?',
|
||||
pluginUninstallConfirm: '플러그인 {0}을(를) 제거하시겠습니까?',
|
||||
securityTab: '보안',
|
||||
otherTab: '기타',
|
||||
timeZone: '시간대',
|
||||
|
||||
@@ -747,6 +747,30 @@ const message = {
|
||||
verified: 'ກວດສອບແລ້ວ',
|
||||
verifySkipped: 'ບໍ່ມີການກວດສອບ',
|
||||
skillsTab: 'ທັກສະ',
|
||||
pluginsTab: 'ປລັກອິນ',
|
||||
pluginsInstalled: 'ຕິດຕັ້ງແລ້ວ',
|
||||
pluginsMarket: 'ຕະຫຼາດປລັກອິນ',
|
||||
pluginsMarketHint: 'ປ້ອນຄຳຄົ້ນເພື່ອຊອກຫາປລັກອິນ',
|
||||
pluginSearchPlaceholder: 'ຊອກຫາປລັກອິນ...',
|
||||
pluginOrigin: 'ແຫຼ່ງທີ່ມາ',
|
||||
pluginOriginAll: 'ທຸກແຫຼ່ງທີ່ມາ',
|
||||
pluginOriginBundled: 'ມາພ້ອມລະບົບ',
|
||||
pluginOriginExternal: 'ພາຍນອກ',
|
||||
pluginStatusAll: 'ທຸກສະຖານະ',
|
||||
pluginEnabled: 'ເປີດໃຊ້ແລ້ວ',
|
||||
pluginDisabled: 'ປິດໃຊ້ແລ້ວ',
|
||||
pluginCategory: 'ໝວດໝູ່',
|
||||
pluginVerification: 'ການຢືນຢັນ',
|
||||
pluginOfficial: 'ທາງການ',
|
||||
pluginCommunity: 'ຊຸມຊົນ',
|
||||
pluginDownloads: 'ດາວໂຫຼດ',
|
||||
pluginEnable: 'ເປີດໃຊ້',
|
||||
pluginDisable: 'ປິດໃຊ້',
|
||||
pluginListEmpty: 'ບໍ່ມີປລັກອິນ',
|
||||
pluginSearchEmpty: 'ບໍ່ພົບປລັກອິນທີ່ກົງກັນ',
|
||||
pluginInstallConfirm: 'ຢືນຢັນຕິດຕັ້ງປລັກອິນ {0} ບໍ?',
|
||||
pluginDisableConfirm: 'ຢືນຢັນປິດໃຊ້ປລັກອິນ {0} ບໍ?',
|
||||
pluginUninstallConfirm: 'ຢືນຢັນຖອນການຕິດຕັ້ງປລັກອິນ {0} ບໍ?',
|
||||
securityTab: 'ຄວາມປອດໄພ',
|
||||
otherTab: 'ອື່ນໆ',
|
||||
timeZone: 'ເຂດເວລາ',
|
||||
|
||||
@@ -755,6 +755,30 @@ const message = {
|
||||
verified: 'Disahkan',
|
||||
verifySkipped: 'Tanpa pengesahan',
|
||||
skillsTab: 'Kemahiran',
|
||||
pluginsTab: 'Pemalam',
|
||||
pluginsInstalled: 'Dipasang',
|
||||
pluginsMarket: 'Pasaran pemalam',
|
||||
pluginsMarketHint: 'Masukkan kata kunci untuk mencari pemalam',
|
||||
pluginSearchPlaceholder: 'Cari pemalam...',
|
||||
pluginOrigin: 'Sumber',
|
||||
pluginOriginAll: 'Semua sumber',
|
||||
pluginOriginBundled: 'Terbina dalam',
|
||||
pluginOriginExternal: 'Luaran',
|
||||
pluginStatusAll: 'Semua status',
|
||||
pluginEnabled: 'Diaktifkan',
|
||||
pluginDisabled: 'Dinyahaktifkan',
|
||||
pluginCategory: 'Kategori',
|
||||
pluginVerification: 'Pengesahan',
|
||||
pluginOfficial: 'Rasmi',
|
||||
pluginCommunity: 'Komuniti',
|
||||
pluginDownloads: 'Muat turun',
|
||||
pluginEnable: 'Aktifkan',
|
||||
pluginDisable: 'Nyahaktifkan',
|
||||
pluginListEmpty: 'Tiada pemalam',
|
||||
pluginSearchEmpty: 'Tiada pemalam sepadan ditemui',
|
||||
pluginInstallConfirm: 'Pasang pemalam {0}?',
|
||||
pluginDisableConfirm: 'Nyahaktifkan pemalam {0}?',
|
||||
pluginUninstallConfirm: 'Nyahpasang pemalam {0}?',
|
||||
securityTab: 'Keselamatan',
|
||||
otherTab: 'Lain-lain',
|
||||
timeZone: 'Zon Waktu',
|
||||
|
||||
@@ -749,6 +749,30 @@ const message = {
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sem verificação',
|
||||
skillsTab: 'Habilidades',
|
||||
pluginsTab: 'Plugins',
|
||||
pluginsInstalled: 'Instalados',
|
||||
pluginsMarket: 'Mercado de plugins',
|
||||
pluginsMarketHint: 'Digite uma palavra-chave para pesquisar plugins',
|
||||
pluginSearchPlaceholder: 'Pesquisar plugins...',
|
||||
pluginOrigin: 'Origem',
|
||||
pluginOriginAll: 'Todas as origens',
|
||||
pluginOriginBundled: 'Integrado',
|
||||
pluginOriginExternal: 'Externo',
|
||||
pluginStatusAll: 'Todos os estados',
|
||||
pluginEnabled: 'Ativado',
|
||||
pluginDisabled: 'Desativado',
|
||||
pluginCategory: 'Categoria',
|
||||
pluginVerification: 'Verificação',
|
||||
pluginOfficial: 'Oficial',
|
||||
pluginCommunity: 'Comunidade',
|
||||
pluginDownloads: 'Downloads',
|
||||
pluginEnable: 'Ativar',
|
||||
pluginDisable: 'Desativar',
|
||||
pluginListEmpty: 'Nenhum plugin',
|
||||
pluginSearchEmpty: 'Nenhum plugin correspondente encontrado',
|
||||
pluginInstallConfirm: 'Instalar o plugin {0}?',
|
||||
pluginDisableConfirm: 'Desativar o plugin {0}?',
|
||||
pluginUninstallConfirm: 'Desinstalar o plugin {0}?',
|
||||
securityTab: 'Segurança',
|
||||
otherTab: 'Outros',
|
||||
timeZone: 'Fuso horário',
|
||||
|
||||
@@ -745,6 +745,30 @@ const message = {
|
||||
verified: 'Проверено',
|
||||
verifySkipped: 'Без проверки',
|
||||
skillsTab: 'Навыки',
|
||||
pluginsTab: 'Плагины',
|
||||
pluginsInstalled: 'Установленные',
|
||||
pluginsMarket: 'Каталог плагинов',
|
||||
pluginsMarketHint: 'Введите ключевое слово для поиска плагинов',
|
||||
pluginSearchPlaceholder: 'Поиск плагинов...',
|
||||
pluginOrigin: 'Источник',
|
||||
pluginOriginAll: 'Все источники',
|
||||
pluginOriginBundled: 'Встроенный',
|
||||
pluginOriginExternal: 'Внешний',
|
||||
pluginStatusAll: 'Все статусы',
|
||||
pluginEnabled: 'Включён',
|
||||
pluginDisabled: 'Отключён',
|
||||
pluginCategory: 'Категория',
|
||||
pluginVerification: 'Проверка',
|
||||
pluginOfficial: 'Официальный',
|
||||
pluginCommunity: 'Сообщество',
|
||||
pluginDownloads: 'Загрузки',
|
||||
pluginEnable: 'Включить',
|
||||
pluginDisable: 'Отключить',
|
||||
pluginListEmpty: 'Плагины отсутствуют',
|
||||
pluginSearchEmpty: 'Подходящие плагины не найдены',
|
||||
pluginInstallConfirm: 'Установить плагин {0}?',
|
||||
pluginDisableConfirm: 'Отключить плагин {0}?',
|
||||
pluginUninstallConfirm: 'Удалить плагин {0}?',
|
||||
securityTab: 'Безопасность',
|
||||
otherTab: 'Другое',
|
||||
timeZone: 'Часовой пояс',
|
||||
|
||||
@@ -751,6 +751,30 @@ const message = {
|
||||
verified: 'Doğrulandı',
|
||||
verifySkipped: 'Doğrulama yok',
|
||||
skillsTab: 'Yetenekler',
|
||||
pluginsTab: 'Eklentiler',
|
||||
pluginsInstalled: 'Yüklü',
|
||||
pluginsMarket: 'Eklenti mağazası',
|
||||
pluginsMarketHint: 'Eklenti aramak için bir anahtar kelime girin',
|
||||
pluginSearchPlaceholder: 'Eklenti ara...',
|
||||
pluginOrigin: 'Kaynak',
|
||||
pluginOriginAll: 'Tüm kaynaklar',
|
||||
pluginOriginBundled: 'Yerleşik',
|
||||
pluginOriginExternal: 'Harici',
|
||||
pluginStatusAll: 'Tüm durumlar',
|
||||
pluginEnabled: 'Etkin',
|
||||
pluginDisabled: 'Devre dışı',
|
||||
pluginCategory: 'Kategori',
|
||||
pluginVerification: 'Doğrulama',
|
||||
pluginOfficial: 'Resmî',
|
||||
pluginCommunity: 'Topluluk',
|
||||
pluginDownloads: 'İndirmeler',
|
||||
pluginEnable: 'Etkinleştir',
|
||||
pluginDisable: 'Devre dışı bırak',
|
||||
pluginListEmpty: 'Eklenti yok',
|
||||
pluginSearchEmpty: 'Eşleşen eklenti bulunamadı',
|
||||
pluginInstallConfirm: '{0} eklentisi yüklensin mi?',
|
||||
pluginDisableConfirm: '{0} eklentisi devre dışı bırakılsın mı?',
|
||||
pluginUninstallConfirm: '{0} eklentisi kaldırılsın mı?',
|
||||
securityTab: 'Güvenlik',
|
||||
otherTab: 'Diğer',
|
||||
timeZone: 'Saat Dilimi',
|
||||
|
||||
@@ -714,6 +714,30 @@ const message = {
|
||||
verified: '驗證狀態',
|
||||
verifySkipped: '不驗證',
|
||||
skillsTab: '技能',
|
||||
pluginsTab: '外掛程式',
|
||||
pluginsInstalled: '已安裝',
|
||||
pluginsMarket: '外掛程式市集',
|
||||
pluginsMarketHint: '請輸入關鍵字搜尋外掛程式',
|
||||
pluginSearchPlaceholder: '搜尋外掛程式...',
|
||||
pluginOrigin: '來源',
|
||||
pluginOriginAll: '全部來源',
|
||||
pluginOriginBundled: '內建',
|
||||
pluginOriginExternal: '外部',
|
||||
pluginStatusAll: '全部狀態',
|
||||
pluginEnabled: '已啟用',
|
||||
pluginDisabled: '已停用',
|
||||
pluginCategory: '分類',
|
||||
pluginVerification: '驗證',
|
||||
pluginOfficial: '官方',
|
||||
pluginCommunity: '社群',
|
||||
pluginDownloads: '下載量',
|
||||
pluginEnable: '啟用',
|
||||
pluginDisable: '停用',
|
||||
pluginListEmpty: '暫無外掛程式',
|
||||
pluginSearchEmpty: '找不到相關外掛程式',
|
||||
pluginInstallConfirm: '確認安裝外掛程式 {0}?',
|
||||
pluginDisableConfirm: '確認停用外掛程式 {0}?',
|
||||
pluginUninstallConfirm: '確認解除安裝外掛程式 {0}?',
|
||||
securityTab: '安全',
|
||||
otherTab: '其他',
|
||||
timeZone: '時區',
|
||||
|
||||
@@ -717,6 +717,30 @@ const message = {
|
||||
verified: '验证状态',
|
||||
verifySkipped: '不验证',
|
||||
skillsTab: '技能',
|
||||
pluginsTab: '插件',
|
||||
pluginsInstalled: '已安装',
|
||||
pluginsMarket: '插件市场',
|
||||
pluginsMarketHint: '请输入关键词搜索插件',
|
||||
pluginSearchPlaceholder: '搜索插件...',
|
||||
pluginOrigin: '来源',
|
||||
pluginOriginAll: '全部来源',
|
||||
pluginOriginBundled: '内置',
|
||||
pluginOriginExternal: '外部',
|
||||
pluginStatusAll: '全部状态',
|
||||
pluginEnabled: '已启用',
|
||||
pluginDisabled: '已禁用',
|
||||
pluginCategory: '分类',
|
||||
pluginVerification: '验证',
|
||||
pluginOfficial: '官方',
|
||||
pluginCommunity: '社区',
|
||||
pluginDownloads: '下载量',
|
||||
pluginEnable: '启用',
|
||||
pluginDisable: '禁用',
|
||||
pluginListEmpty: '暂无插件',
|
||||
pluginSearchEmpty: '未找到相关插件',
|
||||
pluginInstallConfirm: '确认安装插件 {0}?',
|
||||
pluginDisableConfirm: '确认禁用插件 {0}?',
|
||||
pluginUninstallConfirm: '确认卸载插件 {0}?',
|
||||
securityTab: '安全',
|
||||
otherTab: '其他',
|
||||
timeZone: '时区',
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
>
|
||||
<SkillsTab ref="skillsRef" :app-version="appVersion" :agent-type="agentType" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-if="agentType === 'openclaw'" :label="t('aiTools.agents.pluginsTab')" name="plugins">
|
||||
<PluginsTab ref="pluginsRef" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-if="agentType === 'openclaw' || agentType === 'hermes-agent'"
|
||||
:label="t('file.setting')"
|
||||
@@ -43,6 +46,7 @@ import ChannelsTab from './tabs/channels.vue';
|
||||
import ModelTab from './tabs/model.vue';
|
||||
import AgentTab from './tabs/agents/index.vue';
|
||||
import SkillsTab from './tabs/skills.vue';
|
||||
import PluginsTab from './tabs/plugins.vue';
|
||||
import SettingsTab from './tabs/settings.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -60,6 +64,7 @@ const channelsRef = ref();
|
||||
const modelRef = ref();
|
||||
const agentRef = ref();
|
||||
const skillsRef = ref();
|
||||
const pluginsRef = ref();
|
||||
const settingsRef = ref();
|
||||
|
||||
const loadSettings = async () => {
|
||||
@@ -115,6 +120,14 @@ const loadSkills = async () => {
|
||||
await skillsRef.value?.load(agentId.value);
|
||||
};
|
||||
|
||||
const loadPlugins = async () => {
|
||||
if (agentId.value <= 0) {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
await pluginsRef.value?.load(agentId.value);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
activeTab.value = 'channels';
|
||||
};
|
||||
@@ -129,6 +142,9 @@ const handleTabClick = async (pane: TabsPaneContext) => {
|
||||
if (pane.paneName === 'skills') {
|
||||
await loadSkills();
|
||||
}
|
||||
if (pane.paneName === 'plugins') {
|
||||
await loadPlugins();
|
||||
}
|
||||
if (pane.paneName === 'agent') {
|
||||
await loadAgent();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<el-radio-group v-model="mode" class="view-switch">
|
||||
<el-radio-button label="installed">{{ t('aiTools.agents.pluginsInstalled') }}</el-radio-button>
|
||||
<el-radio-button label="market">{{ t('aiTools.agents.pluginsMarket') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<div v-loading="loading" class="plugin-content">
|
||||
<div class="toolbar">
|
||||
<template v-if="mode === 'installed'">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
:placeholder="t('aiTools.agents.pluginSearchPlaceholder')"
|
||||
clearable
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select v-model="origin" class="filter-select">
|
||||
<el-option :label="t('aiTools.agents.pluginOriginAll')" value="" />
|
||||
<el-option :label="t('aiTools.agents.pluginOriginBundled')" value="bundled" />
|
||||
<el-option :label="t('aiTools.agents.pluginOriginExternal')" value="external" />
|
||||
</el-select>
|
||||
<el-select v-model="status" class="filter-select">
|
||||
<el-option :label="t('aiTools.agents.pluginStatusAll')" value="" />
|
||||
<el-option :label="t('aiTools.agents.pluginEnabled')" value="enabled" />
|
||||
<el-option :label="t('aiTools.agents.pluginDisabled')" value="disabled" />
|
||||
</el-select>
|
||||
<el-button :loading="loading" @click="loadPlugins">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input
|
||||
v-model="marketKeyword"
|
||||
:placeholder="t('aiTools.agents.pluginSearchPlaceholder')"
|
||||
clearable
|
||||
class="search-input"
|
||||
@keyup.enter="searchMarket"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button type="primary" :loading="searching" @click="searchMarket">
|
||||
{{ t('commons.button.search') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<el-table v-if="mode === 'installed'" :data="pagedPlugins">
|
||||
<el-table-column :label="t('commons.table.name')" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div class="plugin-name">{{ row.name || row.id }}</div>
|
||||
<div class="secondary">{{ row.id }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('app.version')" min-width="110">
|
||||
<template #default="{ row }">{{ row.version || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('aiTools.agents.pluginOrigin')" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" effect="plain">
|
||||
{{
|
||||
row.origin === 'bundled'
|
||||
? t('aiTools.agents.pluginOriginBundled')
|
||||
: t('aiTools.agents.pluginOriginExternal')
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('commons.table.status')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">
|
||||
{{ row.enabled ? t('aiTools.agents.pluginEnabled') : t('aiTools.agents.pluginDisabled') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('commons.table.operate')" fixed="right" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
link
|
||||
:disabled="operating !== ''"
|
||||
@click="operate(row, row.enabled ? 'disable' : 'enable')"
|
||||
>
|
||||
{{ row.enabled ? t('aiTools.agents.pluginDisable') : t('aiTools.agents.pluginEnable') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.origin !== 'bundled'"
|
||||
v-permission
|
||||
type="primary"
|
||||
link
|
||||
:disabled="operating !== ''"
|
||||
@click="operate(row, 'update')"
|
||||
>
|
||||
{{ t('commons.button.upgrade') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.origin !== 'bundled'"
|
||||
v-permission
|
||||
type="danger"
|
||||
link
|
||||
:disabled="operating !== ''"
|
||||
@click="operate(row, 'uninstall')"
|
||||
>
|
||||
{{ t('commons.button.uninstall') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table v-else :data="marketResults">
|
||||
<el-table-column :label="t('commons.table.name')" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="plugin-name">{{ row.name || row.package }}</div>
|
||||
<div class="secondary">{{ row.package }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('app.version')" prop="version" min-width="110" />
|
||||
<el-table-column :label="t('aiTools.agents.pluginCategory')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-space wrap>
|
||||
<el-tag v-for="item in row.categories" :key="item" size="small" type="info">
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('aiTools.agents.pluginVerification')" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.official ? 'success' : 'info'" size="small" effect="plain">
|
||||
{{ row.official ? t('aiTools.agents.pluginOfficial') : t('aiTools.agents.pluginCommunity') }}
|
||||
</el-tag>
|
||||
<div class="secondary">{{ row.verificationTier || '-' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('aiTools.agents.pluginDownloads')" prop="downloads" min-width="110" />
|
||||
<el-table-column :label="t('commons.table.operate')" fixed="right" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button v-permission type="primary" link :disabled="operating !== ''" @click="install(row)">
|
||||
{{ t('commons.button.install') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty
|
||||
v-if="mode === 'installed' && !loading && filteredPlugins.length === 0"
|
||||
:description="t('aiTools.agents.pluginListEmpty')"
|
||||
/>
|
||||
<el-empty
|
||||
v-if="mode === 'market' && !searching && marketResults.length === 0"
|
||||
:description="
|
||||
marketSearched ? t('aiTools.agents.pluginSearchEmpty') : t('aiTools.agents.pluginsMarketHint')
|
||||
"
|
||||
/>
|
||||
|
||||
<div v-if="mode === 'installed' && filteredPlugins.length > pageSize" class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="filteredPlugins.length"
|
||||
layout="total, prev, pager, next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TaskLog ref="taskLogRef" @close="loadPlugins" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { installAgentMarketPlugin, listAgentPlugins, operateAgentPlugin, searchAgentPlugins } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
|
||||
type PluginMode = 'installed' | 'market';
|
||||
type PluginOperate = AI.AgentPluginOperateReq['operate'];
|
||||
|
||||
const { t } = useI18n();
|
||||
const mode = ref<PluginMode>('installed');
|
||||
const agentId = ref(0);
|
||||
const loading = ref(false);
|
||||
const searching = ref(false);
|
||||
const operating = ref('');
|
||||
const keyword = ref('');
|
||||
const marketKeyword = ref('');
|
||||
const origin = ref('');
|
||||
const status = ref('');
|
||||
const page = ref(1);
|
||||
const pageSize = 10;
|
||||
const plugins = ref<AI.AgentPluginItem[]>([]);
|
||||
const marketResults = ref<AI.AgentPluginSearchItem[]>([]);
|
||||
const marketSearched = ref(false);
|
||||
const taskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||
|
||||
const filteredPlugins = computed(() => {
|
||||
const search = keyword.value.trim().toLowerCase();
|
||||
return plugins.value.filter((plugin) => {
|
||||
const matchesKeyword =
|
||||
!search || plugin.name.toLowerCase().includes(search) || plugin.id.toLowerCase().includes(search);
|
||||
const matchesOrigin =
|
||||
!origin.value ||
|
||||
(origin.value === 'external' ? plugin.origin !== 'bundled' : plugin.origin === origin.value);
|
||||
const matchesStatus = !status.value || (status.value === 'enabled' ? plugin.enabled : !plugin.enabled);
|
||||
return matchesKeyword && matchesOrigin && matchesStatus;
|
||||
});
|
||||
});
|
||||
|
||||
const pagedPlugins = computed(() => {
|
||||
const start = (page.value - 1) * pageSize;
|
||||
return filteredPlugins.value.slice(start, start + pageSize);
|
||||
});
|
||||
|
||||
watch([keyword, origin, status], () => {
|
||||
page.value = 1;
|
||||
});
|
||||
|
||||
async function loadPlugins() {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listAgentPlugins({ agentId: agentId.value });
|
||||
plugins.value = res.data || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
operating.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function searchMarket() {
|
||||
const search = marketKeyword.value.trim();
|
||||
if (!search) {
|
||||
return;
|
||||
}
|
||||
searching.value = true;
|
||||
try {
|
||||
const res = await searchAgentPlugins({ agentId: agentId.value, keyword: search, limit: 20 });
|
||||
marketResults.value = res.data || [];
|
||||
marketSearched.value = true;
|
||||
} finally {
|
||||
searching.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function install(plugin: AI.AgentPluginSearchItem) {
|
||||
await ElMessageBox.confirm(
|
||||
t('aiTools.agents.pluginInstallConfirm', [plugin.name || plugin.package]),
|
||||
t('commons.button.install'),
|
||||
{ type: 'warning' },
|
||||
);
|
||||
const taskID = newUUID();
|
||||
operating.value = plugin.package;
|
||||
try {
|
||||
await installAgentMarketPlugin({
|
||||
agentId: agentId.value,
|
||||
package: plugin.package,
|
||||
version: plugin.version,
|
||||
taskID,
|
||||
});
|
||||
taskLogRef.value?.openWithTaskID(taskID);
|
||||
} finally {
|
||||
operating.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function operate(plugin: AI.AgentPluginItem, action: PluginOperate) {
|
||||
if (action === 'disable' || action === 'uninstall') {
|
||||
const key =
|
||||
action === 'disable' ? 'aiTools.agents.pluginDisableConfirm' : 'aiTools.agents.pluginUninstallConfirm';
|
||||
await ElMessageBox.confirm(t(key, [plugin.name || plugin.id]), t('commons.button.confirm'), {
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
const taskID = newUUID();
|
||||
operating.value = plugin.id;
|
||||
try {
|
||||
await operateAgentPlugin({
|
||||
agentId: agentId.value,
|
||||
pluginId: plugin.id,
|
||||
operate: action,
|
||||
taskID,
|
||||
});
|
||||
taskLogRef.value?.openWithTaskID(taskID);
|
||||
} finally {
|
||||
operating.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function load(id: number) {
|
||||
agentId.value = id;
|
||||
mode.value = 'installed';
|
||||
keyword.value = '';
|
||||
marketKeyword.value = '';
|
||||
origin.value = '';
|
||||
status.value = '';
|
||||
page.value = 1;
|
||||
marketResults.value = [];
|
||||
marketSearched.value = false;
|
||||
await loadPlugins();
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.view-switch {
|
||||
padding-top: 1px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.plugin-content {
|
||||
min-height: 390px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 280px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.plugin-name {
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
margin-top: 2px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user