diff --git a/Chat.go b/Chat.go
new file mode 100644
index 0000000..cfd7ec1
--- /dev/null
+++ b/Chat.go
@@ -0,0 +1,232 @@
+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 := popoverTmpl.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 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
+}
+
+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 := "
"
+
+ 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(),
+ }
+ 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 += ""
+ }
+ }
+ }
+
+ htmlString += "
"
+
+ // Render the HTML template with the messages
+ return htmlString
+}
+
+func GetMessageContentHandler(c *fiber.Ctx) error {
+ messageId := c.FormValue("id")
+ fmt.Println(messageId)
+ 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",
+ })
+ }
+
+ fmt.Println(selectedMessage)
+
+ return c.SendString(selectedMessage.Content)
+}
diff --git a/Request.go b/Request.go
new file mode 100644
index 0000000..64c67ec
--- /dev/null
+++ b/Request.go
@@ -0,0 +1,111 @@
+package main
+
+import (
+ "context"
+ "sync"
+ "time"
+
+ "github.com/flosch/pongo2"
+ "github.com/gofiber/fiber/v2"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type ModelInfo struct {
+ ID string
+ Name string
+ Icon string
+ MaxToken int
+ InputPrice float64
+ OutputPrice float64
+}
+
+type CompanyInfo struct {
+ ID string
+ Name string
+ Icon string
+ ModelInfos []ModelInfo
+}
+
+var CompanyInfos []CompanyInfo
+
+type MultipleModelsCompletionRequest struct {
+ ModelIds []string
+ Messages []Message
+ Message string
+}
+
+type BotContentMessage struct {
+ Content string
+ Hidden bool
+}
+
+var lastSelectedModelIds []string
+
+func GenerateMultipleMessages(c *fiber.Ctx) error {
+ // Create a wait group to synchronize the goroutines
+ var wg sync.WaitGroup
+ var InsertedIDs []string
+
+ // Add the length of lastSelectedModelIds goroutines to the wait group
+ wg.Add(len(lastSelectedModelIds))
+
+ for i := range lastSelectedModelIds {
+ if model2Icon(lastSelectedModelIds[i]) == "openai" {
+ go func() {
+ defer wg.Done()
+ response := addOpenaiMessage(lastSelectedModelIds[i], i == 0)
+ InsertedIDs = append(InsertedIDs, response)
+ }()
+ } else if model2Icon(lastSelectedModelIds[i]) == "anthropic" {
+ go func() {
+ defer wg.Done()
+ response := addAnthropicMessage(lastSelectedModelIds[i], i == 0)
+ InsertedIDs = append(InsertedIDs, response)
+ }()
+ }
+ }
+
+ // Wait for both goroutines to finish
+ wg.Wait()
+
+ collection := mongoClient.Database("chat").Collection("messages")
+ for i := range InsertedIDs {
+ objectID, _ := primitive.ObjectIDFromHex(InsertedIDs[i])
+ collection.UpdateOne(context.Background(), bson.M{"_id": objectID}, bson.M{"$set": bson.M{"linked_message_ids": InsertedIDs}})
+ }
+
+ return c.SendString(generateChatHTML())
+}
+
+func RequestMultipleMessages(c *fiber.Ctx) error {
+ message := c.FormValue("message")
+ if chatString, commandExecuted := DetectCommand(message, c); commandExecuted {
+ return c.SendString(chatString)
+ }
+
+ collection := mongoClient.Database("chat").Collection("messages")
+ result, _ := collection.InsertOne(context.Background(), bson.M{"message": message, "role": "user", "date": time.Now()})
+
+ selectedModelIds := []string{}
+ for CompanyInfo := range CompanyInfos {
+ for ModelInfo := range CompanyInfos[CompanyInfo].ModelInfos {
+ out := c.FormValue("model-check-" + CompanyInfos[CompanyInfo].ModelInfos[ModelInfo].ID)
+ if out != "" {
+ selectedModelIds = append(selectedModelIds, CompanyInfos[CompanyInfo].ModelInfos[ModelInfo].ID)
+ }
+ }
+ }
+ lastSelectedModelIds = selectedModelIds
+
+ out := ""
+
+ HexID := result.InsertedID.(primitive.ObjectID).Hex()
+ messageOut, _ := userTmpl.Execute(pongo2.Context{"Content": message, "ID": HexID})
+ out += messageOut
+
+ messageOut, _ = botTmpl.Execute(pongo2.Context{"IsPlaceholder": true, "SelectedModelIds": selectedModelIds, "Message": message})
+ out += messageOut
+
+ return c.SendString(out)
+}
diff --git a/RequestAnthropic.go b/RequestAnthropic.go
new file mode 100644
index 0000000..eeb19ce
--- /dev/null
+++ b/RequestAnthropic.go
@@ -0,0 +1,181 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type AnthropicChatCompletionRequest struct {
+ Model string `json:"model"`
+ Messages []AnthropicMessage `json:"messages"`
+ MaxTokens int `json:"max_tokens"`
+ Temperature float64 `json:"temperature"`
+}
+
+type AnthropicMessage struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+}
+
+type AnthropicChatCompletionResponse struct {
+ ID string `json:"id"`
+ Content []AnthropicContentItem `json:"content"`
+ Model string `json:"model"`
+ StopReason string `json:"stop_reason"`
+ StopSequence string `json:"stop_sequence"`
+ Usage AnthropicUsage `json:"usage"`
+}
+
+type AnthropicContentItem struct {
+ Text string `json:"text"`
+}
+
+type AnthropicUsage struct {
+ InputTokens int `json:"input_tokens"`
+ OutputTokens int `json:"output_tokens"`
+}
+
+func init() {
+ var ModelInfosList = []ModelInfo{}
+
+ modelInfo := ModelInfo{
+ ID: "claude-3-haiku-20240307",
+ Name: "Haiku",
+ Icon: "anthropic",
+ MaxToken: 8192,
+ InputPrice: 0.50 / 1000000,
+ OutputPrice: 1.50 / 1000000,
+ }
+ ModelInfosList = append(ModelInfosList, modelInfo)
+
+ modelInfo = ModelInfo{
+ ID: "claude-3-sonnet-20240229",
+ Name: "Sonnet",
+ Icon: "anthropic",
+ MaxToken: 8192,
+ InputPrice: 0.50 / 1000000,
+ OutputPrice: 1.50 / 1000000,
+ }
+ ModelInfosList = append(ModelInfosList, modelInfo)
+
+ modelInfo = ModelInfo{
+ ID: "claude-3-opus-20240229",
+ Name: "Opus",
+ Icon: "anthropic",
+ MaxToken: 8192,
+ InputPrice: 0.50 / 1000000,
+ OutputPrice: 1.50 / 1000000,
+ }
+ ModelInfosList = append(ModelInfosList, modelInfo)
+
+ companyInfo := CompanyInfo{
+ ID: "anthropic",
+ Name: "Anthropic",
+ ModelInfos: ModelInfosList,
+ }
+ CompanyInfos = append(CompanyInfos, companyInfo)
+}
+
+func addAnthropicMessage(modelID string, selected bool) string {
+ Messages := getAllMessages()
+
+ chatCompletion, err := RequestAnthropic(modelID, Messages, 2048, 0.7) // TODO CHange parameters
+ if err != nil {
+ // Print error
+ fmt.Println("Error:", err)
+ } else if len(chatCompletion.Content) == 0 {
+ fmt.Println(chatCompletion)
+ fmt.Println("No response from Anthropic")
+ collection := mongoClient.Database("chat").Collection("messages")
+ response, err := collection.InsertOne(context.Background(), bson.M{"message": "no response", "role": "bot", "date": time.Now(), "model": modelID, "selected": selected})
+ if err != nil {
+ fmt.Println(err)
+ }
+ return response.InsertedID.(primitive.ObjectID).Hex()
+ } else {
+ Content := chatCompletion.Content[0].Text
+ collection := mongoClient.Database("chat").Collection("messages")
+ response, err := collection.InsertOne(context.Background(), bson.M{"message": Content, "role": "bot", "date": time.Now(), "model": modelID, "selected": selected})
+ if err != nil {
+ fmt.Println(err)
+ }
+ return response.InsertedID.(primitive.ObjectID).Hex()
+ }
+ return ""
+}
+
+func Messages2AnthropicMessages(messages []Message) []AnthropicMessage {
+ AnthropicMessages := make([]AnthropicMessage, len(messages))
+ for i, msg := range messages {
+ var role string
+ switch msg.Role {
+ case "user":
+ role = "user"
+ case "bot":
+ role = "assistant"
+ default:
+ role = "system"
+ }
+ AnthropicMessages[i] = AnthropicMessage{
+ Role: role,
+ Content: msg.Content,
+ }
+ }
+ return AnthropicMessages
+}
+
+func RequestAnthropic(model string, messages []Message, maxTokens int, temperature float64) (AnthropicChatCompletionResponse, error) {
+ apiKey := "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" // TODO Use env variable
+ url := "https://api.anthropic.com/v1/messages"
+
+ AnthropicMessages := Messages2AnthropicMessages(messages)
+
+ requestBody := AnthropicChatCompletionRequest{
+ Model: model,
+ Messages: AnthropicMessages,
+ MaxTokens: maxTokens,
+ Temperature: temperature,
+ }
+
+ jsonBody, err := json.Marshal(requestBody)
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
+ }
+
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
+ }
+
+ req.Header.Set("content-Type", "application/json")
+ req.Header.Set("anthropic-version", "2023-06-01")
+ req.Header.Set("x-api-key", apiKey)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
+ }
+
+ var chatCompletionResponse AnthropicChatCompletionResponse
+ err = json.Unmarshal(body, &chatCompletionResponse)
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
+ }
+
+ return chatCompletionResponse, nil
+}
diff --git a/RequestOpenai.go b/RequestOpenai.go
index 1652ae5..b0cf56c 100644
--- a/RequestOpenai.go
+++ b/RequestOpenai.go
@@ -9,17 +9,11 @@ import (
"net/http"
"time"
- "github.com/flosch/pongo2"
- "github.com/gofiber/fiber/v2"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
-type dataForTemplate struct {
- Message OpenaiMessage
-}
-
-type ChatCompletionRequest struct {
+type OpenaiChatCompletionRequest struct {
Model string `json:"model"`
Messages []OpenaiMessage `json:"messages"`
Temperature float64 `json:"temperature"`
@@ -30,7 +24,7 @@ type OpenaiMessage struct {
Content string `json:"content"`
}
-type ChatCompletionResponse struct {
+type OpenaiChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
@@ -51,56 +45,93 @@ type OpenaiChoice struct {
Index int `json:"index"`
}
-var lastMessageAsked string // TODO Remove this
+func init() {
+ var ModelInfosList = []ModelInfo{}
-func addOpenaiMessage(c *fiber.Ctx) error {
- message := lastMessageAsked // TODO Remove this
-
- chatCompletion, err := RequestOpenai("gpt-3.5-turbo", []Message{{Content: message, Role: "user", Date: time.Now(), ID: primitive.NilObjectID}}, 0.7)
- if err != nil {
- // Print error
- fmt.Println("Error:", err)
- return err
- } else if len(chatCompletion.Choices) == 0 {
- fmt.Println("No response from OpenAI")
- return err
+ modelInfo := ModelInfo{
+ ID: "gpt-3.5-turbo",
+ Name: "GPT-3.5",
+ Icon: "openai",
+ MaxToken: 4096,
+ InputPrice: 0.50 / 1000000,
+ OutputPrice: 1.50 / 1000000,
}
+ ModelInfosList = append(ModelInfosList, modelInfo)
- Content := chatCompletion.Choices[0].Message.Content
- out, err := botTmpl.Execute(pongo2.Context{"Content": Content})
- if err != nil {
- panic(err)
+ modelInfo = ModelInfo{
+ ID: "gpt-4-turbo",
+ Name: "GPT-4",
+ Icon: "openai",
+ MaxToken: 8192,
+ InputPrice: 10.00 / 1000000,
+ OutputPrice: 30.00 / 1000000,
}
+ ModelInfosList = append(ModelInfosList, modelInfo)
- collection := mongoClient.Database("chat").Collection("messages")
- collection.InsertOne(context.Background(), bson.M{"message": Content, "role": "bot", "date": time.Now()})
-
- return c.SendString(out)
+ companyInfo := CompanyInfo{
+ ID: "openai",
+ Name: "OpenAI",
+ Icon: "icons/openai.png",
+ ModelInfos: ModelInfosList,
+ }
+ CompanyInfos = append(CompanyInfos, companyInfo)
}
-func Message2OpenaiMessage(message Message) OpenaiMessage {
- return OpenaiMessage{
- Role: message.Role,
- Content: message.Content,
+func addOpenaiMessage(modelID string, selected bool) string {
+ Messages := getAllMessages()
+
+ chatCompletion, err := RequestOpenai(modelID, Messages, 0.7)
+ if err != nil {
+ fmt.Println("Error:", err)
+ } else if len(chatCompletion.Choices) == 0 {
+ fmt.Println(chatCompletion)
+ fmt.Println("No response from OpenAI")
+ collection := mongoClient.Database("chat").Collection("messages")
+ response, err := collection.InsertOne(context.Background(), bson.M{"message": "no response", "role": "bot", "date": time.Now(), "model": modelID, "selected": selected})
+ if err != nil {
+ fmt.Println(err)
+ }
+ return response.InsertedID.(primitive.ObjectID).Hex()
+ } else {
+ Content := chatCompletion.Choices[0].Message.Content
+ collection := mongoClient.Database("chat").Collection("messages")
+ response, err := collection.InsertOne(context.Background(), bson.M{"message": Content, "role": "bot", "date": time.Now(), "model": modelID, "selected": selected})
+ if err != nil {
+ fmt.Println(err)
+ }
+ return response.InsertedID.(primitive.ObjectID).Hex()
}
+ return ""
}
func Messages2OpenaiMessages(messages []Message) []OpenaiMessage {
- var openaiMessages []OpenaiMessage
- for _, message := range messages {
- openaiMessages = append(openaiMessages, Message2OpenaiMessage(message))
+ openaiMessages := make([]OpenaiMessage, len(messages))
+ for i, msg := range messages {
+ var role string
+ switch msg.Role {
+ case "user":
+ role = "user"
+ case "bot":
+ role = "assistant"
+ default:
+ role = "system"
+ }
+ openaiMessages[i] = OpenaiMessage{
+ Role: role,
+ Content: msg.Content,
+ }
}
return openaiMessages
}
-func RequestOpenai(model string, messages []Message, temperature float64) (ChatCompletionResponse, error) {
+func RequestOpenai(model string, messages []Message, temperature float64) (OpenaiChatCompletionResponse, error) {
apiKey := "sk-proj-f7StCvXCtcmiOOayiVmgT3BlbkFJlVtAcOo3JcrnGq1cPa5o" // TODO Use env variable
url := "https://api.openai.com/v1/chat/completions"
// Convert messages to OpenAI format
openaiMessages := Messages2OpenaiMessages(messages)
- requestBody := ChatCompletionRequest{
+ requestBody := OpenaiChatCompletionRequest{
Model: model,
Messages: openaiMessages,
Temperature: temperature,
@@ -108,12 +139,12 @@ func RequestOpenai(model string, messages []Message, temperature float64) (ChatC
jsonBody, err := json.Marshal(requestBody)
if err != nil {
- return ChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
- return ChatCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
@@ -122,19 +153,19 @@ func RequestOpenai(model string, messages []Message, temperature float64) (ChatC
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- return ChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
- return ChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
}
- var chatCompletionResponse ChatCompletionResponse
+ var chatCompletionResponse OpenaiChatCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
- return ChatCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
}
return chatCompletionResponse, nil
diff --git a/article/Simplicity.drawio.png b/article/Simplicity.drawio.png
deleted file mode 100644
index 55ed97c..0000000
Binary files a/article/Simplicity.drawio.png and /dev/null differ
diff --git a/article/Simplicity2.drawio.png b/article/Simplicity2.drawio.png
deleted file mode 100644
index 7dec469..0000000
Binary files a/article/Simplicity2.drawio.png and /dev/null differ
diff --git a/article/Simplicity3.drawio.png b/article/Simplicity3.drawio.png
deleted file mode 100644
index deddd3f..0000000
Binary files a/article/Simplicity3.drawio.png and /dev/null differ
diff --git a/article/article.md b/article/article.md
deleted file mode 100644
index 3ace3b0..0000000
--- a/article/article.md
+++ /dev/null
@@ -1,368 +0,0 @@
-# Hyper PyFleX
-
-Thx to https://hypermedia.systems/ for the book.
-Really nice.
-
-## Me
-No web dev before
-Like coding, AI, casual geek
-Was a full python dev, never really touched anything else
-Wanted to do SaaS, like software
-Didn't do CS, did Thermal and Energy Engineering
-First wanted to become data scientist then engineer then software dev then full stack dev. Guess I want to do everything so full stack make sense
-
-## Intro
-Don't wanted to learn JS so search and found HTMX, was a revelation. I couldn't understand why everybody use JS client if this is available. I mean to do for some highly performant software. But I use python as a backend so, yeah. I have the phylosiphie of simplicity. SOmething simple is something durable, reparaible, upgradable, understadable, ect.
-
-Call me crazy but I don't comment my code, or few. I do more docs than comments. Why ? Because my code need to be my comment. I need to be able to understand what this code do by looking at it. Simplicity is readable, understable. So I can upgrade it or repaire it, ect. I guess because I didn't do a CS degree, I don't have strong beleive in comment or stuff said at school.
-
-So when I saw some JS code and started to do some basic stuff using chatGPT to give me a script that add a row to a table. I made it work, my app was pretty good, not a lot JS. But WHAT A PAIN IN THE ASS. And look at this shit! How ugly it is. I can't read it. I mean I know, I need to learn JS, code, blablabla to understand it like I understand python, blablabla. I KNOW, I don't WANT to lear JS. Ear me out, I am not saying that JS is bad at all, I'm sure it's great, but I don't WANT to know, I want to stick with what I know, my beloved python and my easy pandas, numpy and stuff. Web dev suck rn because of that in my opinion, you need to learn JS, and not small Java, did you see the size of Java client for some basic stuff ? Crazy !!! JS is the popular because you are force to use this shit. Anyway, no djugi here right ?
-
-Anyway, I did the first version of my app using streamlit. Streamlit, if you don't know, it's great, it's easy, it's python. I know, I know, I'm biais, bite me. I did a really great app with it, I wouln't be ashame to share it as if and try to sell it. Specially if I needed to learn JS lol, no way, prefer stick to streamlit than learning JS. That's why thx god HTMX. So for this first version, I used mongoDB as a DB, and I like it. I first took it for the vector seach, that I used it in the v1! But removed to focus on the chat, but I had a all library, import doc, semantic search and shit. And I was already a bit using the idea of HTMX in some way.
-
-All data was in mongoDB, and nothing store locally, everytime I reload the script I get the data from mongo and display the messages. Some peoples would tell me "but that useless and you use databse cpu, you should add the message to the db and update a list of message in your client app and then trigger an event on just the chat to blablabla" Well no, I'm not doing that. I keep it simple. And it was the best idea. At first I was worry "it's stupid, it will broke so fast". But guess what, quite the oposite. In fact it was so easy to add features !!! For example I want to add a message to the conversation, well I add it to the db and reload the script, that's it. So I can do it from anywere, at anytime, whithout breaking anything. Noting in the style of "but this small thing need to be change because of this special scenario because I am in this part of the app and not here, blablabla" Nothing, Easy. Yes I end up doing client -> server -> db -> server -> client
-
-So HTMX feeled a bit similare. Instead of Client + Server, it is Server + Screen. No client, that is the core idea of Hyper PyFleX. Easy, no JS, no client/server shit.
-And the beauty is that Hyper PyFleX is obviously all about HTMX. So you can do like Hyper GinX for Go + Gin + HTMX or Hyper RailX, ect
-The Hyper in all of them is for Hypermedia in HTML, whick is the heart of how internet and webbrowser work and mostly display and organise things. (Recommand https://hypermedia.systems/) TODO ADD the acrynyme from the books
-Another huhg advantages of using HTMX instead of JS is that you can use all HTML library, like alpine.js, ect.
-
-The core idea being that the server send all the browser need to know t odisplay the app. It doesn't need to understand what the app is or do, all of that is either in the HTML, in our head or in the server.
-When I click on a button to remove something, the client don't understand that I want to remove something. It just understand that I want to change this part. And if the change happend to be an empty HTML, well it's been "removed".
-Compare to the API phylosophy where the server send just the data, but then the server need a function that understand and interpret the data.
-
-For example I want to display a username and an email. If my API send this:
-```html
-
Adrien
-
adrien.bouvais@blabla.com
-```
-In this response, there is everything the browser need to know to display. The name is an header and above the mail, ect. I can update, put the email next to the name, ect.
-
-Now let's see the current approche. Usuqlly server send JSON to the client and then the client need to do stuff.
-```json
-{
- "name" : "Adrien",
- "email" : "adrien.bouvais@blabla.com"
-}
-```
-
-But with that, you don't know how to display it. You need a new JS function to pass from one to another.
-
-Why ? I already did the logic server side of getting the data, ect. Why do I need to split some part in JS and do some shit twice ?
-
-## part 0 - table
-
-At my work, I was task to implement an app. The app is pretty simple. You have a table that is a list of test done on a database. You can see the number of errors. For each test there is a button to see the detail.
-
-To understand here a test example: I have a list of client, I check if any have more than 5 addresses. If so, when I click on the +, I get a new table that list all client with more than 5 addresses. And there is multiple test like that.
-Pretty simple right ? Well no, obviously.
-
-Man, I stuggeled so hard to just add one + button for the details. And then if I do that, it change that, and that and blablabla and it lag and it's not center. God damm, that why I don't wanted JS, I don't know JS. I did a script to exclude a row of the second table and it would react... What a mistake, I think I touched the ultimate use case here, how easy it is in HTMX compare to the other way is just incridible.
-And now the ultimate roast for JS. Yes JS on client side is in theory more efficient... in theory. But I don't know what I'm am doing here! chatGPT gave me this and it work but god, I use it for python and sometime it give me somecrazy shit. Just to say that it is if you know what you are doing. Way better to stick to the language you know, here you can do someting opti, here you understand.
-
-Let's go into a bit of code. So I have my Flask app running (I use Flask for my work)
-```python
-from flask import Flask, render_template, request
-
-app = Flask(__name__)
-
-@app.route('/')
-def index():
- return render_template('demo1.html')
-
-if __name__ == '__main__':
- app.run(debug=True)
-```
-
-That look like that:
-TODO Add image demo1
-
-Easy enough. A list and a form with 2 input and a button.
-
-Obviously I want to do 2 things, be able to add row and delete it. With JS, a bit of a pain in the ass. I know that some people will come with "just import react and react a React component then link it to the react hook to auto react to action of the user react blablabla". But let's ee how HTMX would to it. First the table itself:
-```html
-
-
-
-
-
Name
-
Age
-
-
-
-
-
-
John
-
20
-
-
-
-
Jane
-
21
-
-
-
-```
-
-So a table with a header, 2 columns and then a body and 2 rows. Now I want the button to remove the row.
-First I will add it `hx-get="/demo1/delete"`. It mean *if the button is click, run this and use what it return*. What it return is ALWAYS an HTML.
-Then `hx-swap="outerHTML"` it mean *replace the old HTML with the new one*.
-And to finish `hx-target="closest tr"` it mean *do the change on this*. In this case the closest tr is the parent parent of the button, or the row the button is currently in.
-With all change, we got:
-```html
-
-
-
-
-
Name
-
Age
-
-
-
-
-
-
John
-
20
-
-
-
-
Jane
-
21
-
-
-
-```
-
-No we just need to implement the route `/demo1/delete`. Well easy, it is supose to return the new HTML of a delete row... Meaning nothing, the funtion need to return nothing.
-```python
-@app.route('/demo1/delete', methods=['GET'])
-def demo1_delete():
- return ''
-```
-
-That's it! Ofc, at this point, you should do some server stuff. Meaning update the database ect. I don't do that here, I just show the interactivity, so if I reload the page, all changes are lost.
-
-ANd the button work, if I click I instently remove it. It REACT! It's ALIVE and JSless!
-Surely the form to add data isn't that simple...
-```html
-
-```
-
-Yes, that's it. If I click on Submit, I instantly get a new row!
-
-Let's see what it do. So like before `hx-post` will issue a request to the server, a POST this time.
-`hx-swap="beforeend"` mean *appends the content after the last child inside the target*. So after the last row of the table (can also be `afterbegin` to append at the beginning of the table)
-`hx-target="#table_body"` is the `tbody` of the table above.
-Let's see `/demo1/add`:
-```python
-@app.route('/demo1/add', methods=['POST'])
-def demo1():
- name = request.form['name']
- age = request.form['age']
- if name and age:
- return f"
{name}
{age}
"
- return ''
-```
-
-The function return the HTML of a new row. ANd because I say to add it after the last child of the `tbody`, it add a row to the table.
-
-And that's it for this first demo/intro. We were able to do some crazy stuff in very few line. I highly recommand reading the HTMX doc in the future to check all availables features.
-
-I was able to learn and do the app it few days while doing other stuff and managing issue on the dev virtual machine.
-The shit was really impressive, and my boss is really similare to me in the way he don"t want to learn JS. He was really impressed.
-
-So after that, I was convinced. Let's do JADE 2.0 using HTMX.
-
-## Why GO ?
-
-After doing a really simple chat where I can write and it answer in like 1-2h. I realease my python code is simple asf.
-I also realise how I want my app to work and it's simple. remember simplicity boys!!!
-
-So how it work. Every time I do something in the chat. I recreate it.
-Let's take an example, I want to add a message to the chat:
-User trigger and send form with message content --> Server get form content and add a message to the database --> Run main generateChatHTML (Read DB -> Generate HTML) -> Send to the client to update just the chat
-
-The goal is once again simplicity. All data are at one place, the database, where it bellow. If I need it, I read the database. There is no buffer, no global messages array that keep track of mesages in GO, and not at all in both in GO and in JS on the client. Then I need to be sure that they are identical, too much trubble, why would I do that ?
-Idk how stupid it is to use this for big app in prod, but worse case you can do a compose and add a mongodb buffer and only receive once and save once when leaving.
-
-So here a simple diagramm to understand:
-
-
-
-Compare to the more traditional approche:
-
-
-
-Or even worse, with JS
-
-
-
-Yep, this is how I see it. WTF is wrong with you peoples? 3 loops ????? 3???? I meqn 2 OOOKKK, I guesssssssss. But 3????
-So If I want to update something in my app, I first need to process the data in JS and updatte what need to be in the JS client then send only the necessarry data through an API in a specific format and have a specific function that receive this specific data and do some stuff on the server, change some stuff and then send the data to the database. Ok we update one thing!
-Don't get me wrong, I see the point of using JS. This is not a loop, the data never comeback from the databse to the client! The server never send anything. Meaning it never do the server to client part that HTMX do. So you do transfert more data for similare optimization level for the same thing but, is it better optimizing that if I get a 500mb RAM client ? Depend the app, no in a lot of scenario I beleive.
-
-Note that in the first one, there is 2 data box. Meaning 2 "states of data". This is the key difference. In the second one, the server is responsible for keeping track of the data as it stay inside it and just send the necessary data to the databse. It is efficient, that is for sure, but the server AND the databse need to keep track of the same thing, that is redondant.
-
-So after chossing to go with this design, as it is basically the same logic I use in streamlit with st.rerun(). I needed a server efficient, powerful and fast !!!
-
-*Process to look at python code*
-
-Yeah.....
-I like you python ,that is for sure. But maybe I need someone else on this job. So I alredy had some eyes on GO and was looking for a good project to use it. I saw other framework and language like Rust, ect. I was thinking about mojo at some point, but it is not mature enough at this point. But could of been a good use case for it.
-
-So GO it is, I did the good old google "Go web framwork" and found a good list, tested with Gin then went with Fiber for the SPEEDDDDDDDDDDDDDDDDDDDDDDDDD!!!!!!!!! YOOOOOOOOOOOLLLLLLLOOOOOOOO!!!!!!!!
-
-No seriously, had to chose, looked really similare. Tryed Fiber and found it easy to use and powerful so went with it.
-
-## Used teck
-So here my full teck stack
-- GO with Fiber
-- HTMX
-- MongoDB
-- Bulma
-
-Done
-
-## part 1 - The chat
-
-Ok so as a first thing is a chat. What I need is:
-- Text input at the bottom of the screen
-- Avatar and message bubble
-
-Should be easy right ? Hopefully with HTMX yes. Let's try...
-
-Well it do was pretty easy. I first did a base.html and then added an navbar and a page.html a bit like that:
-```html
-{% extends 'base.html' %}
-
-{% block content %}
-
-
-
-
-
-
-
-
-
-{% endblock %}
-```
-
-So in this HTML, I have a columns with one column empty for now and a form with an input. I also created a small style so the text are on the right or left of the colum depending of if user of bot and that's it.
-
-Now in this HTML, I use HTMX at 2 places, the column and the form. Let's take a look at the column first:
-- `hx-trigger="load"` mean *when the page load*
-- `hx-get="/chat/messages"` *get the HTML here*
-- `hx-swap="outerHTML"` *to replace yourself with the response*
-
-The HTML at /chat/messages look like:
-```html
-
- {% for message in messages %}
- {% if message.Role == 'user' %}
-
-
-
User
-
-
-
- {{ message.Content }}
-
-
- {% else %}
-
-
-
Bot
-
-
-
- {{ message.Content }}
-
-
- {% endif %}
- {% endfor %}
-
-```
-
-So it create itself back with `
` and then do a for loop on the messages that I extracted from the Database on the server side.
-
-Now let's take a look at the `form`:
-- `hx-posts="/chat"` mean *get the HTML here*
-- `hx-swap="outerHTML"` *to replace*
-- `hx-target="#messages"` *this with the response*
-
-The server function at POST /chat will add the message in the databse then return the same as /chat/messages. So if you understand, the function at /chat/messages is the second "Server" box on my diagram. And it's always the same function. Once again simplicity. Out of 4 boxs, only one change basically, the first "Server". Meaning the function that change the database.
-
-And yeah, that's it. A bit of backend but just this:
-```go
-func addMessageHandler(c *fiber.Ctx) error {
- message := c.FormValue("message")
-
- collection := mongoClient.Database("chat").Collection("messages")
- collection.InsertOne(context.Background(), bson.M{"message": message, "role": "user", "date": time.Now()})
- collection.InsertOne(context.Background(), bson.M{"message": "I did something!", "role": "bot", "date": time.Now()})
-
- return generateChatHTML(c)
-}
-
-func generateChatHTML(c *fiber.Ctx) error {
- collection := mongoClient.Database("chat").Collection("messages")
-
- // Get all messages
- cursor, _ := collection.Find(context.TODO(), bson.D{})
-
- // Convert the cursor to an array of messages
- var messages []Message
- if err = cursor.All(context.TODO(), &messages); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to convert cursor to array",
- })
- }
-
- // Render the HTML template with the messages
- return c.Render("chat/messages", fiber.Map{
- "messages": messages,
- })
-}
-```
-
-And that' it! The chat work like that! Obviously I need to add the model api stuff with it but otherwise... done. I just did an ineractive chat in what ? 100 lines of codes ? And in an easy to understand, share and maintain way ? Isn't it beautifull?
-
-## part 2 - The API
-
-Ok so now that we have a chat. I need to be able to communicate with the different API. I'm a bit affray on this one, I hope I don't NEED to use python. Now that I think about, I always used the OpenAI Client, because easy to use. Let's see if I can talk with OpeanAI server through Go first...
-
-
-
-# Conclusion
-- Say a lot about JS during the article. JS is great, I guess, I didn't try. Every language have strenght weakness, blablabla. The point is that it shouln't be a necessity to learn it. And it shouldn't be so difficult to do basic stuff.
\ No newline at end of file
diff --git a/chatCommands.go b/chatCommands.go
new file mode 100644
index 0000000..0363338
--- /dev/null
+++ b/chatCommands.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/gofiber/fiber/v2"
+ "go.mongodb.org/mongo-driver/bson"
+)
+
+type Command struct {
+ Name string
+ Trigger string
+ Handler func(c *fiber.Ctx) string
+}
+
+var Commands []Command
+
+func init() {
+ Commands = []Command{
+ {
+ Name: "Clear",
+ Trigger: "clear",
+ Handler: ClearCommandHandler,
+ },
+ }
+}
+
+func ClearCommandHandler(c *fiber.Ctx) string {
+ collection := mongoClient.Database("chat").Collection("messages")
+ _, err := collection.DeleteMany(c.Context(), bson.D{})
+ if err != nil {
+ c.Status(http.StatusInternalServerError).JSON(fiber.Map{
+ "error": "Failed to clear messages",
+ })
+ }
+ return ""
+}
+
+func DetectCommand(message string, c *fiber.Ctx) (string, bool) {
+ for _, command := range Commands {
+ if command.Trigger == message {
+ return command.Handler(c), true
+ }
+ }
+ return "", false // No command detected
+}
diff --git a/database.go b/database.go
new file mode 100644
index 0000000..841d48f
--- /dev/null
+++ b/database.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+var mongoClient *mongo.Client
+
+func init() {
+ serverAPI := options.ServerAPI(options.ServerAPIVersion1)
+ opts := options.Client().ApplyURI("mongodb://localhost:27017").SetServerAPIOptions(serverAPI)
+ client, err := mongo.Connect(context.TODO(), opts)
+ if err != nil {
+ panic(err)
+ }
+ mongoClient = client
+
+ if err := mongoClient.Ping(context.TODO(), nil); err != nil {
+ panic(err)
+ }
+}
+
+func getAllMessages() []Message {
+ collection := mongoClient.Database("chat").Collection("messages")
+ cursor, err := collection.Find(context.TODO(), bson.D{})
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ var Messages []Message
+ if err = cursor.All(context.TODO(), &Messages); err != nil {
+ fmt.Println(err)
+ }
+ return Messages
+}
diff --git a/dockerfile b/dockerfile
index b54d01c..b457dac 100644
--- a/dockerfile
+++ b/dockerfile
@@ -27,7 +27,8 @@ WORKDIR /
# Copy our static executable.
COPY --from=builder /go/main /go/main
-COPY templates /go/templates
+COPY views /go/views
+COPY static /go/static
ENV PORT 8080
ENV GIN_MODE release
diff --git a/go.mod b/go.mod
index 1a25dfa..466800e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/MrBounty/JADE2.0
go 1.22.2
require (
+ github.com/alecthomas/chroma v0.10.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/bytedance/sonic v1.11.5 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
@@ -10,6 +11,7 @@ require (
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/cloudwego/base64x v0.1.3 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
+ github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/edgedb/edgedb-go v0.17.1 // indirect
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect
github.com/flosch/pongo2/v6 v6.0.0 // indirect
@@ -26,6 +28,7 @@ require (
github.com/gofiber/template/html/v2 v2.1.1 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
+ github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.0 // indirect
@@ -51,6 +54,8 @@ require (
github.com/xdg/scram v1.0.5 // indirect
github.com/xdg/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
+ github.com/yuin/goldmark v1.7.1 // indirect
+ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
diff --git a/go.sum b/go.sum
index 35b486c..f8b2195 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
+github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@@ -20,6 +22,8 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
+github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/edgedb/edgedb-go v0.17.1 h1:nWVNWq61X1KyJziy5Zm+NfUwr7nXiCW7/qmH1zMSOpI=
github.com/edgedb/edgedb-go v0.17.1/go.mod h1:J+llluepGAi/rIPNcUgIFEedCCISLKFG+VUEWnBhIqE=
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE=
@@ -53,6 +57,8 @@ github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQ
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2 h1:yEt5djSYb4iNtmV9iJGVday+i4e9u6Mrn5iP64HH5QM=
+github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
@@ -124,7 +130,12 @@ github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=
github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
+github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg=
+github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
diff --git a/main.go b/main.go
index f75ca1a..aaa3bc1 100644
--- a/main.go
+++ b/main.go
@@ -1,73 +1,26 @@
package main
import (
- "context"
- "time"
+ "fmt"
"github.com/flosch/pongo2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/template/django/v3"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/bson/primitive"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
)
-var mongoClient *mongo.Client
var userTmpl *pongo2.Template
var botTmpl *pongo2.Template
-
-type Message struct {
- ID primitive.ObjectID `bson:"_id"`
- Content string `bson:"message"`
- Role string `bson:"role"`
- Date time.Time `bson:"date"`
-}
-
-type Conversation struct {
- ID string
- Messages []Message
-}
-
-type User struct {
- ID string `bson:"_id"`
- Username string `bson:"username"`
- OAth2Token string `bson:"oauth2token"`
- IsSub bool `bson:"isSub"`
-}
-
-type CurrentSession struct {
- ID string
- CurrentConversationID string
- CurrentUserID string
-}
-
-func connectToMongoDB(uri string) {
- serverAPI := options.ServerAPI(options.ServerAPIVersion1)
- opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
- client, err := mongo.Connect(context.TODO(), opts)
- if err != nil {
- panic(err)
- }
- mongoClient = client
-
- if err := mongoClient.Ping(context.TODO(), nil); err != nil {
- panic(err)
- }
-}
+var popoverTmpl *pongo2.Template
func main() {
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html"))
+ popoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/popover.html"))
// Import HTML using django engine/template
engine := django.New("./views", ".html")
- if engine == nil {
- panic("Failed to create django engine")
- }
-
// Create new Fiber instance. Can use any framework. I use fiber for speed and simplicity
app := fiber.New(fiber.Config{
Views: engine,
@@ -75,9 +28,6 @@ func main() {
EnablePrintRoutes: true,
})
- // Initialize
- go connectToMongoDB("mongodb://localhost:27017")
-
// Add default logger
app.Use(logger.New())
@@ -85,150 +35,23 @@ func main() {
app.Static("/", "./static")
// Add routes
- app.Get("/", indexHandler)
- app.Get("/isMongoDBConnected", isMongoDBConnectedHandler)
-
- app.Get("/chat", chatPageHandler) // Complete chat page
- app.Put("/chat", addMessageHandler) // Add message
- app.Delete("/chat", deleteMessageHandler) // Delete message
- app.Get("/loadChat", generateChatHTML) // Load chat
-
- app.Get("/generateOpenai", addOpenaiMessage)
-
- app.Get("/test-button", testButtonHandler)
+ app.Get("/", indexHandler) // Welcome page
+ app.Get("/chat", ChatPageHandler) // Complete chat page
+ app.Post("/requestMultipleMessages", RequestMultipleMessages) // Request multiple messages
+ app.Get("/loadChat", LoadChatHandler) // Load chat
+ app.Post("/deleteMessage", DeleteMessageHandler) // Delete message
+ app.Get("/loadModelSelection", LoadModelSelectionHandler) // Load model selection
+ app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages
+ app.Get("/messageContent", GetMessageContentHandler)
+ app.Get("test", func(c *fiber.Ctx) error {
+ fmt.Println("test")
+ return c.SendString("")
+ })
// Start server
- app.Listen(":3000")
+ app.Listen(":8080")
}
func indexHandler(c *fiber.Ctx) error {
return c.Render("welcome", fiber.Map{}, "layouts/main")
}
-
-func chatPageHandler(c *fiber.Ctx) error {
- return c.Render("chat", fiber.Map{
- "Messages": []Message{},
- }, "layouts/main")
-}
-
-func testButtonHandler(c *fiber.Ctx) error {
- return c.Render("partials/test-button", fiber.Map{})
-}
-
-func addMessageHandler(c *fiber.Ctx) error {
- message := c.FormValue("message")
- lastMessageAsked = message
-
- // Add bot message if there is no error
- userOut, err := userTmpl.Execute(pongo2.Context{"Content": message})
- if err != nil {
- panic(err)
- }
-
- botOut, err := botTmpl.Execute(pongo2.Context{"Content": "Waiting...", "IsPlaceholder": true})
- if err != nil {
- panic(err)
- }
-
- collection := mongoClient.Database("chat").Collection("messages")
- collection.InsertOne(context.Background(), bson.M{"message": message, "role": "user", "date": time.Now()})
-
- return c.SendString(userOut + botOut)
-}
-
-func deleteMessageHandler(c *fiber.Ctx) error {
- messageId := c.FormValue("messageId")
-
- // Convert the string ID to a MongoDB ObjectID
- objID, err := primitive.ObjectIDFromHex(messageId)
- if err != nil {
- // If the conversion fails, return an error response
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
- "error": "Invalid message ID format",
- })
- }
-
- // Delete messages in the database. This one and all the following ones
- collection := mongoClient.Database("chat").Collection("messages")
-
- // Get the date of the message
- var message Message
- err = collection.FindOne(context.TODO(), bson.M{"_id": objID}).Decode(&message)
- if err != nil {
- if err == mongo.ErrNoDocuments {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
- "error": "Message not found",
- })
- }
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to find message",
- })
- }
-
- // Delete the message and all messages with a date after the specified date
- filter := bson.M{
- "$or": []bson.M{
- {"_id": objID},
- {"date": bson.M{"$gt": message.Date}},
- },
- }
- _, err = collection.DeleteMany(context.TODO(), filter)
- if err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to delete messages",
- })
- }
-
- return c.SendString("")
-}
-
-func generateChatHTML(c *fiber.Ctx) error {
- // 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 {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to find messages",
- })
- }
-
- // Convert the cursor to an array of messages
- var Messages []Message
- if err = cursor.All(context.TODO(), &Messages); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
- "error": "Failed to convert cursor to array",
- })
- }
-
- htmlString := "