feat: add Feishu configuration for OpenClaw (#11832)

This commit is contained in:
CityFun
2026-02-09 06:39:02 +00:00
committed by GitHub
parent b8b873836d
commit 4b1f643c70
21 changed files with 698 additions and 6 deletions
+61
View File
@@ -189,3 +189,64 @@ func (b *BaseApi) DeleteAgentAccount(c *gin.Context) {
}
helper.Success(c)
}
// @Tags AI
// @Summary Get Agent Feishu channel config
// @Accept json
// @Param request body dto.AgentFeishuConfigReq true "request"
// @Success 200 {object} dto.AgentFeishuConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/get [post]
func (b *BaseApi) GetAgentFeishuConfig(c *gin.Context) {
var req dto.AgentFeishuConfigReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := agentService.GetFeishuConfig(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags AI
// @Summary Update Agent Feishu channel config
// @Accept json
// @Param request body dto.AgentFeishuConfigUpdateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/update [post]
func (b *BaseApi) UpdateAgentFeishuConfig(c *gin.Context) {
var req dto.AgentFeishuConfigUpdateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.UpdateFeishuConfig(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Approve Agent Feishu pairing code
// @Accept json
// @Param request body dto.AgentFeishuPairingApproveReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/approve [post]
func (b *BaseApi) ApproveAgentFeishuPairing(c *gin.Context) {
var req dto.AgentFeishuPairingApproveReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.ApproveFeishuPairing(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
+27
View File
@@ -44,6 +44,7 @@ type AgentItem struct {
BridgePort int `json:"bridgePort"`
Path string `json:"path"`
ConfigPath string `json:"configPath"`
Upgradable bool `json:"upgradable"`
CreatedAt time.Time `json:"createdAt"`
}
@@ -108,3 +109,29 @@ type ProviderInfo struct {
BaseURL string `json:"baseUrl"`
Models []ProviderModelInfo `json:"models"`
}
type AgentFeishuConfigReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
type AgentFeishuConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
BotName string `json:"botName" validate:"required"`
AppID string `json:"appId" validate:"required"`
AppSecret string `json:"appSecret" validate:"required"`
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy" validate:"required"`
}
type AgentFeishuPairingApproveReq struct {
AgentID uint `json:"agentId" validate:"required"`
PairingCode string `json:"pairingCode" validate:"required"`
}
type AgentFeishuConfig struct {
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy"`
BotName string `json:"botName"`
AppID string `json:"appId"`
AppSecret string `json:"appSecret"`
}
+191 -1
View File
@@ -20,6 +20,8 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
@@ -35,6 +37,9 @@ type IAgentService interface {
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
VerifyAccount(req dto.AgentAccountVerifyReq) error
DeleteAccount(req dto.AgentAccountDeleteReq) error
GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error)
UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error
ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error
}
func NewIAgentService() IAgentService {
@@ -172,7 +177,9 @@ func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, erro
for _, item := range list {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
envMap := readInstallEnv(appInstall.Env)
items = append(items, buildAgentItem(&item, &appInstall, envMap))
agentItem := buildAgentItem(&item, &appInstall, envMap)
agentItem.Upgradable = checkAgentUpgradable(appInstall)
items = append(items, agentItem)
}
return count, items, nil
}
@@ -349,6 +356,156 @@ func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
return agentAccountRepo.DeleteByID(req.ID)
}
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
agent, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return nil, err
}
_ = install
conf, err := readOpenclawConfig(agent.ConfigPath)
if err != nil {
return nil, err
}
result := extractFeishuConfig(conf)
return &result, nil
}
func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error {
agent, _, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
conf, err := readOpenclawConfig(agent.ConfigPath)
if err != nil {
return err
}
if req.DmPolicy == "" {
req.DmPolicy = "pairing"
}
setFeishuConfig(conf, dto.AgentFeishuConfig{
Enabled: req.Enabled,
DmPolicy: req.DmPolicy,
BotName: req.BotName,
AppID: req.AppID,
AppSecret: req.AppSecret,
})
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
return err
}
return nil
}
func (a AgentService) ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error {
_, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
if err := cmd.RunDefaultBashCf(
"docker exec %s openclaw pairing approve feishu %q",
install.ContainerName,
strings.TrimSpace(req.PairingCode),
); err != nil {
return err
}
return nil
}
func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
agent, err := agentRepo.GetFirst(repo.WithByID(agentID))
if err != nil {
return nil, nil, err
}
if agent.AppInstallID == 0 {
return nil, nil, buserr.New("ErrRecordNotFound")
}
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
if err != nil {
return nil, nil, err
}
return agent, &install, nil
}
func readOpenclawConfig(configPath string) (map[string]interface{}, error) {
if strings.TrimSpace(configPath) == "" {
return nil, buserr.New("ErrRecordNotFound")
}
fileOp := files.NewFileOp()
content, err := fileOp.GetContent(configPath)
if err != nil {
return nil, err
}
conf := map[string]interface{}{}
if err := json.Unmarshal(content, &conf); err != nil {
return nil, err
}
return conf, nil
}
func writeOpenclawConfigRaw(configPath string, conf map[string]interface{}) error {
payload, err := json.MarshalIndent(conf, "", " ")
if err != nil {
return err
}
fileOp := files.NewFileOp()
return fileOp.SaveFile(configPath, string(payload), 0600)
}
func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"}
channels, ok := conf["channels"].(map[string]interface{})
if !ok {
return result
}
feishu, ok := channels["feishu"].(map[string]interface{})
if !ok {
return result
}
if enabled, ok := feishu["enabled"].(bool); ok {
result.Enabled = enabled
}
if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
result.DmPolicy = dmPolicy
}
accounts, ok := feishu["accounts"].(map[string]interface{})
if !ok {
return result
}
main, ok := accounts["main"].(map[string]interface{})
if !ok {
return result
}
if appID, ok := main["appId"].(string); ok {
result.AppID = appID
}
if appSecret, ok := main["appSecret"].(string); ok {
result.AppSecret = appSecret
}
if botName, ok := main["botName"].(string); ok {
result.BotName = botName
}
return result
}
func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) {
channels, ok := conf["channels"].(map[string]interface{})
if !ok {
channels = map[string]interface{}{}
conf["channels"] = channels
}
feishu := map[string]interface{}{
"enabled": config.Enabled,
"dmPolicy": config.DmPolicy,
"accounts": map[string]interface{}{
"main": map[string]interface{}{
"appId": config.AppID,
"appSecret": config.AppSecret,
"botName": config.BotName,
},
},
}
channels["feishu"] = feishu
}
func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
agents, err := agentRepo.List(repo.WithByAccountID(account.ID))
if err != nil {
@@ -436,6 +593,39 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
return item
}
func checkAgentUpgradable(install model.AppInstall) bool {
if install.ID == 0 || install.Version == "" || install.Version == "latest" {
return false
}
if install.App.ID == 0 {
return false
}
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(install.App.ID))
if err != nil || len(details) == 0 {
return false
}
versions := make([]string, 0, len(details))
for _, item := range details {
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(item.ID), appIgnoreUpgradeRepo.WithScope("version"))
if len(ignores) > 0 {
continue
}
if common.IsCrossVersion(install.Version, item.Version) && !install.App.CrossVersionUpdate {
continue
}
versions = append(versions, item.Version)
}
if len(versions) == 0 {
return false
}
versions = common.GetSortedVersions(versions)
lastVersion := versions[0]
if common.IsCrossVersion(install.Version, lastVersion) {
return install.App.CrossVersionUpdate
}
return common.CompareVersion(lastVersion, install.Version)
}
func (a AgentService) waitAndDeleteAgent(agentID uint, appInstallID uint) {
if appInstallID == 0 {
_ = agentRepo.DeleteByID(agentID)
+3
View File
@@ -49,5 +49,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents/accounts/search", baseApi.PageAgentAccounts)
aiToolsRouter.POST("/agents/accounts/verify", baseApi.VerifyAgentAccount)
aiToolsRouter.POST("/agents/accounts/delete", baseApi.DeleteAgentAccount)
aiToolsRouter.POST("/agents/channel/feishu/get", baseApi.GetAgentFeishuConfig)
aiToolsRouter.POST("/agents/channel/feishu/update", baseApi.UpdateAgentFeishuConfig)
aiToolsRouter.POST("/agents/channel/feishu/approve", baseApi.ApproveAgentFeishuPairing)
}
}
+27
View File
@@ -278,6 +278,7 @@ export namespace AI {
bridgePort: number;
path: string;
configPath: string;
upgradable: boolean;
createdAt: string;
}
@@ -343,4 +344,30 @@ export namespace AI {
export interface AgentAccountDeleteReq {
id: number;
}
export interface AgentFeishuConfigReq {
agentId: number;
}
export interface AgentFeishuConfig {
enabled: boolean;
dmPolicy: string;
botName: string;
appId: string;
appSecret: string;
}
export interface AgentFeishuConfigUpdateReq {
agentId: number;
enabled: boolean;
dmPolicy: string;
botName: string;
appId: string;
appSecret: string;
}
export interface AgentFeishuPairingApproveReq {
agentId: number;
pairingCode: string;
}
}
+12
View File
@@ -127,3 +127,15 @@ export const verifyAgentAccount = (req: AI.AgentAccountVerifyReq) => {
export const deleteAgentAccount = (req: AI.AgentAccountDeleteReq) => {
return http.post(`/ai/agents/accounts/delete`, req);
};
export const getAgentFeishuConfig = (req: AI.AgentFeishuConfigReq) => {
return http.post<AI.AgentFeishuConfig>(`/ai/agents/channel/feishu/get`, req);
};
export const updateAgentFeishuConfig = (req: AI.AgentFeishuConfigUpdateReq) => {
return http.post(`/ai/agents/channel/feishu/update`, req);
};
export const approveAgentFeishuPairing = (req: AI.AgentFeishuPairingApproveReq) => {
return http.post(`/ai/agents/channel/feishu/approve`, req);
};
+15
View File
@@ -695,6 +695,21 @@ const message = {
token: 'Token',
manualModel: 'Manual input',
verified: 'Verified',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Models',
+15
View File
@@ -691,6 +691,21 @@ const message = {
token: 'Token',
manualModel: 'Entrada manual de modelo',
verified: 'Verificado',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Modelo',
+15
View File
@@ -680,6 +680,21 @@ const message = {
token: 'トークン',
manualModel: '手動入力',
verified: '検証済み',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'モデル',
+15
View File
@@ -677,6 +677,21 @@ const message = {
token: '토큰',
manualModel: '수동 입력',
verified: '검증됨',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: '모델',
+15
View File
@@ -692,6 +692,21 @@ const message = {
token: 'Token',
manualModel: 'Input manual',
verified: 'Disahkan',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Model',
+15
View File
@@ -689,6 +689,21 @@ const message = {
token: 'Token',
manualModel: 'Entrada manual',
verified: 'Verificado',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Modelo',
+15
View File
@@ -685,6 +685,21 @@ const message = {
token: 'Токен',
manualModel: 'Ручной ввод',
verified: 'Проверено',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Модель',
+15
View File
@@ -699,6 +699,21 @@ const message = {
token: 'Token',
manualModel: 'Manuel giriş',
verified: 'Doğrulandı',
configTitle: 'Configuration',
channelsTab: 'Channels',
feishu: 'Feishu',
dmPolicy: 'DM Policy',
botName: 'Bot Name',
appId: 'App ID',
appSecret: 'App Secret',
saveAndRestartGateway: 'Save and restart gateway',
pairingCode: 'Pairing Code',
pairingCodePlaceholder: 'Enter pairing code',
approvePairing: 'Approve Pairing',
feishuRequired: 'Please fill botName / appId / appSecret',
feishuSaveSuccess: 'Saved successfully',
pairingCodeRequired: 'Please enter pairing code',
pairingApproveSuccess: 'Pairing approved successfully',
},
model: {
model: 'Model',
+15
View File
@@ -667,6 +667,21 @@ const message = {
token: 'Token',
manualModel: '手動輸入模型',
verified: '驗證狀態',
configTitle: '配置',
channelsTab: '聊天渠道',
feishu: '飛書',
dmPolicy: '私聊策略',
botName: '機器人名稱',
appId: '應用 App ID',
appSecret: '應用 App Secret',
saveAndRestartGateway: '保存並重啟網關',
pairingCode: '配對碼',
pairingCodePlaceholder: '請輸入配對碼',
approvePairing: '批准配對',
feishuRequired: '請填寫 botName / appId / appSecret',
feishuSaveSuccess: '保存成功',
pairingCodeRequired: '請輸入配對碼',
pairingApproveSuccess: '配對成功',
},
model: {
model: '模型',
+15
View File
@@ -669,6 +669,21 @@ const message = {
token: 'Token',
manualModel: '手动输入模型',
verified: '验证状态',
configTitle: '配置',
channelsTab: '聊天渠道',
feishu: '飞书',
dmPolicy: '私聊策略',
botName: '机器人名称',
appId: '应用 App ID',
appSecret: '应用 App Secret',
saveAndRestartGateway: '保存并重启网关',
pairingCode: '配对码',
pairingCodePlaceholder: '请输入配对码',
approvePairing: '批准配对',
feishuRequired: '请填写 botName / appId / appSecret',
feishuSaveSuccess: '保存成功',
pairingCodeRequired: '请输入配对码',
pairingApproveSuccess: '配对成功',
},
model: {
model: '模型',
@@ -0,0 +1,60 @@
<template>
<DrawerPro v-model="open" :header="header" size="large" @close="handleClose">
<el-tabs v-model="activeTab" tab-position="left" class="config-tabs" @tab-click="handleTabClick">
<el-tab-pane :label="t('aiTools.agents.channelsTab')" name="channels">
<ChannelsTab ref="channelsRef" />
</el-tab-pane>
</el-tabs>
</DrawerPro>
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue';
import type { TabsPaneContext } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { AI } from '@/api/interface/ai';
import ChannelsTab from './tabs/channels.vue';
const { t } = useI18n();
const open = ref(false);
const activeTab = ref('channels');
const header = ref('');
const agentId = ref(0);
const channelsRef = ref();
const loadChannels = async () => {
if (agentId.value <= 0) {
return;
}
await nextTick();
await channelsRef.value?.load(agentId.value);
};
const handleClose = () => {
activeTab.value = 'channels';
};
const handleTabClick = async (pane: TabsPaneContext) => {
if (pane.paneName === 'channels' && agentId.value > 0) {
await loadChannels();
}
};
const openDrawer = async (agent: AI.AgentItem) => {
agentId.value = agent.id;
header.value = `${agent.name} - ${t('aiTools.agents.configTitle')}`;
activeTab.value = 'channels';
open.value = true;
await loadChannels();
};
defineExpose({
open: openDrawer,
});
</script>
<style scoped lang="scss">
.config-tabs {
min-height: 440px;
}
</style>
@@ -0,0 +1,128 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
<el-form-item :label="t('aiTools.agents.feishu')">
<el-switch v-model="form.enabled" />
</el-form-item>
<el-form-item :label="t('aiTools.agents.dmPolicy')" prop="dmPolicy">
<el-select v-model="form.dmPolicy">
<el-option label="pairing" value="pairing" />
</el-select>
</el-form-item>
<el-form-item :label="t('aiTools.agents.botName')" prop="botName">
<el-input v-model="form.botName" />
</el-form-item>
<el-form-item :label="t('aiTools.agents.appId')" prop="appId">
<el-input v-model="form.appId" />
</el-form-item>
<el-form-item :label="t('aiTools.agents.appSecret')" prop="appSecret">
<el-input v-model="form.appSecret" type="password" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="saveChannel">
{{ t('commons.button.save') }}
</el-button>
</el-form-item>
<el-divider />
<el-form-item :label="t('aiTools.agents.pairingCode')">
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="approving" @click="approvePairing">
{{ t('aiTools.agents.approvePairing') }}
</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { AI } from '@/api/interface/ai';
import { approveAgentFeishuPairing, getAgentFeishuConfig, updateAgentFeishuConfig } from '@/api/modules/ai';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import { Rules } from '@/global/form-rules';
const { t } = useI18n();
const saving = ref(false);
const approving = ref(false);
const agentId = ref(0);
const pairingCode = ref('');
const formRef = ref<FormInstance>();
const form = reactive<AI.AgentFeishuConfig>({
enabled: true,
dmPolicy: 'pairing',
botName: '',
appId: '',
appSecret: '',
});
const rules = reactive({
dmPolicy: [Rules.requiredSelect],
botName: [Rules.requiredInput],
appId: [Rules.requiredInput],
appSecret: [Rules.requiredInput],
});
const load = async (id: number) => {
agentId.value = id;
pairingCode.value = '';
const res = await getAgentFeishuConfig({ agentId: id });
Object.assign(form, res.data || {});
if (!form.dmPolicy) {
form.dmPolicy = 'pairing';
}
};
const saveChannel = async () => {
if (!agentId.value) {
return;
}
if (!formRef.value) {
return;
}
await formRef.value.validate();
saving.value = true;
try {
await updateAgentFeishuConfig({
agentId: agentId.value,
enabled: form.enabled,
dmPolicy: form.dmPolicy || 'pairing',
botName: form.botName,
appId: form.appId,
appSecret: form.appSecret,
});
MsgSuccess(t('aiTools.agents.feishuSaveSuccess'));
} finally {
saving.value = false;
}
};
const approvePairing = async () => {
if (!agentId.value) {
return;
}
if (!pairingCode.value) {
MsgWarning(t('aiTools.agents.pairingCodeRequired'));
return;
}
approving.value = true;
try {
await approveAgentFeishuPairing({
agentId: agentId.value,
pairingCode: pairingCode.value,
});
MsgSuccess(t('aiTools.agents.pairingApproveSuccess'));
pairingCode.value = '';
} finally {
approving.value = false;
}
};
defineExpose({
load,
});
</script>
+39 -3
View File
@@ -23,7 +23,14 @@
<Status :status="row.status" />
</template>
</el-table-column>
<el-table-column :label="$t('aiTools.agents.appVersion')" prop="appVersion" min-width="100" />
<el-table-column :label="$t('aiTools.agents.appVersion')" prop="appVersion" min-width="140">
<template #default="{ row }">
<span>{{ row.appVersion }}</span>
<el-button v-if="row.upgradable" link type="primary" class="ml-1" @click="openUpgrade(row)">
{{ $t('commons.button.upgrade') }}
</el-button>
</template>
</el-table-column>
<el-table-column
:label="$t('aiTools.model.model')"
show-overflow-tooltip
@@ -66,7 +73,7 @@
min-width="220"
:label="$t('commons.table.operate')"
fixed="right"
:ellipsis="2"
:ellipsis="3"
/>
</ComplexTable>
</template>
@@ -74,6 +81,8 @@
<AddDialog ref="addRef" @search="search" @task="openTaskLog" />
<TaskLog ref="taskLogRef" @close="search" />
<DeleteDialog ref="deleteRef" @close="search" />
<ConfigDrawer ref="configRef" />
<AppUpgrade ref="upgradeRef" @close="search" />
<ComposeLogs ref="composeLogRef" />
<TerminalDialog ref="dialogTerminalRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
@@ -83,14 +92,17 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { pageAgents } from '@/api/modules/ai';
import { installedOp } from '@/api/modules/app';
import { installedOp, searchAppInstalled } from '@/api/modules/app';
import { AI } from '@/api/interface/ai';
import { App } from '@/api/interface/app';
import { SearchWithPage } from '@/api/interface';
import { dateFormat, newUUID } from '@/utils/util';
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 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';
import TerminalDialog from '@/views/container/container/terminal/index.vue';
@@ -103,6 +115,8 @@ const loading = ref(false);
const addRef = ref();
const taskLogRef = ref();
const deleteRef = ref();
const configRef = ref();
const upgradeRef = ref();
const composeLogRef = ref();
const dialogTerminalRef = ref();
const dialogPortJumpRef = ref();
@@ -124,6 +138,10 @@ const getProviderLabel = (value: string) => {
};
const buttons = [
{
label: i18n.global.t('commons.button.set'),
click: (row: AI.AgentItem) => openConfig(row),
},
{
label: i18n.global.t('menu.terminal'),
click: (row: AI.AgentItem) => openTerminal(row),
@@ -146,6 +164,11 @@ const buttons = [
label: i18n.global.t('commons.operate.restart'),
click: (row: AI.AgentItem) => onOperate(row, 'restart'),
},
{
label: i18n.global.t('commons.button.upgrade'),
click: (row: AI.AgentItem) => openUpgrade(row),
disabled: (row: AI.AgentItem) => !row.upgradable,
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: AI.AgentItem) => onDelete(row),
@@ -232,6 +255,19 @@ const onDelete = (row: AI.AgentItem) => {
deleteRef.value?.acceptParams(row.id, row.name);
};
const openConfig = (row: AI.AgentItem) => {
configRef.value?.open(row);
};
const openUpgrade = async (row: AI.AgentItem) => {
const res = await searchAppInstalled({ page: 1, pageSize: 200, name: row.name });
const appInstall = (res.data.items || []).find((item: App.AppInstallDto) => item.id === row.appInstallId);
if (!appInstall) {
return;
}
upgradeRef.value?.acceptParams(appInstall, 'upgrade');
};
onMounted(async () => {
await search();
});
@@ -293,7 +293,6 @@ const acceptParams = async (props: ParamProps) => {
await get();
open.value = true;
openConfig.value = false;
console.log('params', params.value);
};
const handleClose = () => {
@@ -545,7 +545,6 @@ const passkeyLogin = async () => {
} catch (res: any) {
if (res?.message) {
MsgError(i18n.t('commons.login.passkeyFailed'));
console.log(res.message);
}
} finally {
isLoggingIn = false;