Files
nginx-ui/api/llm/session.go
T
2025-09-02 08:01:09 +00:00

331 lines
7.3 KiB
Go

package llm
import (
"net/http"
"path/filepath"
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/llm"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
)
// GetLLMSessions returns LLM sessions with optional filtering
func GetLLMSessions(c *gin.Context) {
g := query.LLMSession
query := g.Order(g.UpdatedAt.Desc())
// Filter by path if provided
if path := c.Query("path"); path != "" {
if !helper.IsUnderDirectory(path, nginx.GetConfPath()) {
c.JSON(http.StatusForbidden, gin.H{
"message": "path is not under the nginx conf path",
})
return
}
query = query.Where(g.Path.Eq(path))
}
sessions, err := query.Find()
if err != nil {
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, sessions)
}
// GetLLMSession returns a single session by session_id
func GetLLMSession(c *gin.Context) {
sessionID := c.Param("session_id")
g := query.LLMSession
session, err := g.Where(g.SessionID.Eq(sessionID)).First()
if err != nil {
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, session)
}
// CreateLLMSession creates a new LLM session
func CreateLLMSession(c *gin.Context) {
var json struct {
Title string `json:"title" binding:"required"`
Path string `json:"path"`
}
if !cosy.BindAndValid(c, &json) {
return
}
// Validate path if provided
if json.Path != "" && !helper.IsUnderDirectory(json.Path, nginx.GetConfPath()) {
c.JSON(http.StatusForbidden, gin.H{
"message": "path is not under the nginx conf path",
})
return
}
session := &model.LLMSession{
Title: json.Title,
Path: json.Path,
Messages: []openai.ChatCompletionMessage{},
MessageCount: 0,
IsActive: true,
}
g := query.LLMSession
err := g.Create(session)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, session)
}
// UpdateLLMSession updates an existing session
func UpdateLLMSession(c *gin.Context) {
sessionID := c.Param("session_id")
var json struct {
Title string `json:"title,omitempty"`
Messages []openai.ChatCompletionMessage `json:"messages,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
}
if !cosy.BindAndValid(c, &json) {
return
}
g := query.LLMSession
session, err := g.Where(g.SessionID.Eq(sessionID)).First()
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
// Update fields
if json.Title != "" {
session.Title = json.Title
}
if json.Messages != nil {
session.Messages = json.Messages
session.MessageCount = len(json.Messages)
}
if json.IsActive != nil {
session.IsActive = *json.IsActive
}
// Save the updated session
err = g.Save(session)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, session)
}
// DeleteLLMSession deletes a session by session_id
func DeleteLLMSession(c *gin.Context) {
sessionID := c.Param("session_id")
g := query.LLMSession
result, err := g.Where(g.SessionID.Eq(sessionID)).Delete()
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{
"message": "Session not found",
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Session deleted successfully",
})
}
// DuplicateLLMSession duplicates an existing session
func DuplicateLLMSession(c *gin.Context) {
sessionID := c.Param("session_id")
g := query.LLMSession
originalSession, err := g.Where(g.SessionID.Eq(sessionID)).First()
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
// Create a new session with the same content
newSession := &model.LLMSession{
Title: originalSession.Title + " (Copy)",
Path: originalSession.Path,
Messages: originalSession.Messages,
MessageCount: originalSession.MessageCount,
}
err = g.Create(newSession)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, newSession)
}
// GetLLMSessionByPath - 兼容性端点,基于路径获取或创建会话
func GetLLMSessionByPath(c *gin.Context) {
path := c.Query("path")
if !helper.IsUnderDirectory(path, nginx.GetConfPath()) {
c.JSON(http.StatusForbidden, gin.H{
"message": "path is not under the nginx conf path",
})
return
}
g := query.LLMSession
// 查找基于该路径的会话
session, err := g.Where(g.Path.Eq(path)).First()
if err != nil {
// 如果没找到,创建一个新的会话
title := "Chat for " + filepath.Base(path)
session = &model.LLMSession{
Title: title,
Path: path,
Messages: []openai.ChatCompletionMessage{},
MessageCount: 0,
IsActive: true,
}
err = g.Create(session)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
}
// 返回兼容格式
response := struct {
Name string `json:"name"`
Content []openai.ChatCompletionMessage `json:"content"`
}{
Name: session.Path,
Content: session.Messages,
}
c.JSON(http.StatusOK, response)
}
// CreateOrUpdateLLMSessionByPath - 兼容性端点,基于路径创建或更新会话
func CreateOrUpdateLLMSessionByPath(c *gin.Context) {
var json struct {
FileName string `json:"file_name"`
Messages []openai.ChatCompletionMessage `json:"messages"`
}
if !cosy.BindAndValid(c, &json) {
return
}
if !helper.IsUnderDirectory(json.FileName, nginx.GetConfPath()) {
c.JSON(http.StatusForbidden, gin.H{
"message": "path is not under the nginx conf path",
})
return
}
g := query.LLMSession
// 查找或创建基于该路径的会话
session, err := g.Where(g.Path.Eq(json.FileName)).First()
if err != nil {
// 创建新会话
title := "Chat for " + filepath.Base(json.FileName)
session = &model.LLMSession{
Title: title,
Path: json.FileName,
Messages: json.Messages,
MessageCount: len(json.Messages),
IsActive: true,
}
err = g.Create(session)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
} else {
// 更新现有会话
session.Messages = json.Messages
session.MessageCount = len(json.Messages)
err = g.Save(session)
if err != nil {
logger.Error(err)
cosy.ErrHandler(c, err)
return
}
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
// GenerateSessionTitle generates a title for a session based on its context
func GenerateSessionTitle(c *gin.Context) {
sessionID := c.Param("session_id")
g := query.LLMSession
session, err := g.Where(g.SessionID.Eq(sessionID)).First()
if err != nil {
cosy.ErrHandler(c, err)
return
}
// Generate title based on session messages
title, err := llm.GenerateSessionTitle(session.Messages)
if err != nil {
logger.Error("Failed to generate session title:", err)
cosy.ErrHandler(c, err)
return
}
// Update the session with the new title
session.Title = title
err = g.Save(session)
if err != nil {
logger.Error("Failed to save session with new title:", err)
cosy.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"title": title,
"message": "Title generated successfully",
})
}