256 lines
7.1 KiB
Go
256 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/flosch/pongo2"
|
|
"github.com/gofiber/fiber/v2"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
)
|
|
|
|
type Message struct {
|
|
ID primitive.ObjectID `bson:"_id"`
|
|
Content string `bson:"message"`
|
|
Role string `bson:"role"`
|
|
Date time.Time `bson:"date"`
|
|
Model string `bson:"model"`
|
|
Selected bool `bson:"selected"`
|
|
LinkedMessageIds []string `bson:"linked_message_ids"`
|
|
}
|
|
|
|
type Conversation struct {
|
|
ID string
|
|
Messages []Message
|
|
}
|
|
|
|
func ChatPageHandler(c *fiber.Ctx) error {
|
|
return c.Render("chat", fiber.Map{}, "layouts/main")
|
|
}
|
|
|
|
func LoadModelSelectionHandler(c *fiber.Ctx) error {
|
|
CheckedModels := []string{"gpt-3.5-turbo"} // Default model
|
|
out, err := modelsPopoverTmpl.Execute(pongo2.Context{
|
|
"CompanyInfos": CompanyInfos,
|
|
"CheckedModels": CheckedModels,
|
|
})
|
|
if err != nil {
|
|
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error rendering template",
|
|
})
|
|
}
|
|
return c.SendString(out)
|
|
}
|
|
|
|
func LoadConversationsSelectionHandler(c *fiber.Ctx) error {
|
|
out, err := conversationsPopoverTmpl.Execute(pongo2.Context{
|
|
"Conversations": "",
|
|
})
|
|
if err != nil {
|
|
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error rendering template",
|
|
})
|
|
}
|
|
return c.SendString(out)
|
|
}
|
|
|
|
func DeleteMessageHandler(c *fiber.Ctx) error {
|
|
messageId := c.FormValue("id")
|
|
|
|
// messageId is a string that look like ObjectID("662d6556e5328bbc8357cfb1")
|
|
// Get the message from the db and delete it and all messages after it
|
|
collection := mongoClient.Database("chat").Collection("messages")
|
|
|
|
// Convert the messageId string to a bson.ObjectId
|
|
objectID, err := primitive.ObjectIDFromHex(messageId)
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid message ID",
|
|
})
|
|
}
|
|
|
|
// Find the message with the given ID
|
|
message := &Message{}
|
|
err = collection.FindOne(c.Context(), bson.M{"_id": objectID}).Decode(message)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{
|
|
"error": "Message not found",
|
|
})
|
|
}
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error finding message",
|
|
})
|
|
}
|
|
|
|
// Delete the message and all messages after it
|
|
_, err = collection.DeleteMany(c.Context(), bson.M{
|
|
"date": bson.M{"$gte": message.Date},
|
|
})
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error deleting messages",
|
|
})
|
|
}
|
|
|
|
return c.SendString(generateChatHTML())
|
|
}
|
|
|
|
func LoadChatHandler(c *fiber.Ctx) error {
|
|
return c.SendString(generateChatHTML())
|
|
}
|
|
|
|
func ChangeSelectedMessageHandler(c *fiber.Ctx) error {
|
|
messageId := c.FormValue("id")
|
|
collection := mongoClient.Database("chat").Collection("messages")
|
|
objectID, err := primitive.ObjectIDFromHex(messageId)
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid message ID",
|
|
})
|
|
}
|
|
|
|
var selectedMessage Message
|
|
err = collection.FindOne(c.Context(), bson.M{"_id": objectID}).Decode(&selectedMessage)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error finding message",
|
|
})
|
|
}
|
|
|
|
for i := range selectedMessage.LinkedMessageIds {
|
|
objectID, _ = primitive.ObjectIDFromHex(selectedMessage.LinkedMessageIds[i])
|
|
_, err = collection.UpdateOne(c.Context(), bson.M{"_id": objectID}, bson.M{"$set": bson.M{"selected": false}})
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error updating message",
|
|
})
|
|
}
|
|
}
|
|
|
|
_, err = collection.UpdateOne(c.Context(), bson.M{"_id": objectID}, bson.M{"$set": bson.M{"selected": true}})
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error updating message",
|
|
})
|
|
}
|
|
|
|
return c.SendString(generateChatHTML())
|
|
}
|
|
|
|
type NextMessage struct {
|
|
Icon string
|
|
Content string
|
|
Hidden bool
|
|
Id string
|
|
Name string
|
|
}
|
|
|
|
func generateChatHTML() string {
|
|
// Get the messages from the database
|
|
collection := mongoClient.Database("chat").Collection("messages")
|
|
|
|
// Get all messages
|
|
cursor, err := collection.Find(context.TODO(), bson.D{})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
// Convert the cursor to an array of messages
|
|
var Messages []Message
|
|
if err = cursor.All(context.TODO(), &Messages); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
|
|
|
|
var NextMessages []NextMessage // Use the custom NextMessage struct
|
|
|
|
for i, message := range Messages {
|
|
if message.Role == "user" {
|
|
htmlContent := markdownToHTML(message.Content)
|
|
userOut, err := userTmpl.Execute(pongo2.Context{"Content": htmlContent, "ID": message.ID.Hex()})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
htmlString += userOut
|
|
// Reset NextMessages when a user message is encountered
|
|
NextMessages = []NextMessage{}
|
|
} else {
|
|
fmt.Println(i)
|
|
// For bot messages, add them to NextMessages with only the needed fields
|
|
nextMsg := NextMessage{
|
|
Icon: model2Icon(message.Model), // Assuming Icon is a field you want to include from Message
|
|
Content: markdownToHTML(message.Content),
|
|
Hidden: !message.Selected, // Assuming Hidden is a field you want to include from Message
|
|
Id: message.ID.Hex(),
|
|
Name: model2Name(message.Model),
|
|
}
|
|
NextMessages = append(NextMessages, nextMsg)
|
|
|
|
// Check if the next message is not a bot or if it's the last message
|
|
if i+1 == len(Messages) || Messages[i+1].Role != "bot" {
|
|
sort.Slice(NextMessages, func(i, j int) bool {
|
|
if !NextMessages[i].Hidden && NextMessages[j].Hidden {
|
|
return true
|
|
}
|
|
if NextMessages[i].Hidden && !NextMessages[j].Hidden {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": i})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
htmlString += botOut
|
|
htmlString += "<div style='height: 10px;'></div>"
|
|
}
|
|
}
|
|
}
|
|
|
|
htmlString += "</div></div>"
|
|
|
|
// Render the HTML template with the messages
|
|
return htmlString
|
|
}
|
|
|
|
func GetMessageContentHandler(c *fiber.Ctx) error {
|
|
messageId := c.FormValue("id")
|
|
|
|
collection := mongoClient.Database("chat").Collection("messages")
|
|
objectID, err := primitive.ObjectIDFromHex(messageId)
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid message ID",
|
|
})
|
|
}
|
|
|
|
var selectedMessage Message
|
|
err = collection.FindOne(c.Context(), bson.M{"_id": objectID}).Decode(&selectedMessage)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Error finding message",
|
|
})
|
|
}
|
|
|
|
out := "<div class='message-header'>"
|
|
out += "<p>"
|
|
out += model2Name(selectedMessage.Model)
|
|
out += " </p>"
|
|
out += "</div>"
|
|
out += "<div class='message-body'>"
|
|
out += " <ct class='content'>"
|
|
out += markdownToHTML(selectedMessage.Content)
|
|
out += " </ct>"
|
|
out += "</div>"
|
|
|
|
return c.SendString(out)
|
|
}
|