diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go index 9ccd34d572e4..ab4ca8078608 100644 --- a/agent/app/api/v2/agents.go +++ b/agent/app/api/v2/agents.go @@ -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 diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index 310aac33a4ed..b951c32b0cab 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -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"` diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 0f611d19c39e..75b64daff13c 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -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 diff --git a/agent/app/service/agents_overview.go b/agent/app/service/agents_overview.go new file mode 100644 index 000000000000..17ff3d7cc1f0 --- /dev/null +++ b/agent/app/service/agents_overview.go @@ -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 + } +} diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index df112e32bd39..86be8104afca 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -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) diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index 18411d57bf5b..8404d1db312b 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -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; diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 82385a06cd82..e50b3337ee94 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -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/agents/overview`, req); +}; + export const getAgentProviders = () => { return http.get(`/ai/agents/providers`); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index e67393f7c223..400e2e0a324b 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 872904bd9c6a..158435e0bb2a 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -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', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 7a2eb2b183f3..10a76cd09821 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -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', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index f8be792ff84b..214e1d08e7ab 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -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', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 20257609175b..ccdb79a2270f 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -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', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index b490418bba9f..7b3b58a96c42 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -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', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 53f4be5af8ef..9ef6093b14c8 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -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', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 3478a69b7162..031a5bf5b9a3 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -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', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index b98f3975f885..a7cacb13f4cc 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -650,7 +650,7 @@ const message = { webuiPort: 'WebUI 埠', allowedOrigins: '訪問地址', allowedOriginsHelper: - '一行一個完整訪問地址,建議優先使用 HTTPS,例如 https://192.168.1.2:18789;未設定預設訪問地址時請手動填寫', + '一行一個完整訪問地址,建議優先使用 HTTPS,例如 https://192.168.1.2:18789.未設定預設訪問地址時請手動填寫', allowedOriginsPlaceholder: 'https://192.168.1.2:18789', allowedOriginsRequired: '請至少填寫一個訪問地址', allowedOriginsInvalid: '訪問地址格式錯誤,請輸入 http(s)://網域或IP[:埠]', @@ -682,6 +682,12 @@ const message = { switchModelSuccess: '模型切換成功', channelsTab: '頻道', configFileRestartHelper: '保存配置檔後需要立即重新啟動容器才能生效。', + overviewSnapshot: '狀態概覽', + defaultModel: '預設模型', + channelCount: '已配置頻道數量', + skillCount: '技能數量', + jobCount: '定時任務數量', + sessionCount: '會話數量', weixin: '微信', wecom: '企業微信', dingtalk: '釘釘', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index cbc021d9118e..2ad78023fecd 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -649,7 +649,7 @@ const message = { webuiPort: 'WebUI 端口', allowedOrigins: '访问地址', allowedOriginsHelper: - '一行一个完整访问地址,建议优先使用 HTTPS,例如 https://192.168.1.2:18789;未配置默认访问地址时请手动填写', + '一行一个完整访问地址,建议优先使用 HTTPS,例如 https://192.168.1.2:18789.未配置默认访问地址时请手动填写', allowedOriginsPlaceholder: 'https://192.168.1.2:18789', allowedOriginsRequired: '请至少填写一个访问地址', allowedOriginsInvalid: '访问地址格式错误,请输入 http(s)://域名或IP[:端口]', @@ -681,6 +681,12 @@ const message = { switchModelSuccess: '模型切换成功', channelsTab: '频道', configFileRestartHelper: '保存配置文件后需要重启容器才能生效。', + overviewSnapshot: '状态概览', + defaultModel: '默认模型', + channelCount: '已配置频道数量', + skillCount: '技能数量', + jobCount: '定时任务数量', + sessionCount: '会话数量', weixin: '微信', wecom: '企业微信', dingtalk: '钉钉', diff --git a/frontend/src/views/ai/agents/agent/components/overview.vue b/frontend/src/views/ai/agents/agent/components/overview.vue new file mode 100644 index 000000000000..2e05410724aa --- /dev/null +++ b/frontend/src/views/ai/agents/agent/components/overview.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/frontend/src/views/ai/agents/agent/index.vue b/frontend/src/views/ai/agents/agent/index.vue index 314e96e2d435..7ab73521221a 100644 --- a/frontend/src/views/ai/agents/agent/index.vue +++ b/frontend/src/views/ai/agents/agent/index.vue @@ -58,7 +58,7 @@ :label="$t('aiTools.model.model')" show-overflow-tooltip prop="provider" - min-width="120" + min-width="150" >