mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 16:00:51 +00:00
feat: Support uninstallation and upgrades of OpenClaw plugins. (#12373)
This commit is contained in:
@@ -742,6 +742,46 @@ func (b *BaseApi) InstallAgentPlugin(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Upgrade Agent plugin
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginUpgradeReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugin/upgrade [post]
|
||||
func (b *BaseApi) UpgradeAgentPlugin(c *gin.Context) {
|
||||
var req dto.AgentPluginUpgradeReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpgradePlugin(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Uninstall Agent plugin
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentPluginUninstallReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/plugin/uninstall [post]
|
||||
func (b *BaseApi) UninstallAgentPlugin(c *gin.Context) {
|
||||
var req dto.AgentPluginUninstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UninstallPlugin(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Check Agent plugin installation status
|
||||
// @Accept json
|
||||
|
||||
+18
-2
@@ -371,13 +371,29 @@ type AgentPluginInstallReq struct {
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginCheckReq struct {
|
||||
type AgentPluginUpgradeReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu qqbot wecom dingtalk weixin"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginUninstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu qqbot wecom dingtalk weixin"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginCheckReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu qqbot wecom dingtalk weixin"`
|
||||
CheckLatest bool `json:"checkLatest"`
|
||||
}
|
||||
|
||||
type AgentPluginStatus struct {
|
||||
Installed bool `json:"installed"`
|
||||
Installed bool `json:"installed"`
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
Upgradable bool `json:"upgradable"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigUpdateReq struct {
|
||||
|
||||
@@ -76,6 +76,8 @@ type IAgentService interface {
|
||||
GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig, error)
|
||||
UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error
|
||||
InstallPlugin(req dto.AgentPluginInstallReq) error
|
||||
UpgradePlugin(req dto.AgentPluginUpgradeReq) error
|
||||
UninstallPlugin(req dto.AgentPluginUninstallReq) error
|
||||
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
||||
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
||||
}
|
||||
@@ -771,7 +773,7 @@ func (a AgentService) UpdateConfigFile(req dto.AgentConfigFileUpdateReq) error {
|
||||
}
|
||||
|
||||
func getOpenclawNPMRegistry(containerName string) (string, error) {
|
||||
registry, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("docker exec %s npm get registry", 20*time.Second, containerName)
|
||||
registry, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -13,15 +15,20 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
)
|
||||
|
||||
type openclawPluginPackage struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractFeishuConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "feishu")
|
||||
installed, _ := checkPluginInstalled(install.GetPath(), "feishu")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
@@ -89,7 +96,7 @@ func (a AgentService) GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig,
|
||||
return nil, err
|
||||
}
|
||||
result := extractQQBotConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "qqbot")
|
||||
installed, _ := checkPluginInstalled(install.GetPath(), "qqbot")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
@@ -110,7 +117,7 @@ func (a AgentService) GetWecomConfig(req dto.AgentIDReq) (*dto.AgentWecomConfig,
|
||||
return nil, err
|
||||
}
|
||||
result := extractWecomConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "wecom")
|
||||
installed, _ := checkPluginInstalled(install.GetPath(), "wecom")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
@@ -133,7 +140,7 @@ func (a AgentService) GetDingTalkConfig(req dto.AgentIDReq) (*dto.AgentDingTalkC
|
||||
return nil, err
|
||||
}
|
||||
result := extractDingTalkConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "dingtalk")
|
||||
installed, _ := checkPluginInstalled(install.GetPath(), "dingtalk")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
@@ -170,9 +177,10 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
if req.Type == "qqbot" {
|
||||
legacyPluginPath := path.Join(openclawPluginBaseDir, "qqbot")
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", install.ContainerName, legacyPluginPath); err == nil {
|
||||
if err := mgr.Run("docker", "exec", "-i", install.ContainerName, "sh", "-c", "printf 'yes\\n' | openclaw plugins uninstall qqbot"); err != nil {
|
||||
if err := mgr.Run("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript("qqbot")); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
if err := mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil {
|
||||
@@ -193,6 +201,76 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpgradePlugin(req dto.AgentPluginUpgradeReq) error {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, pluginID, err := resolvePluginMeta(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upgradeTask, err := task.NewTaskWithOps(req.Type, task.TaskUpgrade, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upgradeTask.AddSubTask("Upgrade OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if err := mgr.Run("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
if err := mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appendPluginAllow(conf, pluginID)
|
||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := upgradeTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("upgrade openclaw plugin failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) UninstallPlugin(req dto.AgentPluginUninstallReq) error {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, pluginID, err := resolvePluginMeta(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uninstallTask, err := task.NewTaskWithOps(req.Type, task.TaskUninstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uninstallTask.AddSubTask("Uninstall OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if err := mgr.Run("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanupOpenclawPluginConfig(conf, req.Type)
|
||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := uninstallTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("uninstall openclaw plugin failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
@@ -219,11 +297,33 @@ func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPlugin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installed, err := checkPluginInstalled(install.ContainerName, req.Type)
|
||||
installed, err := checkPluginInstalled(install.GetPath(), req.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AgentPluginStatus{Installed: installed}, nil
|
||||
status := &dto.AgentPluginStatus{Installed: installed}
|
||||
if !installed {
|
||||
return status, nil
|
||||
}
|
||||
currentVersion, err := loadOpenclawPluginCurrentVersion(install.GetPath(), req.Type)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load openclaw plugin current version failed: %v", err)
|
||||
return status, nil
|
||||
}
|
||||
status.CurrentVersion = currentVersion
|
||||
if !req.CheckLatest {
|
||||
return status, nil
|
||||
}
|
||||
latestVersion, err := loadOpenclawPluginLatestVersion(install.ContainerName, req.Type)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load openclaw plugin latest version failed: %v", err)
|
||||
return status, nil
|
||||
}
|
||||
status.LatestVersion = latestVersion
|
||||
if currentVersion != "" && latestVersion != "" {
|
||||
status.Upgradable = common.CompareVersion(latestVersion, currentVersion)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error {
|
||||
@@ -717,6 +817,13 @@ func buildOpenclawPluginInstallScript(spec, pluginID string) string {
|
||||
)
|
||||
}
|
||||
|
||||
func buildOpenclawPluginUninstallScript(pluginID string) string {
|
||||
return fmt.Sprintf(
|
||||
"set +e; printf 'yes\\n' | openclaw plugins uninstall %s; code=$?; if [ \"$code\" -eq 137 ]; then exit 0; fi; exit \"$code\"",
|
||||
pluginID,
|
||||
)
|
||||
}
|
||||
|
||||
func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
switch pluginType {
|
||||
case "qqbot":
|
||||
@@ -734,22 +841,94 @@ func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkPluginInstalled(containerName, pluginType string) (bool, error) {
|
||||
_, pluginDir, err := resolvePluginMeta(pluginType)
|
||||
func checkPluginInstalled(installPath, pluginType string) (bool, error) {
|
||||
packagePath, err := resolveOpenclawPluginPackagePath(installPath, pluginType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.TrimSpace(containerName) == "" {
|
||||
return false, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
pluginPath := path.Join(openclawPluginBaseDir, pluginDir)
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", containerName, pluginPath); err != nil {
|
||||
return false, nil
|
||||
if _, err := os.Stat(packagePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func loadOpenclawPluginCurrentVersion(installPath, pluginType string) (string, error) {
|
||||
packagePath, err := resolveOpenclawPluginPackagePath(installPath, pluginType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content, err := os.ReadFile(packagePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var pkg openclawPluginPackage
|
||||
if err := json.Unmarshal(content, &pkg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return pkg.Version, nil
|
||||
}
|
||||
|
||||
func loadOpenclawPluginLatestVersion(containerName, pluginType string) (string, error) {
|
||||
spec, _, err := resolvePluginMeta(pluginType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var version string
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &version); err == nil {
|
||||
return version, nil
|
||||
}
|
||||
return strings.Trim(strings.TrimSpace(output), `"`), nil
|
||||
}
|
||||
|
||||
func resolveOpenclawPluginPackagePath(installPath, pluginType string) (string, error) {
|
||||
_, pluginID, err := resolvePluginMeta(pluginType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if installPath == "" {
|
||||
return "", buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
return path.Join(installPath, "data", "conf", "extensions", pluginID, "package.json"), nil
|
||||
}
|
||||
|
||||
func cleanupOpenclawPluginConfig(conf map[string]interface{}, pluginType string) {
|
||||
channels, _ := conf["channels"].(map[string]interface{})
|
||||
plugins, _ := conf["plugins"].(map[string]interface{})
|
||||
entries, _ := plugins["entries"].(map[string]interface{})
|
||||
|
||||
switch pluginType {
|
||||
case "feishu":
|
||||
delete(channels, "feishu")
|
||||
delete(entries, "openclaw-lark")
|
||||
delete(entries, "feishu")
|
||||
case "qqbot":
|
||||
delete(channels, "qqbot")
|
||||
delete(entries, "openclaw-qqbot")
|
||||
delete(entries, "qqbot")
|
||||
case "wecom":
|
||||
delete(channels, "wecom")
|
||||
delete(entries, "wecom-openclaw-plugin")
|
||||
case "dingtalk":
|
||||
delete(channels, "dingtalk-connector")
|
||||
delete(entries, "dingtalk-connector")
|
||||
gateway := ensureChildMap(conf, "gateway")
|
||||
httpMap := ensureChildMap(gateway, "http")
|
||||
endpoints := ensureChildMap(httpMap, "endpoints")
|
||||
chatCompletions := ensureChildMap(endpoints, "chatCompletions")
|
||||
chatCompletions["enabled"] = false
|
||||
case "weixin":
|
||||
delete(channels, "weixin")
|
||||
delete(entries, "openclaw-weixin")
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelConfig(conf map[string]interface{}, channel string) map[string]interface{} {
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -102,11 +101,7 @@ func countOpenclawConfiguredChannels(conf map[string]interface{}) int {
|
||||
}
|
||||
|
||||
func loadOpenclawOverviewSkillStats(containerName string) (int, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills list --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
)
|
||||
output, err := runDockerExecWithStdout(30*time.Second, containerName, "sh", "-c", "openclaw skills list --json 2>&1")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -45,11 +45,7 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills list --json 2>&1",
|
||||
30*time.Second,
|
||||
install.ContainerName,
|
||||
)
|
||||
output, err := runDockerExecWithStdout(30*time.Second, install.ContainerName, "sh", "-c", "openclaw skills list --json 2>&1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,19 +150,9 @@ func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) {
|
||||
func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (string, error) {
|
||||
switch source {
|
||||
case "skillhub":
|
||||
return cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s skillhub search %q --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
keyword,
|
||||
)
|
||||
return runDockerExecWithStdout(30*time.Second, containerName, "skillhub", "search", keyword, "--json")
|
||||
default:
|
||||
return cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s clawhub search %q 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
keyword,
|
||||
)
|
||||
return runDockerExecWithStdout(30*time.Second, containerName, "clawhub", "search", keyword)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,12 +219,7 @@ func buildOpenclawSkillInstallCommand(source, slug string) string {
|
||||
}
|
||||
|
||||
func getOpenclawSkillKey(containerName, name string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills info %q --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
name,
|
||||
)
|
||||
output, err := runDockerExecWithStdout(30*time.Second, containerName, "sh", "-c", fmt.Sprintf("openclaw skills info %q --json 2>&1", name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
|
||||
@@ -59,6 +60,11 @@ func ensureContainerRunning(containerName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDockerExecWithStdout(timeout time.Duration, containerName string, args ...string) (string, error) {
|
||||
commandArgs := append([]string{"exec", containerName}, args...)
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(timeout)).RunWithStdout("docker", commandArgs...)
|
||||
}
|
||||
|
||||
func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) {
|
||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||
resolvedBaseURL := strings.TrimSpace(baseURL)
|
||||
|
||||
@@ -77,6 +77,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/channel/qqbot/get", baseApi.GetAgentQQBotConfig)
|
||||
aiToolsRouter.POST("/agents/channel/qqbot/update", baseApi.UpdateAgentQQBotConfig)
|
||||
aiToolsRouter.POST("/agents/plugin/install", baseApi.InstallAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugin/upgrade", baseApi.UpgradeAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugin/uninstall", baseApi.UninstallAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/plugin/check", baseApi.CheckAgentPlugin)
|
||||
aiToolsRouter.POST("/agents/security/get", baseApi.GetAgentSecurityConfig)
|
||||
aiToolsRouter.POST("/agents/security/update", baseApi.UpdateAgentSecurityConfig)
|
||||
|
||||
@@ -629,13 +629,29 @@ export namespace AI {
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginUpgradeReq {
|
||||
agentId: number;
|
||||
type: 'feishu' | 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginUninstallReq {
|
||||
agentId: number;
|
||||
type: 'feishu' | 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginCheckReq {
|
||||
agentId: number;
|
||||
type: 'feishu' | 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
|
||||
checkLatest?: boolean;
|
||||
}
|
||||
|
||||
export interface AgentPluginStatus {
|
||||
installed: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
upgradable: boolean;
|
||||
}
|
||||
|
||||
export interface AgentDiscordConfigReq {
|
||||
|
||||
@@ -241,6 +241,14 @@ export const installAgentPlugin = (req: AI.AgentPluginInstallReq) => {
|
||||
return http.post(`/ai/agents/plugin/install`, req);
|
||||
};
|
||||
|
||||
export const upgradeAgentPlugin = (req: AI.AgentPluginUpgradeReq) => {
|
||||
return http.post(`/ai/agents/plugin/upgrade`, req);
|
||||
};
|
||||
|
||||
export const uninstallAgentPlugin = (req: AI.AgentPluginUninstallReq) => {
|
||||
return http.post(`/ai/agents/plugin/uninstall`, req);
|
||||
};
|
||||
|
||||
export const checkAgentPlugin = (req: AI.AgentPluginCheckReq) => {
|
||||
return http.post<AI.AgentPluginStatus>(`/ai/agents/plugin/check`, req);
|
||||
};
|
||||
|
||||
@@ -692,46 +692,47 @@ const message = {
|
||||
allowedOriginsRequired: 'Introduce al menos una dirección de acceso',
|
||||
allowedOriginsInvalid: 'Usa el formato http(s)://host-o-ip[:puerto]',
|
||||
provider: 'Proveedor de modelos',
|
||||
accountModelsHelper: 'Configure the models this account exposes to OpenClaw for switching and settings',
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
accountModelsHelper:
|
||||
'Configure los modelos que esta cuenta expone a OpenClaw para el cambio y la configuración',
|
||||
accountModelsRequired: 'Configure al menos un modelo',
|
||||
accountModelsDuplicate: 'Existen modelos duplicados en el catálogo',
|
||||
modelPool: 'Pool de modelos',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
reasoning: 'Modelo de razonamiento',
|
||||
manualModel: 'Entrada manual de modelo',
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sin verificacion',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: 'Habilidades',
|
||||
securityTab: 'Seguridad',
|
||||
otherTab: 'Otros',
|
||||
timeZone: 'Zona horaria',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'Navegador habilitado',
|
||||
npmRegistry: 'Registro NPM',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'Se usa para instalar plugins de OpenClaw. Puede elegir un registro predefinido o introducir uno personalizado.',
|
||||
npmRegistryInvalid: 'Introduzca una URL válida del registro NPM que empiece por http:// o https://',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'Vaya a Configuración -> Otros para configurar el registro NPM y acelerar la instalación de plugins',
|
||||
skillsSearchPlaceholder: 'Buscar habilidades...',
|
||||
skillsEmpty: 'Sin habilidades',
|
||||
skillsMarket: 'Mercado de habilidades',
|
||||
skillsMarketHint: 'Seleccione una fuente y busque habilidades',
|
||||
skillsMarketEmpty: 'No se encontraron habilidades relacionadas',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'Puntuación',
|
||||
versionUnsupportedTitle: 'Esta función no es compatible con la versión actual',
|
||||
versionUnsupportedHelper: 'Actualice OpenClaw a la versión {0} o posterior.',
|
||||
skillsStatusDisabled: 'Deshabilitado',
|
||||
skillsGroupBuiltIn: 'Integradas',
|
||||
skillsGroupExternal: 'Externas',
|
||||
skillsGroupWorkspace: 'Espacio de trabajo',
|
||||
switchModelSuccess: 'Modelo cambiado correctamente',
|
||||
channelsTab: 'Canales',
|
||||
agentRoleTab: 'Agentes',
|
||||
agentRoleUnsupported: 'La gestión de roles actualmente solo es compatible con OpenClaw.',
|
||||
workspace: 'Directorio del espacio de trabajo',
|
||||
agentDir: 'Directorio del agente',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -759,51 +760,53 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'Vinculaciones',
|
||||
accountIdOptional: 'ID de cuenta (opcional)',
|
||||
saveAllMd: 'Guardar todo',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
'Guardar todos los archivos markdown actuales requiere reiniciar el contenedor para surtir efecto. Elija si reiniciar ahora o más tarde.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'Guardar el archivo de configuración requiere reiniciar inmediatamente el contenedor para surtir efecto.',
|
||||
overviewSnapshot: 'Resumen',
|
||||
defaultModel: 'Modelo predeterminado',
|
||||
channelCount: 'Canales configurados',
|
||||
skillCount: 'Habilidades',
|
||||
jobCount: 'Tareas programadas',
|
||||
sessionCount: 'Sesiones',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'El plugin no está instalado. Instálalo primero.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
dmPolicy: 'Política de MD',
|
||||
groupPolicy: 'Política de grupo',
|
||||
policyAllowlist: 'Lista permitida',
|
||||
policyOpen: 'Abierto',
|
||||
policyDisabled: 'Deshabilitado',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
addBot: 'Agregar bot',
|
||||
accountId: 'ID de cuenta',
|
||||
setDefaultBot: 'Establecer como predeterminado',
|
||||
botDuplicateAccountId: 'El ID de cuenta ya existe',
|
||||
botRequired: 'Agregue al menos un bot',
|
||||
botId: 'ID del bot',
|
||||
allowFrom: 'Lista permitida de MD',
|
||||
allowFromHelper: 'Un ID de remitente por línea. Solo se usa cuando la política de MD es Lista permitida.',
|
||||
allowFromPlaceholder: 'Un ID de remitente por línea',
|
||||
groupAllowFrom: 'Lista permitida de grupos',
|
||||
groupAllowFromHelper:
|
||||
'Un ID de grupo por línea. Solo se usa cuando la política de grupo es Lista permitida.',
|
||||
groupAllowFromPlaceholder: 'Un ID de grupo por línea',
|
||||
allowFromRequired: 'Introduzca al menos una entrada en la lista permitida',
|
||||
saveAndRestartGateway: 'Guardar y reiniciar gateway',
|
||||
pairingCode: 'Código de emparejamiento',
|
||||
pairingCodePlaceholder: 'Introduzca el código de emparejamiento',
|
||||
approvePairing: 'Aprobar emparejamiento',
|
||||
saveSuccess: 'Guardado correctamente',
|
||||
pairingApproveSuccess: 'Emparejamiento aprobado correctamente',
|
||||
scanConnect: 'Escanear para conectar',
|
||||
scanConnectHelper:
|
||||
'Haga clic para iniciar la tarea de inicio de sesión por QR. El código QR aparecerá en el registro de tareas.',
|
||||
customProviderHelper: 'En el proveedor de modelo personalizado no se valida si la cuenta está disponible',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -687,44 +687,44 @@ const message = {
|
||||
provider: 'モデルプロバイダー',
|
||||
accountModelsHelper: 'Configure the models this account exposes to OpenClaw for switching and settings',
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
accountModelsDuplicate: 'カタログに重複したモデルがあります',
|
||||
modelPool: 'モデルプール',
|
||||
modelInputTypes: '入力タイプ',
|
||||
reasoning: '推論モデル',
|
||||
manualModel: '手動入力',
|
||||
verified: '検証済み',
|
||||
verifySkipped: '検証なし',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: '技能',
|
||||
securityTab: 'セキュリティ',
|
||||
otherTab: 'その他',
|
||||
timeZone: 'タイムゾーン',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'ブラウザ有効化',
|
||||
npmRegistry: 'NPM レジストリ',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'OpenClaw プラグインのインストールに使用します。プリセットのレジストリを選択するか、カスタムのレジストリを入力できます。',
|
||||
npmRegistryInvalid: 'http:// または https:// で始まる有効な NPM レジストリ URL を入力してください',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
'設定 -> その他 で NPM レジストリを設定すると、プラグインのインストールを高速化できます',
|
||||
skillsSearchPlaceholder: '技能を検索...',
|
||||
skillsEmpty: '技能がありません',
|
||||
skillsMarket: '技能マーケット',
|
||||
skillsMarketHint: 'ソースを選択して技能を検索してください',
|
||||
skillsMarketEmpty: '関連する技能が見つかりませんでした',
|
||||
skillsMarketSourceClawhub: 'ClawHub(公式)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub(Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'スコア',
|
||||
versionUnsupportedTitle: '現在のバージョンではこの機能はサポートされていません',
|
||||
versionUnsupportedHelper: 'OpenClaw をバージョン {0} 以降にアップグレードしてください。',
|
||||
skillsStatusDisabled: '無効',
|
||||
skillsGroupBuiltIn: '内蔵',
|
||||
skillsGroupExternal: '外部',
|
||||
skillsGroupWorkspace: 'ワークスペース',
|
||||
switchModelSuccess: 'モデルの切り替えに成功しました',
|
||||
channelsTab: 'チャンネル',
|
||||
agentRoleTab: 'エージェント',
|
||||
agentRoleUnsupported: 'ロール管理は現在 OpenClaw のみ対応しています。',
|
||||
workspace: 'ワークスペースディレクトリ',
|
||||
agentDir: 'エージェントディレクトリ',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -752,51 +752,51 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'バインディング',
|
||||
accountIdOptional: 'アカウント ID(任意)',
|
||||
saveAllMd: 'すべて保存',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'現在の markdown ファイルをすべて保存するには、反映のためコンテナの再起動が必要です。今すぐ再起動するか後で再起動するかを選択してください。',
|
||||
configFileRestartHelper: '設定ファイルを保存するには、反映のため直ちにコンテナを再起動する必要があります。',
|
||||
overviewSnapshot: '概要',
|
||||
defaultModel: 'デフォルトモデル',
|
||||
channelCount: '設定済みチャンネル数',
|
||||
skillCount: '技能数',
|
||||
jobCount: '定期タスク数',
|
||||
sessionCount: 'セッション数',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'プラグインがインストールされていません。先にインストールしてください。',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
dmPolicy: 'DM ポリシー',
|
||||
groupPolicy: 'グループポリシー',
|
||||
policyAllowlist: '許可リスト',
|
||||
policyOpen: '開放',
|
||||
policyDisabled: '無効',
|
||||
bots: 'Bot',
|
||||
addBot: 'Bot を追加',
|
||||
accountId: 'アカウント ID',
|
||||
setDefaultBot: 'デフォルトに設定',
|
||||
botDuplicateAccountId: 'アカウント ID は既に存在します',
|
||||
botRequired: '少なくとも 1 つの Bot を追加してください',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
allowFrom: 'DM 許可リスト',
|
||||
allowFromHelper: '1 行に 1 つの送信者 ID を入力します。DM ポリシーが許可リストの場合のみ使用されます。',
|
||||
allowFromPlaceholder: '1 行に 1 つの送信者 ID',
|
||||
groupAllowFrom: 'グループ許可リスト',
|
||||
groupAllowFromHelper:
|
||||
'1 行に 1 つのグループ ID を入力します。グループポリシーが許可リストの場合のみ使用されます。',
|
||||
groupAllowFromPlaceholder: '1 行に 1 つのグループ ID',
|
||||
allowFromRequired: '少なくとも 1 件の許可リスト項目を入力してください',
|
||||
saveAndRestartGateway: '保存して gateway を再起動',
|
||||
pairingCode: 'ペアリングコード',
|
||||
pairingCodePlaceholder: 'ペアリングコードを入力',
|
||||
approvePairing: 'ペアリングを承認',
|
||||
saveSuccess: '保存しました',
|
||||
pairingApproveSuccess: 'ペアリングに成功しました',
|
||||
scanConnect: 'スキャンして接続',
|
||||
scanConnectHelper: 'クリックして QR ログインタスクを開始します。QR コードはタスクログに表示されます。',
|
||||
customProviderHelper: 'カスタムモデルプロバイダーでは、アカウントの有効性を検証しません',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -679,44 +679,44 @@ const message = {
|
||||
provider: '모델 제공자',
|
||||
accountModelsHelper: 'Configure the models this account exposes to OpenClaw for switching and settings',
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
accountModelsDuplicate: '카탈로그에 중복된 모델이 있습니다',
|
||||
modelPool: '모델 풀',
|
||||
modelInputTypes: '입력 유형',
|
||||
reasoning: '추론 모델',
|
||||
manualModel: '수동 입력',
|
||||
verified: '검증됨',
|
||||
verifySkipped: '검증 안 함',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: '기술',
|
||||
securityTab: '보안',
|
||||
otherTab: '기타',
|
||||
timeZone: '시간대',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: '브라우저 활성화',
|
||||
npmRegistry: 'NPM 레지스트리',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'OpenClaw 플러그인 설치에 사용됩니다. 사전 설정된 레지스트리를 선택하거나 사용자 지정 레지스트리를 입력할 수 있습니다.',
|
||||
npmRegistryInvalid: 'http:// 또는 https:// 로 시작하는 올바른 NPM 레지스트리 URL을 입력하세요',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'설정 -> 기타에서 NPM 레지스트리를 설정하면 플러그인 설치 속도를 높일 수 있습니다',
|
||||
skillsSearchPlaceholder: '기술 검색...',
|
||||
skillsEmpty: '기술 없음',
|
||||
skillsMarket: '기술 마켓',
|
||||
skillsMarketHint: '소스를 선택하고 기술을 검색하세요',
|
||||
skillsMarketEmpty: '관련 기술을 찾을 수 없습니다',
|
||||
skillsMarketSourceClawhub: 'ClawHub (공식)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: '점수',
|
||||
versionUnsupportedTitle: '현재 버전에서는 이 기능을 지원하지 않습니다',
|
||||
versionUnsupportedHelper: 'OpenClaw 를 버전 {0} 이상으로 업그레이드하세요.',
|
||||
skillsStatusDisabled: '비활성화됨',
|
||||
skillsGroupBuiltIn: '내장',
|
||||
skillsGroupExternal: '외부',
|
||||
skillsGroupWorkspace: '워크스페이스',
|
||||
switchModelSuccess: '모델이 성공적으로 전환되었습니다',
|
||||
channelsTab: '채널',
|
||||
agentRoleTab: '에이전트',
|
||||
agentRoleUnsupported: '역할 관리는 현재 OpenClaw 에서만 지원됩니다.',
|
||||
workspace: '워크스페이스 디렉터리',
|
||||
agentDir: '에이전트 디렉터리',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -744,51 +744,50 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: '바인딩',
|
||||
accountIdOptional: '계정 ID (선택 사항)',
|
||||
saveAllMd: '모두 저장',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'현재 markdown 파일을 모두 저장하려면 적용을 위해 컨테이너를 재시작해야 합니다. 지금 재시작할지 나중에 재시작할지 선택하세요.',
|
||||
configFileRestartHelper: '설정 파일을 저장하려면 즉시 컨테이너를 재시작해야 적용됩니다.',
|
||||
overviewSnapshot: '개요',
|
||||
defaultModel: '기본 모델',
|
||||
channelCount: '설정된 채널 수',
|
||||
skillCount: '기술 수',
|
||||
jobCount: '예약 작업 수',
|
||||
sessionCount: '세션 수',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: '플러그인이 설치되지 않았습니다. 먼저 설치해 주세요.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
dmPolicy: 'DM 정책',
|
||||
groupPolicy: '그룹 정책',
|
||||
policyAllowlist: '허용 목록',
|
||||
policyOpen: '열림',
|
||||
policyDisabled: '비활성화',
|
||||
bots: '봇',
|
||||
addBot: '봇 추가',
|
||||
accountId: '계정 ID',
|
||||
setDefaultBot: '기본값으로 설정',
|
||||
botDuplicateAccountId: '계정 ID가 이미 존재합니다',
|
||||
botRequired: '봇을 하나 이상 추가하세요',
|
||||
botId: '봇 ID',
|
||||
allowFrom: 'DM 허용 목록',
|
||||
allowFromHelper: '한 줄에 하나의 발신자 ID를 입력하세요. DM 정책이 허용 목록일 때만 사용됩니다.',
|
||||
allowFromPlaceholder: '한 줄에 하나의 발신자 ID',
|
||||
groupAllowFrom: '그룹 허용 목록',
|
||||
groupAllowFromHelper: '한 줄에 하나의 그룹 ID를 입력하세요. 그룹 정책이 허용 목록일 때만 사용됩니다.',
|
||||
groupAllowFromPlaceholder: '한 줄에 하나의 그룹 ID',
|
||||
allowFromRequired: '허용 목록 항목을 하나 이상 입력하세요',
|
||||
saveAndRestartGateway: '저장 후 gateway 재시작',
|
||||
pairingCode: '페어링 코드',
|
||||
pairingCodePlaceholder: '페어링 코드를 입력하세요',
|
||||
approvePairing: '페어링 승인',
|
||||
saveSuccess: '저장되었습니다',
|
||||
pairingApproveSuccess: '페어링이 승인되었습니다',
|
||||
scanConnect: '스캔하여 연결',
|
||||
scanConnectHelper: '클릭하여 QR 로그인 작업을 시작하세요. QR 코드는 작업 로그에 표시됩니다.',
|
||||
customProviderHelper: '사용자 정의 모델 공급자는 계정 사용 가능 여부를 검증하지 않습니다',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -696,42 +696,42 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
modelInputTypes: 'Jenis input',
|
||||
reasoning: 'Model penaakulan',
|
||||
manualModel: 'Input manual',
|
||||
verified: 'Disahkan',
|
||||
verifySkipped: 'Tanpa pengesahan',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: 'Kemahiran',
|
||||
securityTab: 'Keselamatan',
|
||||
otherTab: 'Lain-lain',
|
||||
timeZone: 'Zon Waktu',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'Pelayar diaktifkan',
|
||||
npmRegistry: 'Registri NPM',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'Digunakan untuk pemasangan plugin OpenClaw. Anda boleh memilih registri pratetap atau memasukkan registri tersuai.',
|
||||
npmRegistryInvalid: 'Masukkan URL registri NPM yang sah bermula dengan http:// atau https://',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'Pergi ke Tetapan -> Lain-lain untuk mengkonfigurasi registri NPM dan mempercepat pemasangan plugin',
|
||||
skillsSearchPlaceholder: 'Cari kemahiran...',
|
||||
skillsEmpty: 'Tiada kemahiran',
|
||||
skillsMarket: 'Pasaran kemahiran',
|
||||
skillsMarketHint: 'Pilih sumber dan cari kemahiran',
|
||||
skillsMarketEmpty: 'Tiada kemahiran berkaitan ditemui',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Rasmi)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'Skor',
|
||||
versionUnsupportedTitle: 'Ciri ini tidak disokong dalam versi semasa',
|
||||
versionUnsupportedHelper: 'Sila naik taraf OpenClaw ke versi {0} atau lebih baharu.',
|
||||
skillsStatusDisabled: 'Dilumpuhkan',
|
||||
skillsGroupBuiltIn: 'Terbina dalam',
|
||||
skillsGroupExternal: 'Luaran',
|
||||
skillsGroupWorkspace: 'Ruang kerja',
|
||||
switchModelSuccess: 'Model berjaya ditukar',
|
||||
channelsTab: 'Saluran',
|
||||
agentRoleTab: 'Ejen',
|
||||
agentRoleUnsupported: 'Pengurusan peranan kini hanya disokong untuk OpenClaw.',
|
||||
workspace: 'Direktori ruang kerja',
|
||||
agentDir: 'Direktori ejen',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -759,51 +759,53 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'Ikatan',
|
||||
accountIdOptional: 'ID akaun (pilihan)',
|
||||
saveAllMd: 'Simpan semua',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
'Menyimpan semua fail markdown semasa memerlukan kontena dimulakan semula untuk berkuat kuasa. Pilih sama ada hendak memulakan semula sekarang atau kemudian.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'Menyimpan fail konfigurasi memerlukan kontena dimulakan semula serta-merta untuk berkuat kuasa.',
|
||||
overviewSnapshot: 'Ringkasan',
|
||||
defaultModel: 'Model lalai',
|
||||
channelCount: 'Bilangan saluran dikonfigurasi',
|
||||
skillCount: 'Bilangan kemahiran',
|
||||
jobCount: 'Bilangan tugas berjadual',
|
||||
sessionCount: 'Bilangan sesi',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'Plugin belum dipasang. Sila pasang dahulu.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
dmPolicy: 'Dasar DM',
|
||||
groupPolicy: 'Dasar kumpulan',
|
||||
policyAllowlist: 'Senarai benarkan',
|
||||
policyOpen: 'Terbuka',
|
||||
policyDisabled: 'Dilumpuhkan',
|
||||
bots: 'Bot',
|
||||
addBot: 'Tambah bot',
|
||||
accountId: 'ID akaun',
|
||||
setDefaultBot: 'Tetapkan sebagai lalai',
|
||||
botDuplicateAccountId: 'ID akaun sudah wujud',
|
||||
botRequired: 'Tambah sekurang-kurangnya satu bot',
|
||||
botId: 'ID bot',
|
||||
allowFrom: 'Senarai benarkan DM',
|
||||
allowFromHelper:
|
||||
'Satu ID penghantar setiap baris. Hanya digunakan apabila dasar DM ialah Senarai benarkan.',
|
||||
allowFromPlaceholder: 'Satu ID penghantar setiap baris',
|
||||
groupAllowFrom: 'Senarai benarkan kumpulan',
|
||||
groupAllowFromHelper:
|
||||
'Satu ID kumpulan setiap baris. Hanya digunakan apabila dasar kumpulan ialah Senarai benarkan.',
|
||||
groupAllowFromPlaceholder: 'Satu ID kumpulan setiap baris',
|
||||
allowFromRequired: 'Masukkan sekurang-kurangnya satu entri senarai benarkan',
|
||||
saveAndRestartGateway: 'Simpan dan mulakan semula gateway',
|
||||
pairingCode: 'Kod pasangan',
|
||||
pairingCodePlaceholder: 'Masukkan kod pasangan',
|
||||
approvePairing: 'Luluskan pasangan',
|
||||
saveSuccess: 'Berjaya disimpan',
|
||||
pairingApproveSuccess: 'Pasangan berjaya diluluskan',
|
||||
scanConnect: 'Imbas untuk sambung',
|
||||
scanConnectHelper: 'Klik untuk memulakan tugas log masuk QR. Kod QR akan muncul dalam log tugas.',
|
||||
customProviderHelper: 'Penyedia model tersuai tidak mengesahkan sama ada akaun boleh digunakan',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -691,42 +691,42 @@ const message = {
|
||||
accountModelsRequired: 'Configure at least one model',
|
||||
accountModelsDuplicate: 'Duplicate models exist in the catalog',
|
||||
modelPool: 'Model Pool',
|
||||
modelInputTypes: 'Input Types',
|
||||
reasoning: 'Reasoning Model',
|
||||
modelInputTypes: 'Tipos de entrada',
|
||||
reasoning: 'Modelo de raciocínio',
|
||||
manualModel: 'Entrada manual',
|
||||
verified: 'Verificado',
|
||||
verifySkipped: 'Sem verificacao',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: 'Habilidades',
|
||||
securityTab: 'Segurança',
|
||||
otherTab: 'Outros',
|
||||
timeZone: 'Fuso horário',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'Navegador habilitado',
|
||||
npmRegistry: 'Registro NPM',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'Usado para a instalação de plugins do OpenClaw. Você pode escolher um registro predefinido ou informar um personalizado.',
|
||||
npmRegistryInvalid: 'Digite uma URL válida de registro NPM começando com http:// ou https://',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'Vá em Configurações -> Outros para configurar o registro NPM e acelerar a instalação do plugin',
|
||||
skillsSearchPlaceholder: 'Pesquisar habilidades...',
|
||||
skillsEmpty: 'Sem habilidades',
|
||||
skillsMarket: 'Mercado de habilidades',
|
||||
skillsMarketHint: 'Selecione uma fonte e pesquise habilidades',
|
||||
skillsMarketEmpty: 'Nenhuma habilidade correspondente encontrada',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'Pontuação',
|
||||
versionUnsupportedTitle: 'Este recurso não é compatível com a versão atual',
|
||||
versionUnsupportedHelper: 'Atualize o OpenClaw para a versão {0} ou superior.',
|
||||
skillsStatusDisabled: 'Desativado',
|
||||
skillsGroupBuiltIn: 'Integradas',
|
||||
skillsGroupExternal: 'Externas',
|
||||
skillsGroupWorkspace: 'Espaço de trabalho',
|
||||
switchModelSuccess: 'Modelo alterado com sucesso',
|
||||
channelsTab: 'Canais',
|
||||
agentRoleTab: 'Agentes',
|
||||
agentRoleUnsupported: 'O gerenciamento de funções atualmente é compatível apenas com o OpenClaw.',
|
||||
workspace: 'Diretório do espaço de trabalho',
|
||||
agentDir: 'Diretório do agente',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -754,51 +754,53 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'Vínculos',
|
||||
accountIdOptional: 'ID da conta (opcional)',
|
||||
saveAllMd: 'Salvar tudo',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
'Salvar todos os arquivos markdown atuais exige reiniciar o contêiner para entrar em vigor. Escolha se deseja reiniciar agora ou depois.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'Salvar o arquivo de configuração exige reiniciar imediatamente o contêiner para entrar em vigor.',
|
||||
overviewSnapshot: 'Visão geral',
|
||||
defaultModel: 'Modelo padrão',
|
||||
channelCount: 'Canais configurados',
|
||||
skillCount: 'Habilidades',
|
||||
jobCount: 'Tarefas agendadas',
|
||||
sessionCount: 'Sessões',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'O plugin não está instalado. Instale-o primeiro.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
dmPolicy: 'Política de DM',
|
||||
groupPolicy: 'Política de grupo',
|
||||
policyAllowlist: 'Lista de permissões',
|
||||
policyOpen: 'Aberto',
|
||||
policyDisabled: 'Desativado',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
addBot: 'Adicionar bot',
|
||||
accountId: 'ID da conta',
|
||||
setDefaultBot: 'Definir como padrão',
|
||||
botDuplicateAccountId: 'O ID da conta já existe',
|
||||
botRequired: 'Adicione pelo menos um bot',
|
||||
botId: 'ID do bot',
|
||||
allowFrom: 'Lista de permissões de DM',
|
||||
allowFromHelper:
|
||||
'Um ID de remetente por linha. Usado apenas quando a Política de DM é Lista de permissões.',
|
||||
allowFromPlaceholder: 'Um ID de remetente por linha',
|
||||
groupAllowFrom: 'Lista de permissões de grupo',
|
||||
groupAllowFromHelper:
|
||||
'Um ID de grupo por linha. Usado apenas quando a Política de grupo é Lista de permissões.',
|
||||
groupAllowFromPlaceholder: 'Um ID de grupo por linha',
|
||||
allowFromRequired: 'Digite pelo menos uma entrada na lista de permissões',
|
||||
saveAndRestartGateway: 'Salvar e reiniciar gateway',
|
||||
pairingCode: 'Código de pareamento',
|
||||
pairingCodePlaceholder: 'Digite o código de pareamento',
|
||||
approvePairing: 'Aprovar pareamento',
|
||||
saveSuccess: 'Salvo com sucesso',
|
||||
pairingApproveSuccess: 'Pareamento aprovado com sucesso',
|
||||
scanConnect: 'Escanear para conectar',
|
||||
scanConnectHelper: 'Clique para iniciar a tarefa de login por QR. O código QR aparecerá no log da tarefa.',
|
||||
customProviderHelper: 'Provedores de modelo personalizados não validam se a conta está disponível',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -697,33 +697,33 @@ const message = {
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
timeZone: 'Часовой пояс',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'Браузер включен',
|
||||
npmRegistry: 'Реестр NPM',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'Используется для установки плагинов OpenClaw. Вы можете выбрать предустановленный реестр или указать свой.',
|
||||
npmRegistryInvalid: 'Введите корректный URL реестра NPM, начинающийся с http:// или https://',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'Перейдите в Настройки -> Другое, чтобы настроить реестр NPM и ускорить установку плагинов',
|
||||
skillsSearchPlaceholder: 'Поиск навыков...',
|
||||
skillsEmpty: 'Нет навыков',
|
||||
skillsMarket: 'Рынок навыков',
|
||||
skillsMarketHint: 'Выберите источник и выполните поиск навыков',
|
||||
skillsMarketEmpty: 'Подходящие навыки не найдены',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Официальный)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'Оценка',
|
||||
versionUnsupportedTitle: 'Эта функция не поддерживается в текущей версии',
|
||||
versionUnsupportedHelper: 'Пожалуйста, обновите OpenClaw до версии {0} или выше.',
|
||||
skillsStatusDisabled: 'Отключено',
|
||||
skillsGroupBuiltIn: 'Встроенные',
|
||||
skillsGroupExternal: 'Внешние',
|
||||
skillsGroupWorkspace: 'Рабочая область',
|
||||
switchModelSuccess: 'Модель успешно переключена',
|
||||
channelsTab: 'Каналы',
|
||||
agentRoleTab: 'Агенты',
|
||||
agentRoleUnsupported: 'Управление ролями в настоящее время поддерживается только для OpenClaw.',
|
||||
workspace: 'Каталог рабочей области',
|
||||
agentDir: 'Каталог агента',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -751,51 +751,52 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'Привязки',
|
||||
accountIdOptional: 'ID аккаунта (необязательно)',
|
||||
saveAllMd: 'Сохранить все',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
'Сохранение всех текущих markdown-файлов требует перезапуска контейнера для применения. Выберите, перезапустить сейчас или позже.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'Сохранение файла конфигурации требует немедленного перезапуска контейнера для применения.',
|
||||
overviewSnapshot: 'Сводка',
|
||||
defaultModel: 'Модель по умолчанию',
|
||||
channelCount: 'Настроенные каналы',
|
||||
skillCount: 'Навыки',
|
||||
jobCount: 'Запланированные задачи',
|
||||
sessionCount: 'Сессии',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'Плагин не установлен. Сначала установите его.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
dmPolicy: 'Политика DM',
|
||||
groupPolicy: 'Групповая политика',
|
||||
policyAllowlist: 'Белый список',
|
||||
policyOpen: 'Открыто',
|
||||
policyDisabled: 'Отключено',
|
||||
bots: 'Боты',
|
||||
addBot: 'Добавить бота',
|
||||
accountId: 'ID аккаунта',
|
||||
setDefaultBot: 'Сделать по умолчанию',
|
||||
botDuplicateAccountId: 'ID аккаунта уже существует',
|
||||
botRequired: 'Добавьте хотя бы одного бота',
|
||||
botId: 'ID бота',
|
||||
allowFrom: 'Белый список DM',
|
||||
allowFromHelper: 'Один ID отправителя на строку. Используется только когда политика DM — Белый список.',
|
||||
allowFromPlaceholder: 'Один ID отправителя на строку',
|
||||
groupAllowFrom: 'Белый список групп',
|
||||
groupAllowFromHelper:
|
||||
'Один ID группы на строку. Используется только когда групповая политика — Белый список.',
|
||||
groupAllowFromPlaceholder: 'Один ID группы на строку',
|
||||
allowFromRequired: 'Введите хотя бы одну запись белого списка',
|
||||
saveAndRestartGateway: 'Сохранить и перезапустить gateway',
|
||||
pairingCode: 'Код сопряжения',
|
||||
pairingCodePlaceholder: 'Введите код сопряжения',
|
||||
approvePairing: 'Одобрить сопряжение',
|
||||
saveSuccess: 'Успешно сохранено',
|
||||
pairingApproveSuccess: 'Сопряжение успешно одобрено',
|
||||
scanConnect: 'Сканировать для подключения',
|
||||
scanConnectHelper: 'Нажмите, чтобы запустить задачу входа по QR-коду. QR-код появится в журнале задач.',
|
||||
customProviderHelper: 'Для пользовательского провайдера модели доступность учетной записи не проверяется',
|
||||
},
|
||||
model: {
|
||||
|
||||
@@ -697,37 +697,37 @@ const message = {
|
||||
manualModel: 'Manuel giriş',
|
||||
verified: 'Doğrulandı',
|
||||
verifySkipped: 'Doğrulama yok',
|
||||
skillsTab: 'Skills',
|
||||
securityTab: 'Security',
|
||||
otherTab: 'Other',
|
||||
skillsTab: 'Yetenekler',
|
||||
securityTab: 'Güvenlik',
|
||||
otherTab: 'Diğer',
|
||||
timeZone: 'Saat Dilimi',
|
||||
browserEnabled: 'Browser Enabled',
|
||||
npmRegistry: 'NPM Registry',
|
||||
browserEnabled: 'Tarayıcı etkin',
|
||||
npmRegistry: 'NPM kayıt defteri',
|
||||
npmRegistryHelper:
|
||||
'Used for OpenClaw plugin installation. You can choose a preset registry or enter a custom one.',
|
||||
npmRegistryInvalid: 'Enter a valid NPM registry URL starting with http:// or https://',
|
||||
'OpenClaw eklenti kurulumu için kullanılır. Hazır bir kayıt defteri seçebilir veya özel bir tane girebilirsiniz.',
|
||||
npmRegistryInvalid: 'http:// veya https:// ile başlayan geçerli bir NPM kayıt defteri URLsi girin',
|
||||
pluginInstallNPMRegistryHelper:
|
||||
'Go to Settings -> Other to configure the NPM registry and speed up plugin installation',
|
||||
skillsSearchPlaceholder: 'Search skills...',
|
||||
skillsEmpty: 'No skills',
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
'NPM kayıt defterini yapılandırmak ve eklenti kurulumunu hızlandırmak için Ayarlar -> Diğer bölümüne gidin',
|
||||
skillsSearchPlaceholder: 'Yetenek ara...',
|
||||
skillsEmpty: 'Yetenek yok',
|
||||
skillsMarket: 'Yetenek pazarı',
|
||||
skillsMarketHint: 'Bir kaynak seçin ve yetenek arayın',
|
||||
skillsMarketEmpty: 'Eşleşen yetenek bulunamadı',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Resmi)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
versionUnsupportedHelper: 'Please upgrade OpenClaw to version {0} or later.',
|
||||
skillsStatusDisabled: 'Disabled',
|
||||
skillsGroupBuiltIn: 'Built-in',
|
||||
skillsGroupExternal: 'External',
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
agentRoleTab: 'Agents',
|
||||
agentRoleUnsupported: 'Role management is currently supported only for OpenClaw.',
|
||||
workspace: 'Workspace Directory',
|
||||
agentDir: 'Agent Directory',
|
||||
skillsScore: 'Puan',
|
||||
versionUnsupportedTitle: 'Bu özellik mevcut sürümde desteklenmiyor',
|
||||
versionUnsupportedHelper: 'Lütfen OpenClawı {0} veya üzeri bir sürüme yükseltin.',
|
||||
skillsStatusDisabled: 'Devre dışı',
|
||||
skillsGroupBuiltIn: 'Yerleşik',
|
||||
skillsGroupExternal: 'Harici',
|
||||
skillsGroupWorkspace: 'Çalışma alanı',
|
||||
switchModelSuccess: 'Model başarıyla değiştirildi',
|
||||
channelsTab: 'Kanallar',
|
||||
agentRoleTab: 'Ajanlar',
|
||||
agentRoleUnsupported: 'Rol yönetimi şu anda yalnızca OpenClaw için desteklenmektedir.',
|
||||
workspace: 'Çalışma alanı dizini',
|
||||
agentDir: 'Ajan dizini',
|
||||
roleMarkdownDescriptions: {
|
||||
'AGENTS.md': [
|
||||
'Operating instructions for the agent and how it should use memory.',
|
||||
@@ -755,51 +755,52 @@ const message = {
|
||||
'Delete it after the ritual is complete.',
|
||||
],
|
||||
},
|
||||
bindings: 'Bindings',
|
||||
accountIdOptional: 'Account ID (Optional)',
|
||||
saveAllMd: 'Save All',
|
||||
bindings: 'Bağlamalar',
|
||||
accountIdOptional: 'Hesap ID (İsteğe bağlı)',
|
||||
saveAllMd: 'Tümünü kaydet',
|
||||
roleMarkdownRestartHelper:
|
||||
'Saving all current markdown files requires a container restart to take effect. Choose whether to restart now or later.',
|
||||
'Mevcut tüm markdown dosyalarını kaydetmek için etkin olması adına konteynerin yeniden başlatılması gerekir. Şimdi mi sonra mı yeniden başlatılacağını seçin.',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
overviewSnapshot: 'Snapshot',
|
||||
defaultModel: 'Default Model',
|
||||
channelCount: 'Configured Channels',
|
||||
skillCount: 'Skills',
|
||||
jobCount: 'Scheduled Jobs',
|
||||
sessionCount: 'Sessions',
|
||||
'Yapılandırma dosyasını kaydetmek, etkin olması için konteynerin hemen yeniden başlatılmasını gerektirir.',
|
||||
overviewSnapshot: 'Özet',
|
||||
defaultModel: 'Varsayılan model',
|
||||
channelCount: 'Yapılandırılmış kanallar',
|
||||
skillCount: 'Yetenekler',
|
||||
jobCount: 'Zamanlanmış görevler',
|
||||
sessionCount: 'Oturumlar',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
feishu: 'Feishu',
|
||||
pluginNotInstalled: 'Eklenti yüklü değil. Lütfen önce yükleyin.',
|
||||
dmPolicy: 'DM Policy',
|
||||
groupPolicy: 'Group Policy',
|
||||
policyAllowlist: 'Allowlist',
|
||||
policyOpen: 'Open',
|
||||
policyDisabled: 'Disabled',
|
||||
bots: 'Bots',
|
||||
addBot: 'Add Bot',
|
||||
accountId: 'Account ID',
|
||||
setDefaultBot: 'Set as Default',
|
||||
botDuplicateAccountId: 'Account ID already exists',
|
||||
botRequired: 'Add at least one bot',
|
||||
dmPolicy: 'DM ilkesi',
|
||||
groupPolicy: 'Grup ilkesi',
|
||||
policyAllowlist: 'İzin listesi',
|
||||
policyOpen: 'Açık',
|
||||
policyDisabled: 'Devre dışı',
|
||||
bots: 'Botlar',
|
||||
addBot: 'Bot ekle',
|
||||
accountId: 'Hesap ID',
|
||||
setDefaultBot: 'Varsayılan yap',
|
||||
botDuplicateAccountId: 'Hesap ID zaten mevcut',
|
||||
botRequired: 'En az bir bot ekleyin',
|
||||
botId: 'Bot ID',
|
||||
allowFrom: 'DM Allowlist',
|
||||
allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.',
|
||||
allowFromPlaceholder: 'One sender ID per line',
|
||||
groupAllowFrom: 'Group Allowlist',
|
||||
groupAllowFromHelper: 'One group ID per line. Used only when Group Policy is Allowlist.',
|
||||
groupAllowFromPlaceholder: 'One group ID per line',
|
||||
allowFromRequired: 'Enter at least one allowlist entry',
|
||||
saveAndRestartGateway: 'Save and restart gateway',
|
||||
pairingCode: 'Pairing Code',
|
||||
pairingCodePlaceholder: 'Enter pairing code',
|
||||
approvePairing: 'Approve Pairing',
|
||||
saveSuccess: 'Saved successfully',
|
||||
pairingApproveSuccess: 'Pairing approved successfully',
|
||||
scanConnect: 'Scan to Connect',
|
||||
scanConnectHelper: 'Click to start the QR login task. The QR code will appear in the task log.',
|
||||
allowFrom: 'DM izin listesi',
|
||||
allowFromHelper: 'Her satıra bir gönderici ID yazın. Yalnızca DM İlkesi İzin listesi olduğunda kullanılır.',
|
||||
allowFromPlaceholder: 'Her satıra bir gönderici ID',
|
||||
groupAllowFrom: 'Grup izin listesi',
|
||||
groupAllowFromHelper:
|
||||
'Her satıra bir grup ID yazın. Yalnızca Grup İlkesi İzin listesi olduğunda kullanılır.',
|
||||
groupAllowFromPlaceholder: 'Her satıra bir grup ID',
|
||||
allowFromRequired: 'En az bir izin listesi girdisi girin',
|
||||
saveAndRestartGateway: 'Kaydet ve gatewayi yeniden başlat',
|
||||
pairingCode: 'Eşleştirme kodu',
|
||||
pairingCodePlaceholder: 'Eşleştirme kodunu girin',
|
||||
approvePairing: 'Eşleştirmeyi onayla',
|
||||
saveSuccess: 'Başarıyla kaydedildi',
|
||||
pairingApproveSuccess: 'Eşleştirme başarıyla onaylandı',
|
||||
scanConnect: 'Tara ve bağlan',
|
||||
scanConnectHelper: 'QR giriş görevini başlatmak için tıklayın. QR kodu görev günlüğünde görünecektir.',
|
||||
customProviderHelper: 'Özel model sağlayıcısında hesabın kullanılabilirliği doğrulanmaz',
|
||||
},
|
||||
model: {
|
||||
|
||||
+65
-35
@@ -25,39 +25,46 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('commons.table.status')" width="120">
|
||||
<template #default="{ row, $index }">
|
||||
<el-switch :model-value="row.enabled" @change="updateEnabled($index, $event)" />
|
||||
<el-switch
|
||||
:model-value="row.enabled"
|
||||
:disabled="disabled || isBotActionDisabled(row)"
|
||||
@change="updateEnabled($index, $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('commons.table.operate')" width="260" fixed="right">
|
||||
<el-table-column :label="t('commons.table.operate')" min-width="320" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button link type="primary" @click="openEdit(row, $index)">
|
||||
{{ t('commons.button.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="defaultable && !row.isDefault"
|
||||
link
|
||||
type="primary"
|
||||
@click="setDefault(row.accountId)"
|
||||
>
|
||||
{{ t('aiTools.agents.setDefaultBot') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="approvable"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="!row.enabled"
|
||||
@click="emit('approve', row)"
|
||||
>
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:disabled="undeletableAccountIds.includes(row.accountId)"
|
||||
@click="removeBot($index)"
|
||||
>
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<div class="channel-bots__actions">
|
||||
<el-button link type="primary" :disabled="disabled" @click="openEdit(row, $index)">
|
||||
{{ t('commons.button.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="defaultable && !row.isDefault"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="disabled || isBotActionDisabled(row)"
|
||||
@click="setDefault(row.accountId)"
|
||||
>
|
||||
{{ t('aiTools.agents.setDefaultBot') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="approvable"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="disabled || !row.enabled || isBotActionDisabled(row)"
|
||||
@click="emit('approve', row)"
|
||||
>
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:disabled="disabled || undeletableAccountIds.includes(row.accountId)"
|
||||
@click="removeBot($index)"
|
||||
>
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -65,13 +72,13 @@
|
||||
<el-dialog v-model="dialogVisible" width="520px" :title="dialogTitle" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item :label="t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
<el-input v-model="form.name" :disabled="disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.accountId')" prop="accountId">
|
||||
<el-input v-model="form.accountId" :disabled="accountIdLocked" />
|
||||
<el-input v-model="form.accountId" :disabled="disabled || accountIdLocked" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" />
|
||||
<el-switch v-model="form.enabled" :disabled="disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item v-for="field in fields" :key="field.prop" :label="field.label" :prop="field.prop">
|
||||
<el-input
|
||||
@@ -79,14 +86,17 @@
|
||||
v-model="form[field.prop]"
|
||||
type="password"
|
||||
show-password
|
||||
:disabled="disabled"
|
||||
:placeholder="field.placeholder"
|
||||
/>
|
||||
<el-input v-else v-model="form[field.prop]" :placeholder="field.placeholder" />
|
||||
<el-input v-else v-model="form[field.prop]" :disabled="disabled" :placeholder="field.placeholder" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveBot">{{ t('commons.button.save') }}</el-button>
|
||||
<el-button type="primary" :disabled="disabled" @click="saveBot">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -153,6 +163,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
undeletableAccountIds: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
@@ -224,6 +238,10 @@ const emitBots = (bots: ChannelBotItem[]) => {
|
||||
emit('update:bots', nextBots);
|
||||
};
|
||||
|
||||
const isBotActionDisabled = (bot: ChannelBotItem) => {
|
||||
return props.fields.some((field) => field.required && !bot[field.prop]);
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
editIndex.value = -1;
|
||||
editingAccountId.value = '';
|
||||
@@ -306,4 +324,16 @@ const updateEnabled = (index: number, enabled: boolean | string | number) => {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.channel-bots__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 12px;
|
||||
|
||||
:deep(.el-button) {
|
||||
margin-left: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+86
-6
@@ -8,25 +8,76 @@
|
||||
:description="t('aiTools.agents.pluginInstallNPMRegistryHelper')"
|
||||
/>
|
||||
<el-form-item v-if="!installed" class="mt-4">
|
||||
<el-button type="primary" :loading="installing" @click="emit('install')">
|
||||
<el-button type="primary" :loading="installing" @click="handleInstall">
|
||||
{{ t('commons.button.install') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div v-else class="plugin-install-status">
|
||||
<div class="plugin-install-status__row">
|
||||
<div class="plugin-install-status__text">
|
||||
<span class="plugin-install-status__label">{{ t('app.version') }}</span>
|
||||
<span class="plugin-install-status__value">{{ currentVersion || '-' }}</span>
|
||||
</div>
|
||||
<el-button type="danger" plain size="small" :loading="uninstalling" @click="handleUninstall">
|
||||
{{ t('commons.button.uninstall') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="upgradable" class="plugin-install-status__row mt-4">
|
||||
<div class="plugin-install-status__text">
|
||||
<span class="plugin-install-status__label">{{ t('app.newVersion') }}</span>
|
||||
<span class="plugin-install-status__value">{{ latestVersion || '-' }}</span>
|
||||
</div>
|
||||
<el-button type="primary" size="small" :loading="upgrading" @click="handleUpgrade">
|
||||
{{ t('commons.button.upgrade') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<TaskLog ref="taskLogRef" @close="handleTaskClose" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
installed: boolean;
|
||||
installing: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
install: [];
|
||||
upgrading: boolean;
|
||||
uninstalling: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
upgradable: boolean;
|
||||
installAction: () => Promise<string>;
|
||||
upgradeAction: () => Promise<string>;
|
||||
uninstallAction: () => Promise<string>;
|
||||
onTaskClose?: () => void | Promise<void>;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const taskLogRef = ref();
|
||||
|
||||
const openTaskLog = (taskID: string) => {
|
||||
if (taskID) {
|
||||
taskLogRef.value?.openWithTaskID(taskID);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstall = async () => {
|
||||
openTaskLog(await props.installAction());
|
||||
};
|
||||
|
||||
const handleUpgrade = async () => {
|
||||
openTaskLog(await props.upgradeAction());
|
||||
};
|
||||
|
||||
const handleUninstall = async () => {
|
||||
openTaskLog(await props.uninstallAction());
|
||||
};
|
||||
|
||||
const handleTaskClose = async () => {
|
||||
await props.onTaskClose?.();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -37,4 +88,33 @@ const { t } = useI18n();
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-install-status__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
background-color: var(--el-fill-color-blank);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.plugin-install-status__text {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.plugin-install-status__label {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.plugin-install-status__value {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,63 +1,81 @@
|
||||
<template>
|
||||
<VersionSupport v-if="!supported" :min-version="openclawMinSupportedVersion" />
|
||||
<el-form v-else ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall :installed="installed" :installing="installing" @install="installPlugin" />
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.dmPolicy')" prop="dmPolicy">
|
||||
<el-select v-model="form.dmPolicy">
|
||||
<el-option :label="t('aiTools.agents.policyAllowlist')" value="allowlist" />
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
<el-option :label="t('aiTools.agents.policyDisabled')" value="disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dmPolicy === 'allowlist'" :label="t('aiTools.agents.allowFrom')" prop="allowFromText">
|
||||
<el-input
|
||||
v-model="form.allowFromText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('aiTools.agents.allowFromPlaceholder')"
|
||||
/>
|
||||
<span class="input-help">{{ t('aiTools.agents.allowFromHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.groupPolicy')" prop="groupPolicy">
|
||||
<el-select v-model="form.groupPolicy">
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
<el-option :label="t('aiTools.agents.policyAllowlist')" value="allowlist" />
|
||||
<el-option :label="t('aiTools.agents.policyDisabled')" value="disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.groupPolicy === 'allowlist'"
|
||||
:label="t('aiTools.agents.groupAllowFrom')"
|
||||
prop="groupAllowFromText"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.groupAllowFromText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('aiTools.agents.groupAllowFromPlaceholder')"
|
||||
/>
|
||||
<span class="input-help">{{ t('aiTools.agents.groupAllowFromHelper') }}</span>
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="Client ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
<el-form v-else ref="formRef" v-loading="loading" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall
|
||||
:installed="installed"
|
||||
:installing="installing"
|
||||
:upgrading="upgrading"
|
||||
:uninstalling="uninstalling"
|
||||
:current-version="currentVersion"
|
||||
:latest-version="latestVersion"
|
||||
:upgradable="upgradable"
|
||||
:install-action="installPlugin"
|
||||
:upgrade-action="upgradePlugin"
|
||||
:uninstall-action="uninstallPlugin"
|
||||
:on-task-close="reload"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<template v-if="installed">
|
||||
<el-form-item :label="t('commons.table.status')" class="mt-4">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.dmPolicy')" prop="dmPolicy">
|
||||
<el-select v-model="form.dmPolicy">
|
||||
<el-option :label="t('aiTools.agents.policyAllowlist')" value="allowlist" />
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
<el-option :label="t('aiTools.agents.policyDisabled')" value="disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dmPolicy === 'allowlist'"
|
||||
:label="t('aiTools.agents.allowFrom')"
|
||||
prop="allowFromText"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.allowFromText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('aiTools.agents.allowFromPlaceholder')"
|
||||
/>
|
||||
<span class="input-help">{{ t('aiTools.agents.allowFromHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.groupPolicy')" prop="groupPolicy">
|
||||
<el-select v-model="form.groupPolicy">
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
<el-option :label="t('aiTools.agents.policyAllowlist')" value="allowlist" />
|
||||
<el-option :label="t('aiTools.agents.policyDisabled')" value="disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.groupPolicy === 'allowlist'"
|
||||
:label="t('aiTools.agents.groupAllowFrom')"
|
||||
prop="groupAllowFromText"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.groupAllowFromText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('aiTools.agents.groupAllowFromPlaceholder')"
|
||||
/>
|
||||
<span class="input-help">{{ t('aiTools.agents.groupAllowFromHelper') }}</span>
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="Client ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
:disabled="!installed"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<TaskLog ref="taskLogRef" @close="checkPluginStatus" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -69,7 +87,6 @@ import { getAgentDingTalkConfig, updateAgentDingTalkConfig } from '@/api/modules
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { isOpenclawCurrentHTTPVersion } from '@/utils/agent';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import PluginInstall from './components/plugin-install.vue';
|
||||
import VersionSupport from '../components/version-support.vue';
|
||||
import { useAgentPluginChannel } from './useAgentPluginChannel';
|
||||
@@ -79,6 +96,12 @@ interface DingTalkForm extends Omit<AI.AgentDingTalkConfig, 'installed' | 'allow
|
||||
allowFromText: string;
|
||||
groupAllowFromText: string;
|
||||
}
|
||||
type BotField = {
|
||||
prop: string;
|
||||
label: string;
|
||||
type?: 'text' | 'password';
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
const openclawMinSupportedVersion = '2026.3.23';
|
||||
const props = defineProps<{
|
||||
@@ -89,8 +112,20 @@ const { t } = useI18n();
|
||||
const saving = ref(false);
|
||||
const agentId = ref(0);
|
||||
const formRef = ref<FormInstance>();
|
||||
const { installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } =
|
||||
useAgentPluginChannel('dingtalk');
|
||||
const {
|
||||
installed,
|
||||
loading,
|
||||
installing,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
} = useAgentPluginChannel('dingtalk');
|
||||
const supported = computed(() => isOpenclawCurrentHTTPVersion(props.appVersion));
|
||||
|
||||
const form = reactive<DingTalkForm>({
|
||||
@@ -144,7 +179,7 @@ const rules = reactive<FormRules>({
|
||||
groupAllowFromText: [{ validator: validateGroupAllowFrom, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const botFields = [
|
||||
const botFields: BotField[] = [
|
||||
{ prop: 'clientId', label: 'Client ID', required: true },
|
||||
{ prop: 'clientSecret', label: 'Client Secret', type: 'password', required: true },
|
||||
];
|
||||
@@ -181,6 +216,13 @@ const load = async (id: number) => {
|
||||
form.groupAllowFromText = (res.data?.groupAllowFrom || []).join('\n');
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
if (!supported.value || !agentId.value || !formRef.value) {
|
||||
return;
|
||||
|
||||
@@ -50,6 +50,12 @@ import { Rules } from '@/global/form-rules';
|
||||
import ChannelBots from './components/channel-bots.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
type BotField = {
|
||||
prop: string;
|
||||
label: string;
|
||||
type?: 'text' | 'password';
|
||||
required?: boolean;
|
||||
};
|
||||
const saving = ref(false);
|
||||
const approving = ref(false);
|
||||
const agentId = ref(0);
|
||||
@@ -69,7 +75,7 @@ const rules = reactive({
|
||||
groupPolicy: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const botFields = [{ prop: 'token', label: 'Token', type: 'password', required: true }];
|
||||
const botFields: BotField[] = [{ prop: 'token', label: 'Token', type: 'password', required: true }];
|
||||
|
||||
const createBot = (): AI.AgentDiscordBot => ({
|
||||
accountId: '',
|
||||
|
||||
@@ -1,35 +1,49 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="approving" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall :installed="installed" :installing="installing" @install="installPlugin" />
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-link type="primary" icon="Position" @click="toFeishuDoc">
|
||||
{{ t('container.mirrorsHelper2') }}
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="App ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
approvable
|
||||
:fixed-account-ids="['default']"
|
||||
:undeletable-account-ids="['default']"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
@approve="approvePairing"
|
||||
<el-form ref="formRef" v-loading="loading || approving" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall
|
||||
:installed="installed"
|
||||
:installing="installing"
|
||||
:upgrading="upgrading"
|
||||
:uninstalling="uninstalling"
|
||||
:current-version="currentVersion"
|
||||
:latest-version="latestVersion"
|
||||
:upgradable="upgradable"
|
||||
:install-action="installPlugin"
|
||||
:upgrade-action="upgradePlugin"
|
||||
:uninstall-action="uninstallPlugin"
|
||||
:on-task-close="reload"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<template v-if="installed">
|
||||
<el-form-item :label="t('commons.table.status')" class="mt-4">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-link type="primary" icon="Position" @click="toFeishuDoc">
|
||||
{{ t('container.mirrorsHelper2') }}
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="App ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
:disabled="!installed"
|
||||
approvable
|
||||
:fixed-account-ids="['default']"
|
||||
:undeletable-account-ids="['default']"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
@approve="approvePairing"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<TaskLog ref="taskLogRef" @close="checkPluginStatus" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -40,17 +54,35 @@ import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { approveAgentChannelPairing, getAgentFeishuConfig, updateAgentFeishuConfig } from '@/api/modules/ai';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import PluginInstall from './components/plugin-install.vue';
|
||||
import { useAgentPluginChannel } from './useAgentPluginChannel';
|
||||
import ChannelBots from './components/channel-bots.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
type BotField = {
|
||||
prop: string;
|
||||
label: string;
|
||||
type?: 'text' | 'password';
|
||||
required?: boolean;
|
||||
};
|
||||
const saving = ref(false);
|
||||
const approving = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const { agentId, installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } =
|
||||
useAgentPluginChannel('feishu');
|
||||
const {
|
||||
agentId,
|
||||
loading,
|
||||
installed,
|
||||
installing,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
} = useAgentPluginChannel('feishu');
|
||||
|
||||
const form = reactive<AI.AgentFeishuConfig>({
|
||||
enabled: true,
|
||||
@@ -60,7 +92,7 @@ const form = reactive<AI.AgentFeishuConfig>({
|
||||
|
||||
const rules = reactive({});
|
||||
|
||||
const botFields = [
|
||||
const botFields: BotField[] = [
|
||||
{ prop: 'appId', label: 'App ID', required: true },
|
||||
{ prop: 'appSecret', label: 'App Secret', type: 'password', required: true },
|
||||
];
|
||||
@@ -93,6 +125,13 @@ const load = async (id: number) => {
|
||||
form.bots = res.data?.bots || [];
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
if (!agentId.value || !formRef.value) {
|
||||
return;
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
<template>
|
||||
<el-form :model="form" label-position="top">
|
||||
<PluginInstall :installed="installed" :installing="installing" @install="installPlugin" />
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="App ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
:fixed-account-ids="['default']"
|
||||
:undeletable-account-ids="['default']"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
<el-form v-loading="loading" :model="form" label-position="top">
|
||||
<PluginInstall
|
||||
:installed="installed"
|
||||
:installing="installing"
|
||||
:upgrading="upgrading"
|
||||
:uninstalling="uninstalling"
|
||||
:current-version="currentVersion"
|
||||
:latest-version="latestVersion"
|
||||
:upgradable="upgradable"
|
||||
:install-action="installPlugin"
|
||||
:upgrade-action="upgradePlugin"
|
||||
:uninstall-action="uninstallPlugin"
|
||||
:on-task-close="reload"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<template v-if="installed">
|
||||
<el-form-item :label="t('commons.table.status')" class="mt-4">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<ChannelBots
|
||||
:bots="form.bots"
|
||||
:fields="botFields"
|
||||
:create-bot="createBot"
|
||||
summary-label="App ID"
|
||||
:summary-formatter="getBotSummary"
|
||||
:add-disabled="!installed"
|
||||
:disabled="!installed"
|
||||
:fixed-account-ids="['default']"
|
||||
:undeletable-account-ids="['default']"
|
||||
@update:bots="updateBots"
|
||||
@save="saveChannel"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<TaskLog ref="taskLogRef" @close="checkPluginStatus" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -31,24 +45,42 @@ import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { getAgentQQBotConfig, updateAgentQQBotConfig } from '@/api/modules/ai';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import PluginInstall from './components/plugin-install.vue';
|
||||
import { useAgentPluginChannel } from './useAgentPluginChannel';
|
||||
import ChannelBots from './components/channel-bots.vue';
|
||||
|
||||
type QQBotForm = Pick<AI.AgentQQBotConfig, 'enabled' | 'bots'>;
|
||||
type BotField = {
|
||||
prop: string;
|
||||
label: string;
|
||||
type?: 'text' | 'password';
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
const { t } = useI18n();
|
||||
const saving = ref(false);
|
||||
const { agentId, installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } =
|
||||
useAgentPluginChannel('qqbot');
|
||||
const {
|
||||
agentId,
|
||||
loading,
|
||||
installed,
|
||||
installing,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
} = useAgentPluginChannel('qqbot');
|
||||
|
||||
const form = reactive<QQBotForm>({
|
||||
enabled: true,
|
||||
bots: [],
|
||||
});
|
||||
|
||||
const botFields = [
|
||||
const botFields: BotField[] = [
|
||||
{ prop: 'appId', label: 'App ID', required: true },
|
||||
{ prop: 'clientSecret', label: 'App Secret', type: 'password', required: true },
|
||||
];
|
||||
@@ -77,6 +109,13 @@ const load = async (id: number) => {
|
||||
form.bots = res.data?.bots || [];
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
|
||||
@@ -44,6 +44,12 @@ import { Rules } from '@/global/form-rules';
|
||||
import ChannelBots from './components/channel-bots.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
type BotField = {
|
||||
prop: string;
|
||||
label: string;
|
||||
type?: 'text' | 'password';
|
||||
required?: boolean;
|
||||
};
|
||||
const saving = ref(false);
|
||||
const approving = ref(false);
|
||||
const agentId = ref(0);
|
||||
@@ -61,7 +67,7 @@ const rules = reactive({
|
||||
dmPolicy: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const botFields = [{ prop: 'botToken', label: 'Bot Token', type: 'password', required: true }];
|
||||
const botFields: BotField[] = [{ prop: 'botToken', label: 'Bot Token', type: 'password', required: true }];
|
||||
|
||||
const createBot = (): AI.AgentTelegramBot => ({
|
||||
accountId: '',
|
||||
|
||||
@@ -1,34 +1,78 @@
|
||||
import { ref } from 'vue';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { checkAgentPlugin, installAgentPlugin } from '@/api/modules/ai';
|
||||
import { checkAgentPlugin, installAgentPlugin, uninstallAgentPlugin, upgradeAgentPlugin } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
export const useAgentPluginChannel = (pluginType: AI.AgentPluginInstallReq['type']) => {
|
||||
const agentId = ref(0);
|
||||
const loading = ref(false);
|
||||
const installed = ref(false);
|
||||
const installing = ref(false);
|
||||
const taskLogRef = ref();
|
||||
const upgrading = ref(false);
|
||||
const uninstalling = ref(false);
|
||||
const currentVersion = ref('');
|
||||
const latestVersion = ref('');
|
||||
const upgradable = ref(false);
|
||||
let loadVersion = 0;
|
||||
|
||||
const checkPluginStatus = async () => {
|
||||
const applyPluginStatus = (status?: AI.AgentPluginStatus) => {
|
||||
installed.value = Boolean(status?.installed);
|
||||
currentVersion.value = status?.currentVersion || '';
|
||||
latestVersion.value = status?.latestVersion || '';
|
||||
upgradable.value = Boolean(status?.upgradable);
|
||||
};
|
||||
|
||||
const checkPluginStatus = async (checkLatest = false) => {
|
||||
if (!agentId.value) {
|
||||
return false;
|
||||
}
|
||||
const res = await checkAgentPlugin({
|
||||
agentId: agentId.value,
|
||||
type: pluginType,
|
||||
checkLatest,
|
||||
});
|
||||
installed.value = Boolean(res.data?.installed);
|
||||
return installed.value;
|
||||
applyPluginStatus(res.data);
|
||||
return Boolean(res.data?.installed);
|
||||
};
|
||||
|
||||
const checkPluginLatestVersion = async (version: number) => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await checkAgentPlugin({
|
||||
agentId: agentId.value,
|
||||
type: pluginType,
|
||||
checkLatest: true,
|
||||
});
|
||||
if (version !== loadVersion) {
|
||||
return;
|
||||
}
|
||||
applyPluginStatus(res.data);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const loadPlugin = async (id: number) => {
|
||||
loadVersion += 1;
|
||||
const version = loadVersion;
|
||||
agentId.value = id;
|
||||
return await checkPluginStatus();
|
||||
loading.value = true;
|
||||
try {
|
||||
latestVersion.value = '';
|
||||
upgradable.value = false;
|
||||
const isInstalled = await checkPluginStatus();
|
||||
if (isInstalled) {
|
||||
void checkPluginLatestVersion(version);
|
||||
}
|
||||
return isInstalled;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const installPlugin = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
const taskID = newUUID();
|
||||
installing.value = true;
|
||||
@@ -38,19 +82,62 @@ export const useAgentPluginChannel = (pluginType: AI.AgentPluginInstallReq['type
|
||||
type: pluginType,
|
||||
taskID,
|
||||
});
|
||||
taskLogRef.value?.openWithTaskID(taskID);
|
||||
return taskID;
|
||||
} finally {
|
||||
installing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const upgradePlugin = async () => {
|
||||
if (!agentId.value) {
|
||||
return '';
|
||||
}
|
||||
const taskID = newUUID();
|
||||
upgrading.value = true;
|
||||
try {
|
||||
await upgradeAgentPlugin({
|
||||
agentId: agentId.value,
|
||||
type: pluginType,
|
||||
taskID,
|
||||
});
|
||||
return taskID;
|
||||
} finally {
|
||||
upgrading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const uninstallPlugin = async () => {
|
||||
if (!agentId.value) {
|
||||
return '';
|
||||
}
|
||||
const taskID = newUUID();
|
||||
uninstalling.value = true;
|
||||
try {
|
||||
await uninstallAgentPlugin({
|
||||
agentId: agentId.value,
|
||||
type: pluginType,
|
||||
taskID,
|
||||
});
|
||||
return taskID;
|
||||
} finally {
|
||||
uninstalling.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
agentId,
|
||||
loading,
|
||||
installed,
|
||||
installing,
|
||||
taskLogRef,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
checkPluginStatus,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,39 +1,52 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="approving" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall :installed="installed" :installing="installing" @install="installPlugin" />
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.dmPolicy')" prop="dmPolicy">
|
||||
<el-select v-model="form.dmPolicy">
|
||||
<el-option :label="t('aiTools.agents.pairingCode')" value="pairing" />
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.botId')" prop="botId">
|
||||
<el-input v-model="form.botId" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('setting.secret')" prop="secret">
|
||||
<el-input v-model="form.secret" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form ref="formRef" v-loading="loading || approving" :model="form" :rules="rules" label-position="top">
|
||||
<PluginInstall
|
||||
:installed="installed"
|
||||
:installing="installing"
|
||||
:upgrading="upgrading"
|
||||
:uninstalling="uninstalling"
|
||||
:current-version="currentVersion"
|
||||
:latest-version="latestVersion"
|
||||
:upgradable="upgradable"
|
||||
:install-action="installPlugin"
|
||||
:upgrade-action="upgradePlugin"
|
||||
:uninstall-action="uninstallPlugin"
|
||||
:on-task-close="reload"
|
||||
/>
|
||||
<template v-if="installed">
|
||||
<el-form-item :label="t('commons.table.status')" class="mt-4">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.dmPolicy')" prop="dmPolicy">
|
||||
<el-select v-model="form.dmPolicy">
|
||||
<el-option :label="t('aiTools.agents.pairingCode')" value="pairing" />
|
||||
<el-option :label="t('aiTools.agents.policyOpen')" value="open" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.botId')" prop="botId">
|
||||
<el-input v-model="form.botId" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('setting.secret')" prop="secret">
|
||||
<el-input v-model="form.secret" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider />
|
||||
<el-divider />
|
||||
|
||||
<el-form-item :label="t('aiTools.agents.pairingCode')">
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="approving" :disabled="!installed" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.pairingCode')">
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="approving" :disabled="!installed" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<TaskLog ref="taskLogRef" @close="checkPluginStatus" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -44,7 +57,6 @@ import { AI } from '@/api/interface/ai';
|
||||
import { approveAgentChannelPairing, getAgentWecomConfig, updateAgentWecomConfig } from '@/api/modules/ai';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import PluginInstall from './components/plugin-install.vue';
|
||||
import { useAgentPluginChannel } from './useAgentPluginChannel';
|
||||
|
||||
@@ -55,8 +67,21 @@ const saving = ref(false);
|
||||
const approving = ref(false);
|
||||
const pairingCode = ref('');
|
||||
const formRef = ref<FormInstance>();
|
||||
const { agentId, installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } =
|
||||
useAgentPluginChannel('wecom');
|
||||
const {
|
||||
agentId,
|
||||
loading,
|
||||
installed,
|
||||
installing,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
} = useAgentPluginChannel('wecom');
|
||||
|
||||
const form = reactive<WecomForm>({
|
||||
enabled: true,
|
||||
@@ -84,6 +109,13 @@ const load = async (id: number) => {
|
||||
}
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
await load(agentId.value);
|
||||
};
|
||||
|
||||
const saveChannel = async () => {
|
||||
if (!agentId.value || !formRef.value) {
|
||||
return;
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
<template>
|
||||
<VersionSupport v-if="!supported" :min-version="openclawMinSupportedVersion" />
|
||||
<el-form v-else label-position="top">
|
||||
<PluginInstall :installed="installed" :installing="installing" @install="installPlugin" />
|
||||
<el-alert type="info" :closable="false" :title="t('aiTools.agents.scanConnectHelper')" />
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="loggingIn" :disabled="!installed" @click="loginChannel">
|
||||
{{ t('aiTools.agents.scanConnect') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form v-else v-loading="loading" label-position="top">
|
||||
<PluginInstall
|
||||
:installed="installed"
|
||||
:installing="installing"
|
||||
:upgrading="upgrading"
|
||||
:uninstalling="uninstalling"
|
||||
:current-version="currentVersion"
|
||||
:latest-version="latestVersion"
|
||||
:upgradable="upgradable"
|
||||
:install-action="installPlugin"
|
||||
:upgrade-action="upgradePlugin"
|
||||
:uninstall-action="uninstallPlugin"
|
||||
:on-task-close="reload"
|
||||
/>
|
||||
<template v-if="installed">
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="loggingIn" :disabled="!installed" @click="loginChannel">
|
||||
{{ t('aiTools.agents.scanConnect') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-alert type="info" :closable="false" :title="t('aiTools.agents.scanConnectHelper')" />
|
||||
</template>
|
||||
</el-form>
|
||||
<TaskLog ref="taskLogRef" @close="reload" />
|
||||
<TaskLog ref="loginTaskLogRef" @close="reload" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -30,7 +44,22 @@ const props = defineProps<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const loggingIn = ref(false);
|
||||
const { agentId, installed, installing, taskLogRef, loadPlugin, installPlugin } = useAgentPluginChannel('weixin');
|
||||
const loginTaskLogRef = ref();
|
||||
const {
|
||||
agentId,
|
||||
loading,
|
||||
installed,
|
||||
installing,
|
||||
upgrading,
|
||||
uninstalling,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
upgradable,
|
||||
loadPlugin,
|
||||
installPlugin,
|
||||
upgradePlugin,
|
||||
uninstallPlugin,
|
||||
} = useAgentPluginChannel('weixin');
|
||||
const supported = computed(() => isOpenclawCurrentHTTPVersion(props.appVersion));
|
||||
|
||||
const load = async (id: number) => {
|
||||
@@ -58,7 +87,7 @@ const loginChannel = async () => {
|
||||
agentId: agentId.value,
|
||||
taskID,
|
||||
});
|
||||
taskLogRef.value?.openWithTaskID(taskID);
|
||||
loginTaskLogRef.value?.openWithTaskID(taskID);
|
||||
} finally {
|
||||
loggingIn.value = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user