Jade/Chat.go
2024-05-31 20:41:22 +02:00

918 lines
28 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"net/url"
"sort"
"strings"
"time"
"github.com/edgedb/edgedb-go"
"github.com/flosch/pongo2"
"github.com/gofiber/fiber/v2"
)
func ChatPageHandler(c *fiber.Ctx) error {
authCookie := c.Cookies("jade-edgedb-auth-token", "")
if authCookie != "" && !checkIfLogin() {
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie})
}
var (
isSub bool
limitReach bool
)
if !checkIfLogin() {
isSub = false
limitReach = false
} else {
isSub = IsCurrentUserSubscribed()
limitReach = IsCurrentUserLimiteReached()
}
return c.Render("chat", fiber.Map{"IsLogin": checkIfLogin(), "HaveKey": checkIfHaveKey(), "IsSubscribed": isSub, "IsLimiteReached": limitReach}, "layouts/main")
}
func DeleteMessageHandler(c *fiber.Ctx) error {
messageId := c.FormValue("id")
messageUUID, err := edgedb.ParseUUID(messageId)
if err != nil {
fmt.Println("Error parsing UUID")
panic(err)
}
// Delete all messages
err = edgeClient.Execute(edgeCtx, `
WITH
messageArea := (SELECT Message FILTER .id = <uuid>$0).area
DELETE Area
FILTER .position >= messageArea.position AND .conversation = messageArea.conversation;
`, messageUUID)
if err != nil {
fmt.Println("Error deleting messages")
panic(err)
}
return c.SendString(generateChatHTML())
}
func LoadChatHandler(c *fiber.Ctx) error {
deleteLLMtoDelete()
if checkIfLogin() {
if IsCurrentUserLimiteReached() && !IsCurrentUserSubscribed() {
return c.SendString(generateLimitReachedChatHTML())
} else if !checkIfHaveKey() {
return c.SendString(generateEnterKeyChatHTML())
}
return c.SendString(generateChatHTML())
} else {
return c.SendString(generateWelcomeChatHTML())
}
}
type TemplateMessage struct {
Icon string
Content string
Hidden bool
Id string
Name string
Model string
ModelID string
}
func generateChatHTML() string {
// Println the name of the current conversation
var currentConv Conversation
err := edgeClient.QuerySingle(edgeCtx, `
SELECT global currentConversation { name }`, &currentConv)
if err != nil {
fmt.Println("Error getting current conversation")
panic(err)
}
fmt.Println("Current conversation: ", currentConv.Name)
// Maybe redo that to be area by area because look like shit rn. It come from early stage of dev. It work tho soooo...
var Messages []Message
err = edgeClient.Query(edgeCtx, `
SELECT Message {
id,
selected,
role,
content,
date,
llm : {
name,
modelInfo : {
modelID,
name,
company : {
icon
}
}
}
}
FILTER .conversation = global currentConversation AND .conversation.user = global currentUser
ORDER BY .date ASC
`, &Messages)
if err != nil {
fmt.Println("Error getting messages")
panic(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 templateMessages []TemplateMessage
for i, message := range Messages {
if message.Role == "user" {
htmlContent := markdownToHTML(message.Content)
userOut, err := userTmpl.Execute(pongo2.Context{"Content": htmlContent, "ID": message.ID.String()})
if err != nil {
fmt.Println("Error executing user template")
panic(err)
}
htmlString += userOut
// Reset NextMessages when a user message is encountered
templateMessages = []TemplateMessage{}
} else {
// For bot messages, add them to NextMessages with only the needed fields
templateMessage := TemplateMessage{
Icon: message.LLM.Model.Company.Icon, // 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.String(),
Name: message.LLM.Name,
Model: message.LLM.Model.Name,
ModelID: message.LLM.Model.ModelID,
}
templateMessages = append(templateMessages, templateMessage)
// 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(templateMessages, func(i, j int) bool {
if !templateMessages[i].Hidden && templateMessages[j].Hidden {
return true
}
if templateMessages[i].Hidden && !templateMessages[j].Hidden {
return false
}
return true
})
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": templateMessages, "ConversationAreaId": i})
if err != nil {
fmt.Println("Error executing bot template")
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
}
}
}
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}
func GetUserMessageHandler(c *fiber.Ctx) error {
id := c.FormValue("id")
messageUUID, _ := edgedb.ParseUUID(id)
var selectedMessage Message
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message {
content
}
FILTER
.id = <uuid>$0;
`, &selectedMessage, messageUUID)
if err != nil {
fmt.Println("Error getting message")
panic(err)
}
out, err := userTmpl.Execute(pongo2.Context{"Content": markdownToHTML(selectedMessage.Content), "ID": id})
if err != nil {
fmt.Println("Error executing user template")
panic(err)
}
return c.SendString(out)
}
func GetMessageContentHandler(c *fiber.Ctx) error {
messageId := c.FormValue("id")
onlyContent := c.FormValue("onlyContent") // To init the text area of the edit message form
messageUUID, _ := edgedb.ParseUUID(messageId)
var selectedMessage Message
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message {
content,
llm : {
name,
modelInfo : {
modelID,
name,
}
}
}
FILTER
.id = <uuid>$0;
`, &selectedMessage, messageUUID)
if err != nil {
panic(err)
}
if onlyContent == "true" {
return c.SendString(markdownToHTML(selectedMessage.Content))
}
out := "<div class='message-header'>"
out += "<p>"
out += "<strong>" + selectedMessage.LLM.Name + "</strong> <small>" + selectedMessage.LLM.Model.ModelID + "</small>"
out += " </p>"
out += "</div>"
out += "<div class='message-body'>"
out += " <ct class='content'>"
out += markdownToHTML(selectedMessage.Content)
out += " </ct>"
out += "</div>"
// Update the selected value of messages in the database
err = edgeClient.Execute(edgeCtx, `
WITH m := (SELECT Message FILTER .id = <uuid>$0)
UPDATE Message
FILTER .area = m.area
SET {selected := false};
`, messageUUID)
if err != nil {
panic(err)
}
_ = edgeClient.Execute(edgeCtx, `
UPDATE Message
FILTER .id = <uuid>$0
SET {selected := true};
`, messageUUID)
return c.SendString(out)
}
func generateWelcomeChatHTML() string {
welcomeMessage := `
No fancy stuff, easy to use, pay as you use, multi-models, work on low 3g...
To start using JADE, please login.`
loginButton := `
<a class="button is-primary is-small" href="/signin">
Log in
</a>`
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: "<br>" + welcomeMessage + loginButton,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
}
NextMessages = append(NextMessages, nextMsg)
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true})
if err != nil {
fmt.Println("Error executing bot template")
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}
func generateEnterKeyChatHTML() string {
welcomeMessage := `
<p class="mt-2">JADE require at least one API key to work. Add one in the settings at the bottom right of the page.</p>
<p>API keys are unique codes that allow you to access and use the services provided by different Large Language Model (LLM) providers. When you sign up for an account with a provider like OpenAI, Anthropic, Groq, or MistralAI, you'll receive an API key that you can use to make requests to their LLMs.</p>
<p>Most providers offer free credits when you first sign up, which means you have a balance in your account that you can use to generate messages. You pay for the service based on the number of tokens generated. A token is a small unit of text, roughly equivalent to 3 characters. So, a small input and output text will cost very little.</p>
<p>To get an idea of how many tokens your text contains, you can use a tokenizer like the one provided by OpenAI: <a href="https://platform.openai.com/tokenizer" target="_blank">https://platform.openai.com/tokenizer</a>. Keep in mind that different models may have slightly different tokenizers.</p>
<h2>Pricing Examples</h2>
<ul>
<li>If you ask a small question that generates around 100 tokens (approximately 300 characters), and the price is $5 per 1 million tokens, it would cost you $0.0005.</li>
<li>With this pricing, you can ask 2,000 small questions for just $1 using GPT-4o, or 20,000 questions using GPT-3.5 turbo.</li>
<li>For a larger text, like a PDF file with 30,000 characters (roughly 10,000 tokens), you would pay around $0.05 per message.</li>
</ul>
<p>Remember, prices and token limits may vary depending on the provider and the specific LLM you're using.</p>
<h2>Learn More</h2>
<p>To learn more about the different LLM providers and their offerings, check out their websites:</p>
<ul>
<li><a href="https://www.openai.com/" target="https://platform.openai.com/docs/overview">OpenAI</a></li>
<li><a href="https://www.anthropic.com/" target="https://console.anthropic.com/">Anthropic</a></li>
<li><a href="https://groq.com/" target="https://console.groq.com/login">Groq</a></li>
<li><a href="https://mistral.ai/" target="https://console.mistral.ai/">MistralAI</a></li>
</ul>
`
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: welcomeMessage,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
}
NextMessages = append(NextMessages, nextMsg)
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true})
if err != nil {
fmt.Println("Error executing bot template")
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}
func generateTermAndServiceHandler(c *fiber.Ctx) error {
return c.SendString(generateTermAndServiceChatHTML())
}
func generateTermAndServiceChatHTML() string {
welcomeMessage := `
<h1 class="mt-2">Terms of Service</h1>
<h2>1. Acceptance of Terms</h2>
<p>By using JADE (the "App"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, please do not use the App.</p>
<h2>2. Description of Service</h2>
<p>The App is a chatbot that makes requests to various third-party APIs to provide information and services. The App is provided "as is" and "as available" without any warranties of any kind.</p>
<h2>3. User Responsibilities</h2>
<p>- You are responsible for any content you generate or share using the App.</p>
<p>- You agree not to use the App for any unlawful or harmful activities.</p>
<h2>4. Authentication and Payment</h2>
<p>- Authentication for the App is managed by Google and GitHub. We are not responsible for any issues related to authentication, including but not limited to, unauthorized access or data breaches. Please refer to Google and GitHub's respective terms of service and privacy policies for more information.</p>
<p>- Payments for any services within the App are processed by Stripe. We are not responsible for any issues related to payment processing, including but not limited to, transaction errors or unauthorized transactions. Please refer to Stripe's terms of service and privacy policy for more information.</p>
<h2>5. Disclaimer of Warranties</h2>
<p>- The App is provided without warranties of any kind, either express or implied, including but not limited to, implied warranties of merchantability, fitness for a particular purpose, or non-infringement.</p>
<p>- We do not guarantee the accuracy, completeness, or usefulness of any information provided by the App.</p>
<h2>6. Limitation of Liability</h2>
<p>- In no event shall BouvAI, its affiliates, or its licensors be liable for any indirect, incidental, special, consequential, or punitive damages, including but not limited to, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your use or inability to use the App; (ii) any unauthorized access to or use of our servers and/or any personal information stored therein; (iii) any bugs, viruses, trojan horses, or the like that may be transmitted to or through our App by any third party; or (iv) any errors or omissions in any content or for any loss or damage incurred as a result of the use of any content posted, emailed, transmitted, or otherwise made available through the App, whether based on warranty, contract, tort (including negligence), or any other legal theory, whether or not we have been informed of the possibility of such damage.</p>
<h2>7. Data Privacy</h2>
<p>- We are not responsible for any data leaks or breaches that may occur. Users are advised to use the App at their own risk.</p>
<p>- We do not store any personal data unless explicitly stated otherwise.</p>
<h2>8. Changes to the Terms</h2>
<p>We reserve the right to modify these Terms at any time. Any changes will be effective immediately upon posting the updated Terms on our website or within the App. Your continued use of the App after any such changes constitutes your acceptance of the new Terms.</p>
<h2>9. Governing Law</h2>
<p>These Terms shall be governed and construed in accordance with the laws of [Your Country/State], without regard to its conflict of law principles.</p>
<h2>10. Contact Information</h2>
<p>If you have any questions about these Terms, please contact us at adrien.bouvais.pro@gmail.com.</p>
<p>**BouvAI**</p>
`
closeBtn := `
<div class="is-flex is-justify-content-flex-end">
<a class="button is-small is-danger is-outlined" hx-get="/loadChat" hx-target="#chat-container" hx-swap="outerHTML"
hx-trigger="click">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</a>
</div>`
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: welcomeMessage + closeBtn,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
}
NextMessages = append(NextMessages, nextMsg)
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true})
if err != nil {
fmt.Println("Error executing bot template")
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}
func generateLimitReachedChatHTML() string {
welcomeMessage := `You have reached the maximum number of messages for a free account. Please upgrade your account to continue using JADE.`
var result User
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser { stripe_id, email } LIMIT 1;", &result)
if err != nil {
fmt.Println("Error getting current user")
panic(err)
}
clientSecretSession := CreateClientSecretSession()
// TODO Replace by live API call
stripeTable := `
<stripe-pricing-table
pricing-table-id="prctbl_1PJAxDP2nW0okNQyY0Q3mbg4"
publishable-key="pk_live_51OxXuWP2nW0okNQyme1qdwbL535jbMmM1uIUi6U5zcvEUUwKraktmpCzudXNdPSTxlHpw2FbCtxpwbyFFcasQ7aj000tJJGpWW"
customer-session-client-secret="` + clientSecretSession + `">
</stripe-pricing-table>`
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: "<br>" + welcomeMessage + "<br>" + stripeTable,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
}
NextMessages = append(NextMessages, nextMsg)
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true})
if err != nil {
fmt.Println("Error executing bot template")
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}
// Buton actions
func GetEditMessageFormHandler(c *fiber.Ctx) error {
id := c.FormValue("id")
idUUID, _ := edgedb.ParseUUID(id)
var message Message
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message { content }
FILTER .id = <uuid>$0;
`, &message, idUUID)
if err != nil {
fmt.Println("Error getting message")
panic(err)
}
// Calculate the number of rows based on the length of the content
rows := len(strings.Split(message.Content, "\n"))
if rows < 10 {
rows = 10
}
out, err := messageEditTmpl.Execute(pongo2.Context{"Content": message.Content, "ID": id, "Rows": rows})
if err != nil {
fmt.Println("Error executing user template")
panic(err)
}
return c.SendString(out)
}
func RedoMessageHandler(c *fiber.Ctx) error {
messageId := c.FormValue("id")
messageUUID, _ := edgedb.ParseUUID(messageId)
var selectedLLMIds []string
err := json.Unmarshal([]byte(c.FormValue("selectedLLMIds")), &selectedLLMIds)
if err != nil {
fmt.Println("Error unmarshalling selected LLM IDs")
panic(err)
}
var message Message
err = edgeClient.QuerySingle(context.Background(), `
SELECT Message { content }
FILTER .id = <uuid>$0;
`, &message, messageUUID)
if err != nil {
fmt.Println("Error getting message")
panic(err)
}
// Delete messages
err = edgeClient.Execute(edgeCtx, `
WITH
messageArea := (SELECT Message FILTER .id = <uuid>$0).area
DELETE Area
FILTER .position > messageArea.position AND .conversation = messageArea.conversation AND .conversation.user = global currentUser;
`, messageUUID)
if err != nil {
fmt.Println("Error deleting messages")
panic(err)
}
return c.SendString(GeneratePlaceholderHTML(message.Content, selectedLLMIds, false))
}
func EditMessageHandler(c *fiber.Ctx) error {
messageId := c.FormValue("id")
message := c.FormValue("message")
messageUUID, _ := edgedb.ParseUUID(messageId)
var selectedLLMIds []string
err := json.Unmarshal([]byte(c.FormValue("selectedLLMIds")), &selectedLLMIds)
if err != nil {
fmt.Println("Error unmarshalling selected LLM IDs")
panic(err)
}
if len(selectedLLMIds) == 0 {
return c.SendString("")
}
// Delete messages
err = edgeClient.Execute(edgeCtx, `
WITH
messageArea := (SELECT Message FILTER .id = <uuid>$0).area
DELETE Area
FILTER .position >= messageArea.position AND .conversation = messageArea.conversation AND .conversation.user = global currentUser;
`, messageUUID)
if err != nil {
fmt.Println("Error deleting messages")
panic(err)
}
return c.SendString(GeneratePlaceholderHTML(message, selectedLLMIds, true))
}
func ClearChatHandler(c *fiber.Ctx) error {
// Delete the default conversation
err := edgeClient.Execute(edgeCtx, `
DELETE Area
FILTER .conversation = global currentConversation;
`)
if err != nil {
fmt.Println("Error deleting messages")
panic(err)
}
return c.SendString(generateChatHTML())
}
// Popover stuff
func LoadUsageKPIHandler(c *fiber.Ctx) error {
if !checkIfLogin() || !checkIfHaveKey() {
return c.SendString("")
}
InputDateID := c.FormValue("month", time.Now().Format("01-2006"))
offset := c.FormValue("offset")
IsActive := false
var InputDate time.Time
InputDate, err := time.Parse("01-2006", InputDateID)
if err != nil {
fmt.Println("Error parsing date")
panic(err)
}
if offset == "-1" {
InputDate = InputDate.AddDate(0, -1, 0)
IsActive = true
} else if offset == "1" {
InputDate = InputDate.AddDate(0, 1, 0)
IsActive = true
}
type UsageKPI struct {
Key struct {
ModelID string `edgedb:"model_id"`
} `edgedb:"key"`
TotalCost float32 `edgedb:"total_cost"`
TotalCount int64 `edgedb:"total_count"`
}
var usages []UsageKPI
err = edgeClient.Query(edgeCtx, `
WITH
U := (
SELECT Usage
FILTER .user = global currentUser and .date >= <datetime>$0 AND .date < <datetime>$1
),
grouped := (
GROUP U {
model_id,
input_cost,
output_cost,
} BY .model_id
)
SELECT grouped {
key := .key { model_id },
total_count := count(.elements),
total_cost := sum(.elements.input_cost) + sum(.elements.output_cost),
} FILTER .total_count > 0 ORDER BY .total_cost DESC
`, &usages, InputDate, InputDate.AddDate(0, 1, 0))
if err != nil {
fmt.Println("Error getting usage")
panic(err)
}
BeautifullDate := InputDate.Format("Jan 2006")
var (
TotalCount int64
TotalCost float32
)
for _, usage := range usages {
TotalCost += usage.TotalCost
TotalCount += usage.TotalCount
}
out, err := usagePopoverTmpl.Execute(pongo2.Context{
"usages": usages,
"TotalCost": TotalCost,
"TotalCount": TotalCount,
"Date": BeautifullDate,
"DateID": InputDate.Format("01-2006"),
"IsActive": IsActive,
})
if err != nil {
fmt.Println("Error generating usage")
panic(err)
}
return c.SendString(out)
}
func GenerateModelPopoverHTML(refresh bool) string {
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys()
var llms []LLM
err := edgeClient.Query(edgeCtx, `
SELECT LLM {
id,
name,
context,
temperature,
modelInfo : {
modelID,
name,
company : {
name,
icon
}
}
}
FILTER .user = global currentUser AND .name != 'none' AND .to_delete = false
ORDER BY .position
`, &llms)
if err != nil {
fmt.Println("Error loading LLMs")
panic(err)
}
modelInfos := GetAvailableModels()
out, err := modelPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(),
"OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists,
"LLMs": llms,
"ModelInfos": modelInfos,
"DeleteUpdate": refresh,
"IsSub": IsCurrentUserSubscribed(),
})
if err != nil {
fmt.Println("Error generating model popover")
panic(err)
}
return out
}
func LoadModelSelectionHandler(c *fiber.Ctx) error {
if !checkIfLogin() || !checkIfHaveKey() {
return c.SendString("")
}
return c.SendString(GenerateModelPopoverHTML(false))
}
func GenerateConversationPopoverHTML(isActive bool) string {
var conversations []Conversation
err := edgeClient.Query(edgeCtx, `
SELECT Conversation {
name,
position,
selected,
id
}
FILTER .user = global currentUser
ORDER BY .position
`, &conversations)
if err != nil {
fmt.Println("Error loading conversations")
panic(err)
}
out, err := conversationPopoverTmpl.Execute(pongo2.Context{
"Conversations": conversations,
"IsActive": isActive,
})
if err != nil {
fmt.Println("Error generating conversation popover")
panic(err)
}
return out
}
func LoadConversationSelectionHandler(c *fiber.Ctx) error {
if !checkIfLogin() || !checkIfHaveKey() {
return c.SendString("")
}
return c.SendString(GenerateConversationPopoverHTML(false))
}
func RefreshConversationSelectionHandler(c *fiber.Ctx) error {
IsActive := c.FormValue("IsActive") == "true"
return c.SendString(GenerateConversationPopoverHTML(!IsActive))
}
func LoadSettingsHandler(c *fiber.Ctx) error {
if !checkIfLogin() {
return c.SendString("")
}
var user User
err := edgeClient.QuerySingle(edgeCtx, `
SELECT User {
email
}
FILTER .id = global currentUser.id
`, &user)
if err != nil {
fmt.Println("Error loading user")
panic(err)
}
// Percent encoding of the email
user.Email = url.QueryEscape(user.Email)
stripeSubLink := "https://billing.stripe.com/p/login/test_eVa5kC1q7dogaaIcMM?prefilled_email=" + user.Email
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys()
out, err := settingPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(),
"OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists,
"IsSub": IsCurrentUserSubscribed(),
"StripeSubLink": stripeSubLink,
})
if err != nil {
fmt.Println("Error loading settings")
panic(err)
}
return c.SendString(out)
}
func CreateConversationHandler(c *fiber.Ctx) error {
name := c.FormValue("conversation-name-input")
if name == "" {
name = "New Conversation"
}
err := edgeClient.Execute(edgeCtx, `
WITH
C := (
SELECT Conversation
FILTER .user = global currentUser
)
INSERT Conversation {
name := <str>$0,
user := global currentUser,
position := count(C) + 1
}
`, name)
if err != nil {
fmt.Println("Error creating conversation")
panic(err)
}
return c.SendString(GenerateConversationPopoverHTML(true))
}
func DeleteConversationHandler(c *fiber.Ctx) error {
conversationId := c.FormValue("conversationId")
conversationUUID, err := edgedb.ParseUUID(conversationId)
if err != nil {
fmt.Println("Error parsing UUID")
panic(err)
}
err = edgeClient.Execute(edgeCtx, `
DELETE Conversation
FILTER .user = global currentUser AND .id = <uuid>$0;
`, conversationUUID)
if err != nil {
fmt.Println("Error deleting conversation")
panic(err)
}
// Select the default conversation
err = edgeClient.Execute(edgeCtx, `
UPDATE Conversation
FILTER .user = global currentUser AND .name = 'Default'
SET {
selected := true
};
`)
if err != nil {
fmt.Println("Error selecting default conversation")
panic(err)
}
reloadChatTriggerHTML := `
<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML" hx-target="#chat-container" style="display: none;"></hx>
`
return c.SendString(GenerateConversationPopoverHTML(true) + reloadChatTriggerHTML)
}
func SelectConversationHandler(c *fiber.Ctx) error {
conversationId := c.FormValue("conversation-id")
conversationUUID, err := edgedb.ParseUUID(conversationId)
if err != nil {
fmt.Println("Error parsing UUID")
panic(err)
}
err = edgeClient.Execute(edgeCtx, `
UPDATE Conversation
FILTER .user = global currentUser
SET {
selected := false
};
`, conversationUUID)
if err != nil {
fmt.Println("Error unselecting conversations")
panic(err)
}
err = edgeClient.Execute(edgeCtx, `
UPDATE Conversation
FILTER .user = global currentUser AND .id = <uuid>$0
SET {
selected := true
};
`, conversationUUID)
if err != nil {
fmt.Println("Error selecting conversations")
panic(err)
}
edgeClient = edgeClient.WithoutGlobals().WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")})
return c.SendString(generateChatHTML())
}