202 lines
5.9 KiB
Go
202 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func main() {
|
|
logrus.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
|
|
logrus.SetLevel(logrus.InfoLevel)
|
|
|
|
if err := loadConfig("config.yaml"); err != nil {
|
|
logrus.Fatalf("Failed to load config.yaml: %v", err)
|
|
}
|
|
logrus.Infof("Loaded config: %+v", appConfig)
|
|
visitDB := NewVisitDB()
|
|
|
|
if err := loadUITemplate("ui.html"); err != nil {
|
|
logrus.Fatalf("Failed to load ui.html: %v", err)
|
|
}
|
|
|
|
// Initialize PostgreSQL repository first
|
|
dsn := buildDefaultDSN()
|
|
logrus.Info("Connecting to PostgreSQL with DSN: ", dsn)
|
|
repo, err := NewPGChatRepository(context.Background(), dsn)
|
|
if err != nil {
|
|
logrus.WithError(err).Warn("PostgreSQL repository disabled (connection failed)")
|
|
} else if repo == nil {
|
|
logrus.Info("PostgreSQL repository not configured (no DSN)")
|
|
}
|
|
// defer repo.Close() // optionally enable
|
|
|
|
// Initialize LLM client
|
|
llmClient := NewLLMClient(
|
|
os.Getenv("OPENAI_API_KEY"),
|
|
os.Getenv("OPENAI_BASE_URL"),
|
|
os.Getenv("OPENAI_MODEL"),
|
|
repo,
|
|
)
|
|
var llm LLMClientAPI = llmClient
|
|
|
|
chatService := NewChatService(llm, &visitDB, repo)
|
|
r := gin.Default()
|
|
// Routes
|
|
r.GET("/", func(c *gin.Context) {
|
|
c.Status(200)
|
|
if err := uiTemplate.Execute(c.Writer, nil); err != nil {
|
|
logrus.Errorf("Failed to execute ui.html template: %v", err)
|
|
}
|
|
})
|
|
r.GET("/health", func(c *gin.Context) {
|
|
c.Status(200)
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
})
|
|
r.POST("/chat", chatService.HandleChat)
|
|
|
|
if err := loadDBEditTemplate("ui_dbedit.html"); err != nil {
|
|
logrus.Fatalf("Failed to load ui_dbedit.html: %v", err)
|
|
}
|
|
if err := loadAdminChatsTemplate("ui_admin_chats.html"); err != nil {
|
|
logrus.Fatalf("Failed to load ui_admin_chats.html: %v", err)
|
|
}
|
|
r.GET("/admin", func(c *gin.Context) {
|
|
c.Status(200)
|
|
if err := uiDBEditTemplate.Execute(c.Writer, nil); err != nil {
|
|
logrus.Errorf("Failed to execute ui_dbedit.html template: %v", err)
|
|
}
|
|
})
|
|
r.GET("/db.yaml", func(c *gin.Context) { c.File("db.yaml") })
|
|
|
|
// Add knowledgeModel save endpoint (replaces snapshot)
|
|
r.POST("/admin/chats/snapshot", func(c *gin.Context) {
|
|
if repo == nil {
|
|
c.JSON(500, gin.H{"error": "repository not configured"})
|
|
return
|
|
}
|
|
var req struct {
|
|
Text string `json:"text"`
|
|
}
|
|
if err := c.BindJSON(&req); err != nil || req.Text == "" {
|
|
c.JSON(400, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
if err := repo.SaveKnowledgeModel(c.Request.Context(), req.Text); err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to save snapshot"})
|
|
return
|
|
}
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
})
|
|
|
|
// JSON: list chat interactions
|
|
r.GET("/admin/chats", func(c *gin.Context) {
|
|
if repo == nil {
|
|
c.JSON(200, gin.H{"items": []ChatInteraction{}, "pagination": gin.H{"limit": 0, "offset": 0, "count": 0}, "warning": "repository not configured"})
|
|
return
|
|
}
|
|
limit := 50
|
|
if ls := c.Query("limit"); ls != "" {
|
|
if v, err := strconv.Atoi(ls); err == nil {
|
|
limit = v
|
|
}
|
|
}
|
|
offset := 0
|
|
if osf := c.Query("offset"); osf != "" {
|
|
if v, err := strconv.Atoi(osf); err == nil {
|
|
offset = v
|
|
}
|
|
}
|
|
items, err := repo.ListChatInteractions(c.Request.Context(), limit, offset)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to list interactions"})
|
|
return
|
|
}
|
|
c.JSON(200, gin.H{"items": items, "pagination": gin.H{"limit": limit, "offset": offset, "count": len(items)}})
|
|
})
|
|
// JSON: list raw LLM events for a correlation id OR serve UI when no correlation_id provided
|
|
r.GET("/admin/chats/events", func(c *gin.Context) {
|
|
corr := c.Query("correlation_id")
|
|
if corr == "" {
|
|
if strings.Contains(c.GetHeader("Accept"), "application/json") {
|
|
c.JSON(400, gin.H{"error": "missing correlation_id"})
|
|
return
|
|
}
|
|
c.Status(200)
|
|
if err := uiAdminChatsTemplate.Execute(c.Writer, nil); err != nil {
|
|
logrus.Errorf("Failed to execute ui_admin_chats.html template: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if repo == nil { // repository not configured, return empty JSON set for events
|
|
c.JSON(200, gin.H{"items": []RawLLMEvent{}, "pagination": gin.H{"limit": 0, "offset": 0, "count": 0}, "warning": "repository not configured"})
|
|
return
|
|
}
|
|
limit := 100
|
|
if ls := c.Query("limit"); ls != "" {
|
|
if v, err := strconv.Atoi(ls); err == nil {
|
|
limit = v
|
|
}
|
|
}
|
|
offset := 0
|
|
if osf := c.Query("offset"); osf != "" {
|
|
if v, err := strconv.Atoi(osf); err == nil {
|
|
offset = v
|
|
}
|
|
}
|
|
events, err := repo.ListLLMRawEvents(c.Request.Context(), corr, limit, offset)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to list events"})
|
|
return
|
|
}
|
|
c.JSON(200, gin.H{"items": events, "pagination": gin.H{"limit": limit, "offset": offset, "count": len(events)}})
|
|
})
|
|
// HTML UI for chats & events
|
|
r.GET("/admin/chats/ui", func(c *gin.Context) {
|
|
c.Status(200)
|
|
if err := uiAdminChatsTemplate.Execute(c.Writer, nil); err != nil {
|
|
logrus.Errorf("Failed to execute ui_admin_chats.html template: %v", err)
|
|
}
|
|
})
|
|
|
|
// List knowledgeModel entries (replaces snapshots)
|
|
r.GET("/admin/chats/snapshots", func(c *gin.Context) {
|
|
if repo == nil {
|
|
c.JSON(200, gin.H{"items": []interface{}{}})
|
|
return
|
|
}
|
|
models, err := repo.ListKnowledgeModels(c.Request.Context(), 100, 0)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to list snapshots"})
|
|
return
|
|
}
|
|
c.JSON(200, gin.H{"items": models})
|
|
})
|
|
|
|
// Get a specific knowledgeModel entry (replaces snapshot)
|
|
r.GET("/admin/chats/snapshot/:id", func(c *gin.Context) {
|
|
if repo == nil {
|
|
c.JSON(404, gin.H{"error": "repository not configured"})
|
|
return
|
|
}
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
text, err := repo.GetKnowledgeModelText(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(404, gin.H{"error": "snapshot not found"})
|
|
return
|
|
}
|
|
c.Data(200, "text/yaml; charset=utf-8", []byte(text))
|
|
})
|
|
|
|
r.Run(":8080")
|
|
}
|