Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions agent/app/api/v2/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,46 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
}
helper.Success(c)
}

// @Tags Backup Account
// @Summary List files in cloud storage account
// @Accept json
// @Param request body dto.CloudFileListReq true "request"
// @Success 200 {array} dto.CloudFileInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backups/cloud/files [post]
func (b *BaseApi) ListCloudFiles(c *gin.Context) {
var req dto.CloudFileListReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

files, err := backupService.ListCloudFiles(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, files)
}

// @Tags Backup Account
// @Summary Sync a file from cloud storage to local path
// @Accept json
// @Param request body dto.CloudFileSyncReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backups/cloud/sync [post]
func (b *BaseApi) SyncCloudFileToLocal(c *gin.Context) {
var req dto.CloudFileSyncReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

if err := backupService.SyncCloudFileToLocal(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
17 changes: 17 additions & 0 deletions agent/app/dto/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,20 @@ type RecordFileSize struct {
Name string `json:"name"`
Size int64 `json:"size"`
}

type CloudFileInfo struct {
Name string `json:"name"`
Size int64 `json:"size"`
IsDir bool `json:"isDir"`
}

type CloudFileListReq struct {
AccountID uint `json:"accountID" validate:"required"`
Path string `json:"path"`
}

type CloudFileSyncReq struct {
AccountID uint `json:"accountID" validate:"required"`
SrcPath string `json:"srcPath" validate:"required"`
DstPath string `json:"dstPath" validate:"required"`
}
14 changes: 9 additions & 5 deletions agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
appKey := constant.AppOpenclaw
if agentType == constant.AppCopaw {
appKey = constant.AppCopaw
} else if agentType == constant.AppNemoclaw {
appKey = constant.AppNemoclaw
}
app, err := appRepo.GetFirst(appRepo.WithKey(appKey))
if err != nil || app.ID == 0 {
Expand Down Expand Up @@ -347,7 +349,7 @@ func (a AgentService) ResetToken(req dto.AgentTokenResetReq) error {
if err != nil {
return err
}
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
if normalizeAgentType(agent.AgentType) == constant.AppCopaw || normalizeAgentType(agent.AgentType) == constant.AppNemoclaw {
return fmt.Errorf("copaw does not support token")
}
configPath := strings.TrimSpace(agent.ConfigPath)
Expand Down Expand Up @@ -390,7 +392,7 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
if err != nil {
return err
}
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
if normalizeAgentType(agent.AgentType) == constant.AppCopaw || normalizeAgentType(agent.AgentType) == constant.AppNemoclaw {
return fmt.Errorf("copaw does not support model config")
}
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
Expand Down Expand Up @@ -867,7 +869,7 @@ func (a AgentService) GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.Ag
if err != nil {
return nil, err
}
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
if normalizeAgentType(agent.AgentType) == constant.AppCopaw || normalizeAgentType(agent.AgentType) == constant.AppNemoclaw {
return nil, fmt.Errorf("copaw does not support security config")
}
conf, err := readOpenclawConfig(agent.ConfigPath)
Expand All @@ -883,7 +885,7 @@ func (a AgentService) UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq)
if err != nil {
return err
}
if normalizeAgentType(agent.AgentType) == constant.AppCopaw {
if normalizeAgentType(agent.AgentType) == constant.AppCopaw || normalizeAgentType(agent.AgentType) == constant.AppNemoclaw {
return fmt.Errorf("copaw does not support security config")
}
allowedOrigins, err := normalizeAllowedOrigins(req.AllowedOrigins)
Expand Down Expand Up @@ -1567,6 +1569,8 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
agentType := normalizeAgentType(agent.AgentType)
if appInstall != nil && appInstall.ID > 0 && appInstall.App.Key == constant.AppCopaw {
agentType = constant.AppCopaw
} else if appInstall != nil && appInstall.ID > 0 && appInstall.App.Key == constant.AppNemoclaw {
agentType = constant.AppNemoclaw
}
item := dto.AgentItem{
ID: agent.ID,
Expand Down Expand Up @@ -2375,7 +2379,7 @@ func providerModelPrefix(provider string) string {

func isSupportedAgentType(agentType string) bool {
switch normalizeAgentType(agentType) {
case constant.AppOpenclaw, constant.AppCopaw:
case constant.AppOpenclaw, constant.AppCopaw, constant.AppNemoclaw:
return true
default:
return false
Expand Down
2 changes: 1 addition & 1 deletion agent/app/service/app_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
return err
}
appKey := install.App.Key
if appKey == constant.AppOpenclaw || appKey == constant.AppCopaw {
if appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppNemoclaw {
_ = agentRepo.DeleteByAppInstallIDWithCtx(ctx, install.ID)
}

Expand Down
31 changes: 31 additions & 0 deletions agent/app/service/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ type IBackupService interface {
ContainerRecover(req dto.CommonRecover) error
ComposeBackup(req dto.CommonBackup) error
ComposeRecover(req dto.CommonRecover) error

ListCloudFiles(req dto.CloudFileListReq) ([]dto.CloudFileInfo, error)
SyncCloudFileToLocal(req dto.CloudFileSyncReq) error
}

func NewIBackupService() IBackupService {
Expand Down Expand Up @@ -635,3 +638,31 @@ func changeLocalBackup(oldPath, newPath string) error {
_ = fileOp.RmRf(path.Join(oldPath, "master"))
return nil
}

func (u *BackupService) ListCloudFiles(req dto.CloudFileListReq) ([]dto.CloudFileInfo, error) {
_, backClient, err := NewBackupClientWithID(req.AccountID)
if err != nil {
return nil, err
}
names, err := backClient.ListObjects(req.Path)
if err != nil {
return nil, err
}
var result []dto.CloudFileInfo
for _, name := range names {
info := dto.CloudFileInfo{Name: name}
result = append(result, info)
}
return result, nil
}

func (u *BackupService) SyncCloudFileToLocal(req dto.CloudFileSyncReq) error {
_, backClient, err := NewBackupClientWithID(req.AccountID)
if err != nil {
return err
}
if _, err := backClient.Download(req.SrcPath, req.DstPath); err != nil {
return err
}
return nil
}
1 change: 1 addition & 0 deletions agent/constant/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
AppOpenresty = "openresty"
AppOpenclaw = "openclaw"
AppCopaw = "copaw"
AppNemoclaw = "nemoclaw"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
Expand Down
2 changes: 1 addition & 1 deletion agent/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/aws/aws-sdk-go v1.55.0
github.com/compose-spec/compose-go/v2 v2.9.0
github.com/creack/pty v1.1.24
github.com/docker/cli v28.5.1+incompatible
github.com/docker/compose/v2 v2.40.2
github.com/docker/docker v28.5.1+incompatible
github.com/docker/go-connections v0.6.0
Expand Down Expand Up @@ -113,7 +114,6 @@ require (
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/buildx v0.29.1 // indirect
github.com/docker/cli v28.5.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
Expand Down
1 change: 1 addition & 0 deletions agent/init/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func InitAgentDB() {
migrations.AddAgentTypeForAgents,
migrations.NormalizeAgentAccountVerifiedStatus,
migrations.NormalizeOllamaAccountAPIType,
migrations.AddNemoclawAgentType,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
Expand Down
11 changes: 11 additions & 0 deletions agent/init/migration/migrations/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -1095,3 +1095,14 @@ var NormalizeOllamaAccountAPIType = &gormigrate.Migration{
Update("api_type", "openai-responses").Error
},
}

var AddNemoclawAgentType = &gormigrate.Migration{
ID: "20260320-add-nemoclaw-agent-type",
Migrate: func(tx *gorm.DB) error {
return tx.Exec(
"UPDATE agents SET agent_type = ? WHERE app_install_id IN (SELECT ai.id FROM app_installs ai JOIN apps a ON ai.app_id = a.id WHERE a.key = ?)",
constant.AppNemoclaw,
constant.AppNemoclaw,
).Error
},
}
3 changes: 3 additions & 0 deletions agent/router/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
backupRouter.POST("/record/download", baseApi.DownloadRecord)
backupRouter.POST("/record/del", baseApi.DeleteBackupRecord)
backupRouter.POST("/record/description/update", baseApi.UpdateRecordDescription)

backupRouter.POST("/cloud/files", baseApi.ListCloudFiles)
backupRouter.POST("/cloud/sync", baseApi.SyncCloudFileToLocal)
}
}
4 changes: 2 additions & 2 deletions frontend/src/api/interface/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export namespace AI {
webUIPort: number;
bridgePort?: number;
allowedOrigins?: string[];
agentType: 'openclaw' | 'copaw';
agentType: 'openclaw' | 'copaw' | 'nemoclaw';
provider?: string;
model?: string;
apiType?: string;
Expand All @@ -269,7 +269,7 @@ export namespace AI {
export interface AgentItem {
id: number;
name: string;
agentType: 'openclaw' | 'copaw';
agentType: 'openclaw' | 'copaw' | 'nemoclaw';
provider: string;
providerName: string;
model: string;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/api/interface/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,18 @@ export namespace Backup {
secret: string;
taskID: string;
}
export interface CloudFileInfo {
name: string;
size: number;
isDir: boolean;
}
export interface CloudFileListReq {
accountID: number;
path: string;
}
export interface CloudFileSyncReq {
accountID: number;
srcPath: string;
dstPath: string;
}
}
10 changes: 10 additions & 0 deletions frontend/src/api/modules/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,13 @@ export const deleteBackup = (params: { id: number; name: string; isPublic: boole
}
return http.post('/core/backups/del', { name: params.name });
};

export const listCloudFiles = (params: Backup.CloudFileListReq, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post<Array<Backup.CloudFileInfo>>(`/backups/cloud/files${query}`, params);
};

export const syncCloudFileToLocal = (params: Backup.CloudFileSyncReq, node?: string) => {
const query = node ? `?operateNode=${node}` : '';
return http.post(`/backups/cloud/sync${query}`, params);
};
10 changes: 10 additions & 0 deletions frontend/src/assets/images/ai-agent-nemoclaw.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ const message = {
syncAgentsHelper: 'Update openclaw.json for agents using this model account',
openclawType: 'OpenClaw',
copawType: 'CoPaw',
nemoclawType: 'NemoClaw',
appVersion: 'App Version',
webuiPort: 'WebUI Port',
allowedOrigins: 'Access Addresses',
Expand Down Expand Up @@ -1993,6 +1994,13 @@ const message = {
loadBucket: 'Get bucket',
accountName: 'Account name',
accountKey: 'Account key',
cloudFiles: 'Cloud Files',
cloudFileBrowser: 'Cloud File Browser',
cloudFileBrowserHelper: 'Browse and sync files from your cloud storage account to local storage',
syncToLocal: 'Sync to Local',
syncToLocalPath: 'Local Destination Path',
syncToLocalSuccess: 'File synced to local storage successfully',
cloudFilePath: 'Cloud File Path',
address: 'Address',
path: 'Path',
safe: 'Security',
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ const message = {
syncAgentsHelper: '更新使用该模型账号的智能体 openclaw.json',
openclawType: 'OpenClaw',
copawType: 'CoPaw',
nemoclawType: 'NemoClaw',
appVersion: '应用版本',
webuiPort: 'WebUI 端口',
allowedOrigins: '访问地址',
Expand Down Expand Up @@ -1862,6 +1863,13 @@ const message = {
loadBucket: '获取桶',
accountName: '账户名称',
accountKey: '账户密钥',
cloudFiles: '云文件',
cloudFileBrowser: '云存储文件浏览',
cloudFileBrowserHelper: '浏览云存储账号中的文件,并同步到本地存储',
syncToLocal: '同步到本地',
syncToLocalPath: '本地目标路径',
syncToLocalSuccess: '文件已成功同步到本地存储',
cloudFilePath: '云文件路径',
address: '地址',
path: '路径',
backupJump: '未在当前备份列表中的备份文件,请尝试从文件目录中下载后导入备份。',
Expand Down
Loading
Loading