mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-26 00:00:59 +00:00
feat: support edit config file (#12313)
This commit is contained in:
@@ -680,6 +680,47 @@ func (b *BaseApi) UpdateAgentOtherConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent config file
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentConfigFileReq true "request"
|
||||
// @Success 200 {object} dto.AgentConfigFile
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/config-file/get [post]
|
||||
func (b *BaseApi) GetAgentConfigFile(c *gin.Context) {
|
||||
var req dto.AgentConfigFileReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetConfigFile(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent config file
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentConfigFileUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/config-file/update [post]
|
||||
func (b *BaseApi) UpdateAgentConfigFile(c *gin.Context) {
|
||||
var req dto.AgentConfigFileUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateConfigFile(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List Agent skills
|
||||
// @Accept json
|
||||
|
||||
@@ -354,6 +354,19 @@ type AgentOtherConfig struct {
|
||||
NPMRegistry string `json:"npmRegistry"`
|
||||
}
|
||||
|
||||
type AgentConfigFileReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentConfigFileUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentConfigFile struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type AgentSkillsReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -33,6 +34,8 @@ type IAgentService interface {
|
||||
UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error
|
||||
GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error)
|
||||
UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
GetConfigFile(req dto.AgentConfigFileReq) (*dto.AgentConfigFile, error)
|
||||
UpdateConfigFile(req dto.AgentConfigFileUpdateReq) error
|
||||
ListSkills(req dto.AgentSkillsReq) ([]dto.AgentSkillItem, error)
|
||||
UpdateSkill(req dto.AgentSkillUpdateReq) error
|
||||
|
||||
@@ -717,6 +720,46 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
return setOpenclawNPMRegistry(install.ContainerName, req.NPMRegistry)
|
||||
}
|
||||
|
||||
func (a AgentService) GetConfigFile(req dto.AgentConfigFileReq) (*dto.AgentConfigFile, error) {
|
||||
agent, _, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if agent.AgentType == constant.AppCopaw {
|
||||
return nil, fmt.Errorf("copaw does not support config file")
|
||||
}
|
||||
content, err := os.ReadFile(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AgentConfigFile{Content: string(content)}, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateConfigFile(req dto.AgentConfigFileUpdateReq) error {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if agent.AgentType == constant.AppCopaw {
|
||||
return fmt.Errorf("copaw does not support config file")
|
||||
}
|
||||
var payload interface{}
|
||||
if err := json.Unmarshal([]byte(req.Content), &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(agent.ConfigPath, []byte(req.Content), info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
||||
InstallId: install.ID,
|
||||
Operate: constant.Restart,
|
||||
})
|
||||
}
|
||||
|
||||
func getOpenclawNPMRegistry(containerName string) (string, error) {
|
||||
registry, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("docker exec %s npm get registry", 20*time.Second, containerName)
|
||||
if err != nil {
|
||||
|
||||
@@ -540,6 +540,7 @@ func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConf
|
||||
func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
qqbot := ensureChildMap(channels, "qqbot")
|
||||
delete(qqbot, "dmPolicy")
|
||||
qqbot["enabled"] = config.Enabled
|
||||
qqbot["allowFrom"] = []string{"*"}
|
||||
qqbot["appId"] = strings.TrimSpace(config.AppID)
|
||||
@@ -547,6 +548,7 @@ func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
delete(entries, "qqbot")
|
||||
qqbotEntry := ensureChildMap(entries, "openclaw-qqbot")
|
||||
qqbotEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/security/update", baseApi.UpdateAgentSecurityConfig)
|
||||
aiToolsRouter.POST("/agents/other/get", baseApi.GetAgentOtherConfig)
|
||||
aiToolsRouter.POST("/agents/other/update", baseApi.UpdateAgentOtherConfig)
|
||||
aiToolsRouter.POST("/agents/config-file/get", baseApi.GetAgentConfigFile)
|
||||
aiToolsRouter.POST("/agents/config-file/update", baseApi.UpdateAgentConfigFile)
|
||||
aiToolsRouter.POST("/agents/skills/list", baseApi.ListAgentSkills)
|
||||
aiToolsRouter.POST("/agents/skills/update", baseApi.UpdateAgentSkill)
|
||||
aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing)
|
||||
|
||||
@@ -588,6 +588,19 @@ export namespace AI {
|
||||
npmRegistry: string;
|
||||
}
|
||||
|
||||
export interface AgentConfigFileReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentConfigFile {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface AgentConfigFileUpdateReq {
|
||||
agentId: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface AgentSkillsReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
@@ -229,6 +229,14 @@ export const updateAgentOtherConfig = (req: AI.AgentOtherConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/other/update`, req);
|
||||
};
|
||||
|
||||
export const getAgentConfigFile = (req: AI.AgentConfigFileReq) => {
|
||||
return http.post<AI.AgentConfigFile>(`/ai/agents/config-file/get`, req);
|
||||
};
|
||||
|
||||
export const updateAgentConfigFile = (req: AI.AgentConfigFileUpdateReq) => {
|
||||
return http.post(`/ai/agents/config-file/update`, req);
|
||||
};
|
||||
|
||||
export const listAgentSkills = (req: AI.AgentSkillsReq) => {
|
||||
return http.post<AI.AgentSkillItem[]>(`/ai/agents/skills/list`, req);
|
||||
};
|
||||
|
||||
@@ -715,6 +715,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -723,6 +723,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -716,6 +716,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -708,6 +708,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -723,6 +723,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -718,6 +718,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -715,6 +715,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -719,6 +719,8 @@ const message = {
|
||||
skillsGroupWorkspace: 'Workspace',
|
||||
switchModelSuccess: 'Model switched successfully',
|
||||
channelsTab: 'Channels',
|
||||
configFileRestartHelper:
|
||||
'Saving the config file requires immediately restarting the container to take effect.',
|
||||
weixin: 'Weixin',
|
||||
wecom: 'WeCom',
|
||||
dingtalk: 'DingTalk',
|
||||
|
||||
@@ -681,6 +681,7 @@ const message = {
|
||||
skillsGroupWorkspace: '工作區',
|
||||
switchModelSuccess: '模型切換成功',
|
||||
channelsTab: '頻道',
|
||||
configFileRestartHelper: '保存配置檔後需要立即重新啟動容器才能生效。',
|
||||
weixin: '微信',
|
||||
wecom: '企業微信',
|
||||
dingtalk: '釘釘',
|
||||
|
||||
@@ -680,6 +680,7 @@ const message = {
|
||||
skillsGroupWorkspace: '工作区',
|
||||
switchModelSuccess: '模型切换成功',
|
||||
channelsTab: '频道',
|
||||
configFileRestartHelper: '保存配置文件后需要重启容器才能生效。',
|
||||
weixin: '微信',
|
||||
wecom: '企业微信',
|
||||
dingtalk: '钉钉',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<el-tab-pane :label="t('aiTools.agents.otherTab')" name="other">
|
||||
<OtherTab ref="otherRef" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('website.source')" name="configFile">
|
||||
<ConfigFileTab ref="configFileRef" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
@@ -14,12 +17,14 @@ import { nextTick, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SecurityTab from './settings/security.vue';
|
||||
import OtherTab from './settings/other.vue';
|
||||
import ConfigFileTab from './settings/config-file.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const activeTab = ref('security');
|
||||
const agentId = ref(0);
|
||||
const securityRef = ref();
|
||||
const otherRef = ref();
|
||||
const configFileRef = ref();
|
||||
|
||||
const loadCurrentTab = async () => {
|
||||
if (agentId.value <= 0) {
|
||||
@@ -32,6 +37,10 @@ const loadCurrentTab = async () => {
|
||||
}
|
||||
if (activeTab.value === 'other') {
|
||||
await otherRef.value?.load(agentId.value);
|
||||
return;
|
||||
}
|
||||
if (activeTab.value === 'configFile') {
|
||||
await configFileRef.value?.load(agentId.value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<CodemirrorPro
|
||||
v-model="form.content"
|
||||
mode="json"
|
||||
:lineWrapping="true"
|
||||
:heightDiff="360"
|
||||
:placeholder="t('commons.msg.noneData')"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" :loading="saving" @click="confirmSave">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="saveConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { getAgentConfigFile, updateAgentConfigFile } from '@/api/modules/ai';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const { t } = useI18n();
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const agentId = ref(0);
|
||||
const confirmDialogRef = ref();
|
||||
|
||||
const form = reactive({
|
||||
content: '',
|
||||
});
|
||||
|
||||
const load = async (id: number) => {
|
||||
agentId.value = id;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getAgentConfigFile({ agentId: id });
|
||||
form.content = res.data?.content || '';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmSave = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSON.parse(form.content);
|
||||
} catch {
|
||||
MsgError(t('commons.rule.formatErr'));
|
||||
return;
|
||||
}
|
||||
confirmDialogRef.value?.acceptParams({
|
||||
header: t('database.confChange'),
|
||||
operationInfo: t('aiTools.agents.configFileRestartHelper'),
|
||||
submitInputInfo: t('database.restartNow'),
|
||||
});
|
||||
};
|
||||
|
||||
const saveConfig = async () => {
|
||||
if (!agentId.value) {
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await updateAgentConfigFile({
|
||||
agentId: agentId.value,
|
||||
content: form.content,
|
||||
});
|
||||
MsgSuccess(t('aiTools.agents.saveSuccess'));
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
load,
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user