- compaction automatique du contexte LLM quand la taille atteint le seuil (contexte max - taille de réponse max), par résumé de l'historique ancien - filtre textuel (?q=) et pagination (20/page) de l'historique des conversations - correctif ORM : offset de pagination en base 0 - suppression d'une conversation : messages associés supprimés, session en cours annulée, popup de confirmation (ConfirmDialog) - titres de conversation nettoyés du markdown - styles des listes markdown, variante danger du bouton - go.mod/go.sum : dépendances go-rod et stealth manquantes du commit précédent
187 lines
4.8 KiB
Go
187 lines
4.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
|
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
|
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
|
)
|
|
|
|
func NewChatGroup(prefix string, e *echo.Group, service *chat.Service, middlewares ...echo.MiddlewareFunc) *echo.Group {
|
|
chatHandler := &ChatHandler{
|
|
chatService: service,
|
|
}
|
|
auth := e.Group(prefix, middlewares...)
|
|
|
|
auth.POST("/:space", chatHandler.NewChat)
|
|
auth.POST("/:space/:id", chatHandler.NewChatMessage)
|
|
auth.GET("/:space/all/:page", chatHandler.List)
|
|
auth.GET("/:space/:id", chatHandler.Get)
|
|
auth.GET("/:space/:id/subscribe", chatHandler.Subscribe)
|
|
auth.DELETE("/:space/:id", chatHandler.Delete)
|
|
return auth
|
|
}
|
|
|
|
type ChatHandler struct {
|
|
chatService *chat.Service
|
|
}
|
|
|
|
type ChatRequest struct {
|
|
Prompt string `json:"prompt"`
|
|
AgentID string `json:"agentID"`
|
|
ProviderID string `json:"providerID"`
|
|
ModelID string `json:"modelID"`
|
|
ReasoningLevel string `json:"reasoningLevel"`
|
|
}
|
|
|
|
func (h *ChatHandler) NewChat(c *echo.Context) error {
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
var request ChatRequest
|
|
err := c.Bind(&request)
|
|
if err != nil {
|
|
c.Logger().Error("error binding request", "error", err.Error())
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error binding request")
|
|
}
|
|
chat, err := h.chatService.NewChat(
|
|
userID,
|
|
teamID,
|
|
request.AgentID,
|
|
)
|
|
if err != nil {
|
|
c.Logger().Error("error creating chat", "error", err.Error())
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error creating chat")
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, chat)
|
|
}
|
|
|
|
func (h *ChatHandler) NewChatMessage(c *echo.Context) error {
|
|
|
|
ctx := context.Background()
|
|
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
chatID := c.Param("id")
|
|
var request ChatRequest
|
|
err := c.Bind(&request)
|
|
if err != nil {
|
|
c.Logger().Error("error binding request", "error", err.Error())
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error binding request")
|
|
}
|
|
|
|
enc := json.NewEncoder(c.Response())
|
|
chunkChan := make(chan *domain.Chunk)
|
|
|
|
_, err = h.chatService.AddChatMessage(
|
|
ctx,
|
|
userID,
|
|
teamID,
|
|
chatID,
|
|
request.AgentID,
|
|
request.Prompt,
|
|
&domain.ChatParams{
|
|
OnChunk: func(chunk *domain.Chunk) {
|
|
chunkChan <- chunk
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
c.Logger().Error("error creating chat message", "error", err.Error())
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error creating chat message")
|
|
}
|
|
|
|
enc.Encode(&domain.Chunk{
|
|
Done: false,
|
|
Role: string(domain.RoleUser),
|
|
Content: request.Prompt,
|
|
})
|
|
http.NewResponseController(c.Response()).Flush()
|
|
|
|
for {
|
|
chunk := <-chunkChan
|
|
enc.Encode(chunk)
|
|
http.NewResponseController(c.Response()).Flush()
|
|
if chunk.Done {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (h *ChatHandler) List(c *echo.Context) error {
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
page := c.Param("page")
|
|
pageInt, _ := strconv.Atoi(page)
|
|
query := c.QueryParam("q")
|
|
chats, err := h.chatService.ListChats(userID, teamID, pageInt, query)
|
|
if err != nil {
|
|
c.Logger().Error("error listing chats", "error", err)
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error listing chats")
|
|
}
|
|
return c.JSON(http.StatusOK, chats)
|
|
}
|
|
|
|
func (h *ChatHandler) Get(c *echo.Context) error {
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
chatID := c.Param("id")
|
|
chatWithMessages, err := h.chatService.GetChatWithMessages(userID, teamID, chatID)
|
|
|
|
if err != nil {
|
|
c.Logger().Error("error getting chat with messages", "error", err)
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error getting chat")
|
|
}
|
|
return c.JSON(http.StatusOK, chatWithMessages)
|
|
}
|
|
|
|
func (h *ChatHandler) Delete(c *echo.Context) error {
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
chatID := c.Param("id")
|
|
err := h.chatService.DeleteChat(userID, teamID, chatID)
|
|
if err != nil {
|
|
c.Logger().Error("error deleting chat", "error", err)
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error deleting chat")
|
|
}
|
|
return c.JSON(http.StatusOK, "chat deleted")
|
|
}
|
|
|
|
func (h *ChatHandler) Subscribe(c *echo.Context) error {
|
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
|
teamID := c.Param("space")
|
|
chatID := c.Param("id")
|
|
|
|
enc := json.NewEncoder(c.Response())
|
|
chunkChan := make(chan *domain.Chunk)
|
|
|
|
err := h.chatService.Subscribe(
|
|
userID,
|
|
teamID,
|
|
chatID,
|
|
func(chunk *domain.Chunk) {
|
|
chunkChan <- chunk
|
|
},
|
|
)
|
|
if err != nil {
|
|
c.Logger().Error("error subscribing to chat", "error", err.Error())
|
|
return echo.NewHTTPError(http.StatusBadRequest, "error creating chat")
|
|
}
|
|
|
|
for {
|
|
chunk := <-chunkChan
|
|
if chunk.Done {
|
|
return nil
|
|
}
|
|
enc.Encode(chunk)
|
|
http.NewResponseController(c.Response()).Flush()
|
|
}
|
|
|
|
}
|