From a3bd39bd90434a2922972bad6a721c98b789a3f3 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 24 Mar 2026 18:29:52 +0800 Subject: [PATCH 1/3] feat: Add openclaw overview button --- agent/app/api/v2/agents.go | 21 ++ agent/app/dto/agents.go | 18 ++ agent/app/service/agents.go | 1 + agent/app/service/agents_overview.go | 182 ++++++++++++++++++ agent/router/ro_ai.go | 1 + frontend/src/api/interface/ai.ts | 18 ++ frontend/src/api/modules/ai.ts | 4 + frontend/src/lang/modules/en.ts | 6 + frontend/src/lang/modules/es-es.ts | 6 + frontend/src/lang/modules/ja.ts | 6 + frontend/src/lang/modules/ko.ts | 6 + frontend/src/lang/modules/ms.ts | 6 + frontend/src/lang/modules/pt-br.ts | 6 + frontend/src/lang/modules/ru.ts | 6 + frontend/src/lang/modules/tr.ts | 6 + frontend/src/lang/modules/zh-Hant.ts | 8 +- frontend/src/lang/modules/zh.ts | 8 +- .../ai/agents/agent/components/overview.vue | 151 +++++++++++++++ frontend/src/views/ai/agents/agent/index.vue | 15 +- 19 files changed, 470 insertions(+), 5 deletions(-) create mode 100644 agent/app/service/agents_overview.go create mode 100644 frontend/src/views/ai/agents/agent/components/overview.vue 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..a148e47399ec 100644 --- a/frontend/src/views/ai/agents/agent/index.vue +++ b/frontend/src/views/ai/agents/agent/index.vue @@ -118,6 +118,7 @@ + @@ -140,6 +141,7 @@ import RouterMenu from '@/views/ai/agents/index.vue'; import AddDialog from '@/views/ai/agents/agent/add/index.vue'; import DeleteDialog from '@/views/ai/agents/agent/delete/index.vue'; import ConfigDrawer from '@/views/ai/agents/agent/config/index.vue'; +import OverviewDrawer from '@/views/ai/agents/agent/components/overview.vue'; import AppUpgrade from '@/views/app-store/installed/upgrade/index.vue'; import TaskLog from '@/components/log/task/index.vue'; import ComposeLogs from '@/components/log/compose/index.vue'; @@ -160,6 +162,7 @@ const addRef = ref(); const taskLogRef = ref(); const deleteRef = ref(); const configRef = ref(); +const overviewRef = ref(); const upgradeRef = ref(); const composeLogRef = ref(); const dialogTerminalRef = ref(); @@ -172,6 +175,11 @@ const noApp = ref(false); const searchName = ref(''); const buttons = [ + { + label: i18n.global.t('menu.home'), + click: (row: AI.AgentItem) => openOverview(row), + show: (row: AI.AgentItem) => row.agentType !== 'copaw', + }, { label: i18n.global.t('menu.config'), click: (row: AI.AgentItem) => openConfig(row), @@ -366,12 +374,13 @@ const onResetToken = async (row: AI.AgentItem) => { }; const openConfig = (row: AI.AgentItem) => { - if (row.agentType === 'copaw') { - return; - } configRef.value?.open(row); }; +const openOverview = (row: AI.AgentItem) => { + overviewRef.value?.acceptParams(row); +}; + const openUpgrade = async (row: AI.AgentItem) => { const res = await searchAppInstalled({ page: 1, pageSize: 200, name: row.name, update: true }); const appInstall = (res.data.items || []).find((item: App.AppInstallDto) => item.id === row.appInstallId); From 4bbfee6dffbad3b171632bc7a141a4e6d8c1f9a2 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Tue, 24 Mar 2026 18:36:05 +0800 Subject: [PATCH 2/3] style: change agent table columns min-width --- frontend/src/views/ai/agents/agent/index.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/ai/agents/agent/index.vue b/frontend/src/views/ai/agents/agent/index.vue index a148e47399ec..efefcd0be87e 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" >