Skip to content
Merged
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
21 changes: 21 additions & 0 deletions agent/app/api/v2/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ func (b *BaseApi) UpdateAgentModelConfig(c *gin.Context) {
helper.Success(c)
}

// @Tags AI
// @Summary Get Agent overview
// @Accept json
// @Param request body dto.AgentOverviewReq true "request"
// @Success 200 {object} dto.AgentOverview
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/overview [post]
func (b *BaseApi) GetAgentOverview(c *gin.Context) {
var req dto.AgentOverviewReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := agentService.GetOverview(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, res)
}

// @Tags AI
// @Summary Get Providers
// @Success 200 {array} dto.ProviderInfo
Expand Down
18 changes: 18 additions & 0 deletions agent/app/dto/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ type AgentModelConfigUpdateReq struct {
Model string `json:"model" validate:"required"`
}

type AgentOverviewReq struct {
AgentID uint `json:"agentId" validate:"required"`
}

type AgentOverview struct {
Snapshot AgentOverviewSnapshot `json:"snapshot"`
}

type AgentOverviewSnapshot struct {
ContainerStatus string `json:"containerStatus"`
AppVersion string `json:"appVersion"`
DefaultModel string `json:"defaultModel"`
ChannelCount int `json:"channelCount"`
SkillCount int `json:"skillCount"`
JobCount int `json:"jobCount"`
SessionCount int `json:"sessionCount"`
}

type AgentAccountModel struct {
RecordID uint `json:"recordId"`
ID string `json:"id"`
Expand Down
1 change: 1 addition & 0 deletions agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type IAgentService interface {
Delete(req dto.AgentDeleteReq) error
ResetToken(req dto.AgentTokenResetReq) error
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error)
GetProviders() ([]dto.ProviderInfo, error)
GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error)
UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error
Expand Down
182 changes: 182 additions & 0 deletions agent/app/service/agents_overview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package service

import (
"encoding/json"
"fmt"
"strings"
"time"

"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)

const (
openclawCronJobsPath = "/home/node/.openclaw/cron/jobs.json"
)

func (a AgentService) GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error) {
agent, install, conf, err := a.loadAgentConfig(req.AgentID)
if err != nil {
return nil, err
}
if agent.AgentType != constant.AppOpenclaw {
return nil, fmt.Errorf("copaw does not support overview")
}

overview := &dto.AgentOverview{
Snapshot: dto.AgentOverviewSnapshot{
ContainerStatus: install.Status,
AppVersion: install.Version,
DefaultModel: extractOpenclawDefaultModel(conf),
ChannelCount: countOpenclawConfiguredChannels(conf),
},
}
if overview.Snapshot.DefaultModel == "" {
overview.Snapshot.DefaultModel = agent.Model
}
if install.Status != constant.StatusRunning {
return overview, nil
}

skillCount, err := loadOpenclawOverviewSkillStats(install.ContainerName)
if err == nil {
overview.Snapshot.SkillCount = skillCount
}

sessionCount, err := loadOpenclawOverviewSessionCount(install.ContainerName)
if err == nil {
overview.Snapshot.SessionCount = sessionCount
}

jobCount, err := loadOpenclawOverviewJobCount(install.ContainerName)
if err == nil {
overview.Snapshot.JobCount = jobCount
}

return overview, nil
}

func extractOpenclawDefaultModel(conf map[string]interface{}) string {
agents, ok := conf["agents"].(map[string]interface{})
if !ok {
return ""
}
defaults, ok := agents["defaults"].(map[string]interface{})
if !ok {
return ""
}
model, ok := defaults["model"].(map[string]interface{})
if !ok {
return ""
}
primary, _ := model["primary"].(string)
return strings.TrimSpace(primary)
}

func countOpenclawConfiguredChannels(conf map[string]interface{}) int {
channels, ok := conf["channels"].(map[string]interface{})
if !ok {
return 0
}
count := 0
for _, value := range channels {
channel, ok := value.(map[string]interface{})
if !ok || len(channel) == 0 {
continue
}
count++
}
return count
}

func loadOpenclawOverviewSkillStats(containerName string) (int, error) {
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
"docker exec %s openclaw skills list --json 2>&1",
30*time.Second,
containerName,
)
if err != nil {
return 0, err
}
if strings.TrimSpace(output) == "" {
return 0, nil
}
skills, err := parseOpenclawSkillsList(output)
if err != nil {
return 0, err
}
return len(skills), nil
}

func loadOpenclawOverviewSessionCount(containerName string) (int, error) {
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
"docker exec %s openclaw sessions --all-agents --json",
20*time.Second,
containerName,
)
if err != nil {
return 0, err
}
return parseOpenclawSessionCount(output)
}

func loadOpenclawOverviewJobCount(containerName string) (int, error) {
script := fmt.Sprintf(`if [ -f %q ]; then cat %q; fi`, openclawCronJobsPath, openclawCronJobsPath)
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
"docker exec %s sh -c %q",
20*time.Second,
containerName,
script,
)
if err != nil {
return 0, err
}
return parseOpenclawCronCount(output)
}

func parseOpenclawSessionCount(output string) (int, error) {
if strings.TrimSpace(output) == "" {
return 0, nil
}
var payload interface{}
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
return 0, err
}
switch value := payload.(type) {
case []interface{}:
return len(value), nil
case map[string]interface{}:
if count, ok := value["count"].(float64); ok {
return int(count), nil
}
if sessions, ok := value["sessions"].([]interface{}); ok {
return len(sessions), nil
}
}
return 0, nil
}

func parseOpenclawCronCount(output string) (int, error) {
if strings.TrimSpace(output) == "" {
return 0, nil
}
var payload interface{}
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
return 0, err
}
switch value := payload.(type) {
case []interface{}:
return len(value), nil
case map[string]interface{}:
if total, ok := value["total"].(float64); ok {
return int(total), nil
}
if jobs, ok := value["jobs"].([]interface{}); ok {
return len(jobs), nil
}
return len(value), nil
default:
return 0, nil
}
}
1 change: 1 addition & 0 deletions agent/router/ro_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
aiToolsRouter.POST("/agents/overview", baseApi.GetAgentOverview)
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
aiToolsRouter.POST("/agents/accounts/update", baseApi.UpdateAgentAccount)
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/api/interface/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,24 @@ export namespace AI {
model: string;
}

export interface AgentOverviewReq {
agentId: number;
}

export interface AgentOverviewSnapshot {
containerStatus: string;
appVersion: string;
defaultModel: string;
channelCount: number;
skillCount: number;
jobCount: number;
sessionCount: number;
}

export interface AgentOverview {
snapshot: AgentOverviewSnapshot;
}

export interface AgentAccountModel {
recordId: number;
id: string;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/modules/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
return http.post(`/ai/agents/model/update`, req);
};

export const getAgentOverview = (req: AI.AgentOverviewReq) => {
return http.post<AI.AgentOverview>(`/ai/agents/overview`, req);
};

export const getAgentProviders = () => {
return http.get<AI.ProviderInfo[]>(`/ai/agents/providers`);
};
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,12 @@ const message = {
channelsTab: 'Channels',
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',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
Loading
Loading