diff --git a/.dockerignore b/.dockerignore index 8fce603..6320cd2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -data/ +data \ No newline at end of file diff --git a/Chat.go b/Chat.go index ec52b15..622cec5 100644 --- a/Chat.go +++ b/Chat.go @@ -398,8 +398,8 @@ func generateLimitReachedChatHTML() string { // TODO Replace by live API call stripeTable := ` ` @@ -449,8 +449,7 @@ func GetEditMessageFormHandler(c *fiber.Ctx) error { rows = 10 } - tmpl := pongo2.Must(pongo2.FromFile("views/partials/message-edit-form.html")) - out, err := tmpl.Execute(pongo2.Context{"Content": message.Content, "ID": id, "Rows": rows}) + out, err := messageEditTmpl.Execute(pongo2.Context{"Content": message.Content, "ID": id, "Rows": rows}) if err != nil { fmt.Println("Error executing user template") panic(err) @@ -608,7 +607,7 @@ func LoadUsageKPIHandler(c *fiber.Ctx) error { TotalCount += usage.TotalCount } - out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-usage.html")).Execute(pongo2.Context{ + out, err := usagePopoverTmpl.Execute(pongo2.Context{ "usages": usages, "TotalCost": TotalCost, "TotalCount": TotalCount, @@ -624,29 +623,6 @@ func LoadUsageKPIHandler(c *fiber.Ctx) error { return c.SendString(out) } -func LoadKeysHandler(c *fiber.Ctx) error { - if !checkIfLogin() { - return c.SendString("") - } - openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys() - - out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-keys.html")).Execute(pongo2.Context{ - "IsLogin": checkIfLogin(), - "OpenaiExists": openaiExists, - "AnthropicExists": anthropicExists, - "MistralExists": mistralExists, - "GroqExists": groqExists, - "GooseaiExists": gooseaiExists, - "GoogleExists": googleExists, - "AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists, - }) - if err != nil { - fmt.Println("Error loading keys") - panic(err) - } - return c.SendString(out) -} - func GenerateModelPopoverHTML(refresh bool) string { openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys() @@ -676,7 +652,7 @@ func GenerateModelPopoverHTML(refresh bool) string { modelInfos := GetAvailableModels() - out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-models.html")).Execute(pongo2.Context{ + out, err := modelPopoverTmpl.Execute(pongo2.Context{ "IsLogin": checkIfLogin(), "OpenaiExists": openaiExists, "AnthropicExists": anthropicExists, @@ -720,7 +696,7 @@ func GenerateConversationPopoverHTML(isActive bool) string { panic(err) } - out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-conversation.html")).Execute(pongo2.Context{ + out, err := conversationPopoverTmpl.Execute(pongo2.Context{ "Conversations": conversations, "IsActive": isActive, }) @@ -768,7 +744,7 @@ func LoadSettingsHandler(c *fiber.Ctx) error { openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys() - out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-settings.html")).Execute(pongo2.Context{ + out, err := settingPopoverTmpl.Execute(pongo2.Context{ "IsLogin": checkIfLogin(), "OpenaiExists": openaiExists, "AnthropicExists": anthropicExists, diff --git a/RequestMistral.go b/RequestMistral.go index 8807e89..669b8aa 100644 --- a/RequestMistral.go +++ b/RequestMistral.go @@ -72,11 +72,13 @@ func TestMistralKey(apiKey string) bool { jsonBody, err := json.Marshal(requestBody) if err != nil { + fmt.Println("Error marshalling request to Mistral") return false } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody)) if err != nil { + fmt.Println("Error creating request to Mistral") return false } @@ -87,21 +89,25 @@ func TestMistralKey(apiKey string) bool { client := &http.Client{} resp, err := client.Do(req) if err != nil { + fmt.Println("Error sending request to Mistral") return false } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { + fmt.Println("Error reading response from Mistral") return false } var chatCompletionResponse MistralChatCompletionResponse err = json.Unmarshal(body, &chatCompletionResponse) if err != nil { + fmt.Println("Error unmarshalling response from Mistral") return false } if chatCompletionResponse.Usage.CompletionTokens == 0 { + fmt.Println("No response from Mistral") return false } return true diff --git a/Stripe.go b/Stripe.go index 13b6b75..8cb29d3 100644 --- a/Stripe.go +++ b/Stripe.go @@ -31,8 +31,8 @@ func generatePricingTableChatHTML() string { // TODO Replace by live API call stripeTable := ` ` diff --git a/_commands.md b/_commands.md deleted file mode 100644 index ef5508b..0000000 --- a/_commands.md +++ /dev/null @@ -1,3 +0,0 @@ - -docker build -t mrbounty1/jade:jadeAlpha1 . -docker push mrbounty1/jade:jadeAlpha1 \ No newline at end of file diff --git a/database.go b/database.go index 310d95e..9122841 100644 --- a/database.go +++ b/database.go @@ -196,7 +196,7 @@ func insertUserMessage(content string) edgedb.UUID { conversation := global currentConversation, selected := true, llm := ( - SELECT LLM FILTER .id = "32cb4f0c-1c3f-11ef-9247-2fac0086a7d1" + SELECT LLM FILTER .id = "f777f024-1da9-11ef-afd4-bf1c8b177a62" ) } `, &inserted, "user", content, lastArea.ID) diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..52365b2 --- /dev/null +++ b/fly.toml @@ -0,0 +1,21 @@ +# fly.toml app configuration file generated for jade-2-0 on 2024-05-28T19:35:25+02:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'jade-2-0' +primary_region = 'cdg' + +[build] + +[http_service] + internal_port = 8080 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + size = 'shared-cpu-1x' + memory = "256MB" \ No newline at end of file diff --git a/main.go b/main.go index d6ed758..807e7f3 100644 --- a/main.go +++ b/main.go @@ -32,19 +32,30 @@ import ( "bufio" "fmt" "log" + "os" "sync" "github.com/flosch/pongo2" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/favicon" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/template/django/v3" "github.com/stripe/stripe-go" ) -var userTmpl *pongo2.Template -var botTmpl *pongo2.Template -var selectBtnTmpl *pongo2.Template +var ( + userTmpl *pongo2.Template + botTmpl *pongo2.Template + selectBtnTmpl *pongo2.Template + modelPopoverTmpl *pongo2.Template + usagePopoverTmpl *pongo2.Template + settingPopoverTmpl *pongo2.Template + messageEditTmpl *pongo2.Template + conversationPopoverTmpl *pongo2.Template + clients = make(map[chan SSE]bool) + mu sync.Mutex +) // SSE event structure type SSE struct { @@ -52,12 +63,6 @@ type SSE struct { Data string } -// Global channel and mutex -var ( - clients = make(map[chan SSE]bool) - mu sync.Mutex -) - // Function to send events to all clients func sendEvent(event, data string) { mu.Lock() @@ -69,12 +74,17 @@ func sendEvent(event, data string) { } func main() { - // TODO Change key to env and the live one - stripe.Key = "sk_test_51OxXuWP2nW0okNQyiNAOcBTTWZSiyP1el5KOmV3yIv1DQR0415YPsH1eb89SLrsOFj80o9p2AxGOy042e53yDvZN00jHxHAbE6" + // Use STRIPE_KEY environment variable + stripe.Key = os.Getenv("STRIPE_KEY") botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html")) userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html")) selectBtnTmpl = pongo2.Must(pongo2.FromFile("views/partials/model-selection-btn.html")) + modelPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/popover-models.html")) + conversationPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/popover-conversation.html")) + usagePopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/popover-usage.html")) + settingPopoverTmpl = pongo2.Must(pongo2.FromFile("views/partials/popover-settings.html")) + messageEditTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-edit-form.html")) // Import HTML using django engine/template engine := django.New("./views", ".html") @@ -82,11 +92,13 @@ func main() { // Create new Fiber instance app := fiber.New(fiber.Config{ Views: engine, - AppName: "JADE 2.0", + AppName: "JADE", EnablePrintRoutes: true, }) + defer app.Shutdown() // Add default logger + app.Use(favicon.New()) app.Use(logger.New()) app.Use(recover.New()) @@ -118,7 +130,6 @@ func main() { app.Get("/loadConversationSelection", LoadConversationSelectionHandler) app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler) app.Get("/loadUsageKPI", LoadUsageKPIHandler) - app.Get("/loadKeys", LoadKeysHandler) app.Get("/loadSettings", LoadSettingsHandler) app.Post("/updateLLMPositionBatch", updateLLMPositionBatch) app.Get("/createConversation", CreateConversationHandler) @@ -175,7 +186,9 @@ func main() { }) // Start server - log.Fatal(app.Listen(":8080")) + if err := app.Listen(":8080"); err != nil { + log.Fatal(err) + } } // The route to add keys, idk where to put it so it's here diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..4a25080 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/style.css b/static/style.css index 706e1e1..bfcc4bd 100644 --- a/static/style.css +++ b/static/style.css @@ -1,5 +1,5 @@ body { - padding-bottom: 155px; + padding-bottom: 40px; } html { diff --git a/views/chat.html b/views/chat.html index c789346..f49317e 100644 --- a/views/chat.html +++ b/views/chat.html @@ -57,9 +57,7 @@ textarea.addEventListener('keydown', handleTextareaKeydown); function toggleSendButton() { - const sendButton = document.getElementById('chat-input-send-btn'); - const selectedLLMIds = JSON.parse(getSelectedModelsIDs()); - sendButton.disabled = textarea.value.trim() === '' || selectedLLMIds.length === 0; + document.getElementById('chat-input-send-btn').disabled = textarea.value.trim().length === 0 || document.getElementsByClassName('selected icon-llm').length === 0; } function clearTextArea() { @@ -75,12 +73,10 @@ } function handleTextareaKeydown(event) { - if (event.metaKey && event.key === 'Enter') { + if (event.metaKey && event.key === 'Enter' && event.target === textarea && textarea.value.trim() !== '' && document.getElementsByClassName('selected icon-llm').length !== 0) { // Check if the cursor is in the textarea - if (event.target === textarea) { - // Trigger the same action as the send button - document.getElementById('chat-input-send-btn').click(); - } + // Trigger the same action as the send button + document.getElementById('chat-input-send-btn').click(); } } \ No newline at end of file diff --git a/views/layouts/main.html b/views/layouts/main.html index 2ad6fbd..ab7bb78 100644 --- a/views/layouts/main.html +++ b/views/layouts/main.html @@ -86,6 +86,63 @@ button.innerHTML = originalText; }, 2000); } + + let lastSelectedIndex = null; + + function toggleSelection(element) { + const elements = Array.from(document.getElementsByClassName('icon-llm')); + const index = elements.indexOf(element); + + if (document.body.classList.contains('shift-pressed') && lastSelectedIndex !== null) { + const [start, end] = [lastSelectedIndex, index].sort((a, b) => a - b); + let allSelected = true; + for (let i = start; i <= end; i++) { + if (!elements[i].classList.contains('selected')) { + allSelected = false; + break; + } + } + for (let i = start; i <= end; i++) { + if (allSelected) { + elements[i].classList.remove('selected'); + elements[i].classList.add('unselected'); + } else { + elements[i].classList.add('selected'); + elements[i].classList.remove('unselected'); + } + } + lastSelectedIndex = null; + + const elements2 = Array.from(document.getElementsByClassName('icon-text')); + for (let i = 0; i < elements2.length; i++) { + elements2[i].classList.remove('shiftselected'); + } + } else if (document.body.classList.contains('shift-pressed') && lastSelectedIndex === null) { + lastSelectedIndex = index; + element.classList.toggle('shiftselected'); + } else { + element.classList.toggle('selected'); + element.classList.toggle('unselected'); + } + + // If at least one model is selected, enable the delete button + if (document.getElementsByClassName('selected icon-llm').length > 0) { + document.getElementById('delete-model-button').disabled = false; + } else { + document.getElementById('delete-model-button').disabled = true; + } + + toggleSendButton(); + } + + function getSelectedModelsIDs() { + var selectedModelsIDs = []; + var selectedModels = document.getElementsByClassName('selected icon-llm'); + for (var i = 0; i < selectedModels.length; i++) { + selectedModelsIDs.push(selectedModels[i].getAttribute('data-id')); + } + return selectedModelsIDs.length > 0 ? JSON.stringify(selectedModelsIDs) : '[]'; + } diff --git a/views/partials/popover-models.html b/views/partials/popover-models.html index 9c9e015..7e3a2f0 100644 --- a/views/partials/popover-models.html +++ b/views/partials/popover-models.html @@ -127,11 +127,13 @@ }) document.getElementById('create-model-button').addEventListener('click', function () { + console.log('create model'); document.getElementById('models-list').classList.add('is-hidden'); document.getElementById('models-creation').classList.remove('is-hidden'); }); document.getElementById('cancel-create-model-button').addEventListener('click', function () { + console.log('cancel create model'); document.getElementById('models-list').classList.remove('is-hidden'); document.getElementById('models-creation').classList.add('is-hidden'); }); @@ -161,68 +163,5 @@ event.preventDefault(); } }); - - let lastSelectedIndex = null; - - function toggleSelection(element) { - const elements = Array.from(document.getElementsByClassName('icon-llm')); - const index = elements.indexOf(element); - - if (document.body.classList.contains('shift-pressed') && lastSelectedIndex !== null) { - const [start, end] = [lastSelectedIndex, index].sort((a, b) => a - b); - let allSelected = true; - for (let i = start; i <= end; i++) { - if (!elements[i].classList.contains('selected')) { - allSelected = false; - break; - } - } - for (let i = start; i <= end; i++) { - if (allSelected) { - elements[i].classList.remove('selected'); - elements[i].classList.add('unselected'); - } else { - elements[i].classList.add('selected'); - elements[i].classList.remove('unselected'); - } - } - lastSelectedIndex = null; - - const elements2 = Array.from(document.getElementsByClassName('icon-text')); - for (let i = 0; i < elements2.length; i++) { - elements2[i].classList.remove('shiftselected'); - } - } else if (document.body.classList.contains('shift-pressed') && lastSelectedIndex === null) { - lastSelectedIndex = index; - element.classList.toggle('shiftselected'); - } else { - element.classList.toggle('selected'); - element.classList.toggle('unselected'); - } - - // If at least one model is selected, enable the delete button - if (document.getElementsByClassName('selected icon-llm').length > 0) { - document.getElementById('delete-model-button').disabled = false; - } else { - document.getElementById('delete-model-button').disabled = true; - } - - toggleSendButton(); - } - - function getSelectedModelsIDs() { - var selectedModelsIDs = []; - var selectedModels = document.getElementsByClassName('selected icon-llm'); - for (var i = 0; i < selectedModels.length; i++) { - selectedModelsIDs.push(selectedModels[i].getAttribute('data-id')); - } - return selectedModelsIDs.length > 0 ? JSON.stringify(selectedModelsIDs) : '[]'; - } - - function toggleSendButton() { - var selectedModels = document.getElementsByClassName('selected'); - var sendButton = document.getElementById('chat-input-send-btn'); - sendButton.disabled = selectedModels.length === 0; - } \ No newline at end of file