Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions agent/app/api/v2/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,47 @@ func (b *BaseApi) GetAgentOverview(c *gin.Context) {
helper.SuccessWithData(c, res)
}

// @Tags AI
// @Summary Get Hermes chat sessions
// @Accept json
// @Param request body dto.AgentIDReq true "request"
// @Success 200 {array} dto.AgentHermesChatSessionItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/hermes/chat/sessions [post]
func (b *BaseApi) GetHermesChatSessions(c *gin.Context) {
var req dto.AgentIDReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := agentService.GetHermesChatSessions(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, res)
}

// @Tags AI
// @Summary Rename Hermes chat session
// @Accept json
// @Param request body dto.AgentHermesChatSessionRenameReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/hermes/chat/sessions/rename [post]
func (b *BaseApi) RenameHermesChatSession(c *gin.Context) {
var req dto.AgentHermesChatSessionRenameReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.RenameHermesChatSession(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}

// @Tags AI
// @Summary Get Providers
// @Success 200 {array} dto.ProviderInfo
Expand Down
15 changes: 15 additions & 0 deletions agent/app/dto/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ type AgentModelConfig struct {
Fallbacks []string `json:"fallbacks"`
}

type AgentHermesChatSessionItem struct {
ID string `json:"id"`
Title string `json:"title"`
Model string `json:"model"`
MessageCount int64 `json:"messageCount"`
StartedAt string `json:"startedAt"`
LastActive string `json:"lastActive"`
}

type AgentOverviewReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
Expand All @@ -99,6 +108,12 @@ type AgentIDReq struct {
AgentID uint `json:"agentId" validate:"required"`
}

type AgentHermesChatSessionRenameReq struct {
AgentID uint `json:"agentId" validate:"required"`
ID string `json:"id" validate:"required"`
Title string `json:"title" validate:"required"`
}

type AgentOverview struct {
Snapshot AgentOverviewSnapshot `json:"snapshot"`
}
Expand Down
2 changes: 2 additions & 0 deletions agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type IAgentService interface {
BindWebsite(req dto.AgentWebsiteBindReq) error
GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, error)
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
GetHermesChatSessions(req dto.AgentIDReq) ([]dto.AgentHermesChatSessionItem, error)
RenameHermesChatSession(req dto.AgentHermesChatSessionRenameReq) error
GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error)
GetProviders() ([]dto.ProviderInfo, error)
GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error)
Expand Down
5 changes: 4 additions & 1 deletion agent/app/service/agents_channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,10 @@ func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveRe
return err
}
if agent.AgentType == constant.AppHermesAgent {
output, err := cmd.RunDefaultWithStdoutBashC(buildHermesPairingApproveCommand(install.ContainerName, req.Type, req.PairingCode))
output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout(
"docker",
buildHermesDockerExecArgs(install.ContainerName, "pairing", "approve", req.Type, req.PairingCode)...,
)
if err != nil {
return err
}
Expand Down
9 changes: 0 additions & 9 deletions agent/app/service/agents_hermes.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,15 +555,6 @@ func extractHermesEnvBool(envMap map[string]string, key string, defaultValue boo
return strings.EqualFold(value, "true")
}

func buildHermesPairingApproveCommand(containerName string, channel string, pairingCode string) string {
return fmt.Sprintf(
"docker exec -u hermes -e HOME=/opt/data/home -e HERMES_HOME=/opt/data %s /opt/hermes/.venv/bin/hermes pairing approve %s %q",
containerName,
channel,
pairingCode,
)
}

func validateHermesPairingApproveOutput(output string) error {
text := strings.TrimSpace(output)
if text == "" {
Expand Down
115 changes: 115 additions & 0 deletions agent/app/service/agents_hermes_chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package service

import (
"database/sql"
"fmt"
"math"
"path/filepath"
"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"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)

const hermesSessionListLimit = 100

func (a AgentService) GetHermesChatSessions(req dto.AgentIDReq) ([]dto.AgentHermesChatSessionItem, error) {
agent, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return nil, err
}
if agent.AgentType != constant.AppHermesAgent {
return nil, fmt.Errorf("%s does not support", agent.AgentType)
}
return listHermesChatSessionsFromStateDB(filepath.Join(install.GetPath(), "data", "state.db"))
}

func (a AgentService) RenameHermesChatSession(req dto.AgentHermesChatSessionRenameReq) error {
agent, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
if agent.AgentType != constant.AppHermesAgent {
return fmt.Errorf("%s does not support", agent.AgentType)
}

_, err = cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout(
"docker",
buildHermesDockerExecArgs(install.ContainerName, "sessions", "rename", req.ID, req.Title)...,
)
return err
}

func listHermesChatSessionsFromStateDB(stateDBPath string) ([]dto.AgentHermesChatSessionItem, error) {
if !files.NewFileOp().Stat(stateDBPath) {
return []dto.AgentHermesChatSessionItem{}, nil
}

db, err := sql.Open("sqlite", stateDBPath)
if err != nil {
return nil, err
}
defer db.Close()

rows, err := db.Query(`
SELECT
s.id,
COALESCE(NULLIF(TRIM(s.title), ''), s.id) AS title,
COALESCE(s.model, '') AS model,
COALESCE(s.message_count, 0) AS message_count,
s.started_at,
COALESCE(MAX(m.timestamp), s.started_at) AS last_active
FROM sessions s
LEFT JOIN messages m ON m.session_id = s.id
WHERE s.source = 'cli'
GROUP BY s.id, title, model, s.message_count, s.started_at
ORDER BY last_active DESC
LIMIT ?
`, hermesSessionListLimit)
if err != nil {
return nil, err
}
defer rows.Close()

items := make([]dto.AgentHermesChatSessionItem, 0, 8)
for rows.Next() {
var item dto.AgentHermesChatSessionItem
var title sql.NullString
var model sql.NullString
var startedAt sql.NullFloat64
var lastActive sql.NullFloat64
if err := rows.Scan(&item.ID, &title, &model, &item.MessageCount, &startedAt, &lastActive); err != nil {
return nil, err
}
item.Title = strings.TrimSpace(title.String)
if item.Title == "" {
item.Title = item.ID
}
item.Model = strings.TrimSpace(model.String)
item.StartedAt = formatHermesSessionTimestamp(startedAt)
item.LastActive = formatHermesSessionTimestamp(lastActive)
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}

return items, nil
}

func formatHermesSessionTimestamp(value sql.NullFloat64) string {
if !value.Valid || value.Float64 <= 0 {
return ""
}

seconds, fraction := math.Modf(value.Float64)
return time.Unix(int64(seconds), int64(fraction*float64(time.Second))).UTC().Format(time.RFC3339)
}

func buildHermesDockerExecArgs(containerName string, hermesArgs ...string) []string {
args := []string{"exec", "-u", "hermes", containerName, "hermes"}
return append(args, hermesArgs...)
}
3 changes: 2 additions & 1 deletion agent/app/service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"sync"
"time"

"github.com/gin-gonic/gin"

Expand Down Expand Up @@ -576,7 +577,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
_ = appInstallRepo.Save(context.Background(), appInstall)
}

installTask.AddSubTask(task.GetTaskName(appInstall.Name, task.TaskInstall, task.TaskScopeApp), installApp, handleAppStatus)
installTask.AddSubTaskWithOps(task.GetTaskName(appInstall.Name, task.TaskInstall, task.TaskScopeApp), installApp, handleAppStatus, 0, time.Hour)

go func() {
if taskErr := installTask.Execute(); taskErr != nil {
Expand Down
2 changes: 2 additions & 0 deletions agent/router/ro_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents/website/bind", baseApi.BindAgentWebsite)
aiToolsRouter.POST("/agents/model/get", baseApi.GetAgentModelConfig)
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
aiToolsRouter.POST("/agents/hermes/chat/sessions", baseApi.GetHermesChatSessions)
aiToolsRouter.POST("/agents/hermes/chat/sessions/rename", baseApi.RenameHermesChatSession)
aiToolsRouter.POST("/agents/overview", baseApi.GetAgentOverview)
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/api/interface/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,21 @@ export namespace AI {
fallbacks: string[];
}

export interface AgentHermesChatSessionItem {
id: string;
title: string;
model: string;
messageCount: number;
startedAt: string;
lastActive: string;
}

export interface AgentHermesChatSessionRenameReq {
agentId: number;
id: string;
title: string;
}

export interface AgentOverviewReq {
agentId: number;
}
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/api/modules/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
return http.post(`/ai/agents/model/update`, req);
};

export const getAgentHermesChatSessions = (req: AI.AgentIDReq) => {
return http.post<AI.AgentHermesChatSessionItem[]>(`/ai/agents/hermes/chat/sessions`, req);
};

export const renameAgentHermesChatSession = (req: AI.AgentHermesChatSessionRenameReq) => {
return http.post(`/ai/agents/hermes/chat/sessions/rename`, req);
};

export const getAgentOverview = (req: AI.AgentOverviewReq) => {
return http.post<AI.AgentOverview>(`/ai/agents/overview`, req, TimeoutEnum.T_5M);
};
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/terminal/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,9 @@ onBeforeUnmount(() => {

.ai-notice-fade-enter-active,
.ai-notice-fade-leave-active {
transition: opacity 180ms ease, transform 180ms ease;
transition:
opacity 180ms ease,
transform 180ms ease;
}

.ai-mask-fade-enter-active,
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,15 @@ const message = {
skillCount: 'Skills',
jobCount: 'Scheduled Jobs',
sessionCount: 'Sessions',
hermesChatAction: 'Chat',
hermesChatTitle: 'Hermes Chat',
hermesChatDialogTitle: 'Hermes Chat - {0}',
hermesChatNewChat: 'New Chat',
hermesChatNoSessions: 'No sessions',
hermesChatMessageCount: '{0} msgs',
hermesChatEmptyHint: 'Select a session or click New Chat',
hermesChatTitlePlaceholder: 'Enter session title',
hermesChatRenameSuccess: 'Session title updated',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,15 @@ const message = {
skillCount: 'Habilidades',
jobCount: 'Tareas programadas',
sessionCount: 'Sesiones',
hermesChatAction: 'Conversación',
hermesChatTitle: 'Conversación de Hermes',
hermesChatDialogTitle: 'Conversación de Hermes - {0}',
hermesChatNewChat: 'Nueva conversación',
hermesChatNoSessions: 'No hay sesiones',
hermesChatMessageCount: '{0} mensajes',
hermesChatEmptyHint: 'Seleccione una sesión a la izquierda o haga clic en Nueva conversación',
hermesChatTitlePlaceholder: 'Introduzca el título de la sesión',
hermesChatRenameSuccess: 'El título de la sesión se ha actualizado',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,15 @@ const message = {
skillCount: '技能数',
jobCount: '定期タスク数',
sessionCount: 'セッション数',
hermesChatAction: '会話',
hermesChatTitle: 'Hermes 会話',
hermesChatDialogTitle: 'Hermes 会話 - {0}',
hermesChatNewChat: '新しい会話',
hermesChatNoSessions: 'セッションはありません',
hermesChatMessageCount: '{0}件',
hermesChatEmptyHint: '左側のセッションを選択するか、「新しい会話」をクリックしてください',
hermesChatTitlePlaceholder: 'セッションタイトルを入力してください',
hermesChatRenameSuccess: 'セッションタイトルを更新しました',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,15 @@ const message = {
skillCount: '기술 수',
jobCount: '예약 작업 수',
sessionCount: '세션 수',
hermesChatAction: '대화',
hermesChatTitle: 'Hermes 대화',
hermesChatDialogTitle: 'Hermes 대화 - {0}',
hermesChatNewChat: '새 대화',
hermesChatNoSessions: '세션이 없습니다',
hermesChatMessageCount: '{0}개',
hermesChatEmptyHint: '왼쪽 세션을 선택하거나 새 대화를 클릭하세요',
hermesChatTitlePlaceholder: '세션 제목을 입력하세요',
hermesChatRenameSuccess: '세션 제목이 업데이트되었습니다',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,15 @@ const message = {
skillCount: 'Bilangan kemahiran',
jobCount: 'Bilangan tugas berjadual',
sessionCount: 'Bilangan sesi',
hermesChatAction: 'Perbualan',
hermesChatTitle: 'Perbualan Hermes',
hermesChatDialogTitle: 'Perbualan Hermes - {0}',
hermesChatNewChat: 'Perbualan baharu',
hermesChatNoSessions: 'Tiada sesi',
hermesChatMessageCount: '{0} mesej',
hermesChatEmptyHint: 'Pilih sesi di sebelah kiri atau klik Perbualan baharu',
hermesChatTitlePlaceholder: 'Masukkan tajuk sesi',
hermesChatRenameSuccess: 'Tajuk sesi telah dikemas kini',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,15 @@ const message = {
skillCount: 'Habilidades',
jobCount: 'Tarefas agendadas',
sessionCount: 'Sessões',
hermesChatAction: 'Conversa',
hermesChatTitle: 'Conversa do Hermes',
hermesChatDialogTitle: 'Conversa do Hermes - {0}',
hermesChatNewChat: 'Nova conversa',
hermesChatNoSessions: 'Nenhuma sessão',
hermesChatMessageCount: '{0} mensagens',
hermesChatEmptyHint: 'Selecione uma sessão à esquerda ou clique em Nova conversa',
hermesChatTitlePlaceholder: 'Digite o título da sessão',
hermesChatRenameSuccess: 'O título da sessão foi atualizado',
weixin: 'Weixin',
wecom: 'WeCom',
dingtalk: 'DingTalk',
Expand Down
Loading
Loading