mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 16:00:51 +00:00
feat: Add openclaw overview button (#12319)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '釘釘',
|
||||
|
||||
@@ -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: '钉钉',
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('menu.home')" :resource="resource" size="60%" @close="handleClose">
|
||||
<div v-loading="loading" class="overview-drawer">
|
||||
<div class="toolbar">
|
||||
<el-button :loading="loading" @click="loadOverview">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
{{ $t('commons.button.refresh') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<span class="section-title">{{ $t('aiTools.agents.overviewSnapshot') }}</span>
|
||||
<el-divider class="divider" />
|
||||
<el-row :gutter="20" class="metrics-row">
|
||||
<el-col v-for="item in snapshotCards" :key="item.label" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
|
||||
<el-form-item label-position="top">
|
||||
<template #label>
|
||||
<span class="metric-label">{{ item.label }}</span>
|
||||
</template>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</div>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { getAgentOverview } from '@/api/modules/ai';
|
||||
|
||||
const { t } = useI18n();
|
||||
const drawerVisible = ref(false);
|
||||
const loading = ref(false);
|
||||
const resource = ref('');
|
||||
const agentId = ref(0);
|
||||
const data = ref<AI.AgentOverview>({
|
||||
snapshot: {
|
||||
containerStatus: '',
|
||||
appVersion: '',
|
||||
defaultModel: '',
|
||||
channelCount: 0,
|
||||
skillCount: 0,
|
||||
jobCount: 0,
|
||||
sessionCount: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const formatNumber = (value: number) => {
|
||||
return new Intl.NumberFormat().format(value || 0);
|
||||
};
|
||||
|
||||
const formatText = (value: string) => {
|
||||
return value || '-';
|
||||
};
|
||||
|
||||
const snapshotCards = computed(() => [
|
||||
{ label: t('commons.table.status'), value: formatText(data.value.snapshot.containerStatus) },
|
||||
{ label: t('aiTools.agents.appVersion'), value: formatText(data.value.snapshot.appVersion) },
|
||||
{ label: t('aiTools.agents.defaultModel'), value: formatText(data.value.snapshot.defaultModel) },
|
||||
{ label: t('aiTools.agents.channelCount'), value: formatNumber(data.value.snapshot.channelCount) },
|
||||
{ label: t('aiTools.agents.skillCount'), value: formatNumber(data.value.snapshot.skillCount) },
|
||||
{ label: t('aiTools.agents.jobCount'), value: formatNumber(data.value.snapshot.jobCount) },
|
||||
{ label: t('aiTools.agents.sessionCount'), value: formatNumber(data.value.snapshot.sessionCount) },
|
||||
]);
|
||||
|
||||
const loadOverview = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getAgentOverview({ agentId: agentId.value });
|
||||
data.value = res.data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptParams = async (row: AI.AgentItem) => {
|
||||
agentId.value = row.id;
|
||||
resource.value = row.name;
|
||||
drawerVisible.value = true;
|
||||
await loadOverview();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.overview-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
border-top: 1px var(--el-border-color) var(--el-border-style);
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.metrics-row {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -58,7 +58,7 @@
|
||||
:label="$t('aiTools.model.model')"
|
||||
show-overflow-tooltip
|
||||
prop="provider"
|
||||
min-width="120"
|
||||
min-width="150"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.agentType !== 'copaw'">
|
||||
@@ -70,7 +70,7 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.port')" prop="webUIPort" min-width="150">
|
||||
<el-table-column :label="$t('commons.table.port')" prop="webUIPort" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button icon="Position" plain size="small" @click="jumpWebUI(row)">
|
||||
{{ $t('aiTools.agents.webuiPort') }}: {{ row.webUIPort }}
|
||||
@@ -86,7 +86,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Token" min-width="80">
|
||||
<el-table-column label="Token" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-space v-if="row.agentType !== 'copaw'">
|
||||
<CopyButton :content="row.token" />
|
||||
@@ -106,10 +106,10 @@
|
||||
/>
|
||||
<fu-table-operations
|
||||
:buttons="buttons"
|
||||
min-width="220"
|
||||
min-width="200"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
:ellipsis="3"
|
||||
:ellipsis="4"
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
@@ -118,6 +118,7 @@
|
||||
<TaskLog ref="taskLogRef" @close="search" />
|
||||
<DeleteDialog ref="deleteRef" @close="search" />
|
||||
<ConfigDrawer ref="configRef" @updated="search" />
|
||||
<OverviewDrawer ref="overviewRef" />
|
||||
<AppUpgrade ref="upgradeRef" @close="search" />
|
||||
<ComposeLogs ref="composeLogRef" />
|
||||
<AgentTerminalDialog ref="dialogTerminalRef" />
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user