feat: Support uninstallation and upgrades of OpenClaw plugins. (#12373)

This commit is contained in:
CityFun
2026-03-31 10:08:05 +00:00
committed by GitHub
parent 52778f520a
commit f787734feb
27 changed files with 1375 additions and 732 deletions
+40
View File
@@ -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
View File
@@ -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 {
+3 -1
View File
@@ -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
}
+195 -16
View File
@@ -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 {
+1 -6
View File
@@ -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
}
+4 -23
View File
@@ -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
}
+6
View File
@@ -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)
+2
View File
@@ -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)
+16
View File
@@ -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 {
+8
View File
@@ -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);
};
+73 -70
View File
@@ -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: {
+68 -68
View File
@@ -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: 'SkillHubTencent',
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: {
+69 -70
View File
@@ -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: {
+69 -67
View File
@@ -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: {
+68 -66
View File
@@ -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)',
' 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 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: {
+63 -62
View File
@@ -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: {
+65 -64
View File
@@ -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: {
@@ -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>
@@ -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;
}