"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
- Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
+ Icon: "icons/bouvai2.png",
Content: welcomeMessage,
- Hidden: false, // Assuming Hidden is a field you want to include from Message
+ Hidden: false,
Id: "0",
Name: "JADE",
}
@@ -929,7 +934,7 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
stripeSubLink := "https://billing.stripe.com/p/login/6oE6sc0PTfvq1Hi288?prefilled_email=" + user.Email
- openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists := getExistingKeys(c)
+ openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists := getExistingKeysNew(c)
isPremium, isBasic := IsCurrentUserSubscribed(c)
out, err := settingPopoverTmpl.Execute(pongo2.Context{
@@ -943,7 +948,9 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
"NimExists": nimExists,
"PerplexityExists": perplexityExists,
"FireworksExists": fireworksExists,
- "AnyExists": fireworksExists || openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists || nimExists,
+ "TogetherExists": togetherExists,
+ "DeepseekExists": deepseekExists,
+ "AnyExists": fireworksExists || openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists || nimExists || togetherExists || deepseekExists,
"isPremium": isPremium,
"isBasic": isBasic,
"StripeSubLink": stripeSubLink,
diff --git a/MyUtils.go b/MyUtils.go
index 0df1792..d8124a5 100644
--- a/MyUtils.go
+++ b/MyUtils.go
@@ -75,123 +75,73 @@ func addCodeHeader(htmlContent string, languages []string) string {
return updatedHTML
}
-func getExistingKeys(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool, bool, bool, bool) {
+func getExistingKeysNew(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool) {
+ // TODO: Optimize to use only one query
if edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}) == nil {
- return false, false, false, false, false, false, false, false, false
+ return false, false, false, false, false, false, false, false, false, false, false
}
- var (
- openaiExists bool
- anthropicExists bool
- mistralExists bool
- groqExists bool
- gooseaiExists bool
- nimExists bool
- googleExists bool
- perplexityExists bool
- fireworksExists bool
- )
+ var userInfo User
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "openai"
- );
- `, &openaiExists)
+ SELECT global currentUser {
+ setting: {
+ keys: {
+ name,
+ key,
+ company: {
+ name
+ }
+ }
+ }
+ }
+ LIMIT 1;
+ `, &userInfo)
if err != nil {
- fmt.Println("Error checking if OpenAI key exists")
- panic(err)
+ fmt.Println("Error getting user keys", err)
+ return false, false, false, false, false, false, false, false, false, false, false
}
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "anthropic"
- );
- `, &anthropicExists)
- if err != nil {
- fmt.Println("Error checking if Anthropic key exists")
- panic(err)
+ openaiExists := false
+ anthropicExists := false
+ mistralExists := false
+ groqExists := false
+ gooseaiExists := false
+ nimExists := false
+ googleExists := false
+ perplexityExists := false
+ fireworksExists := false
+ togetherExists := false
+ deepseekExists := false
+
+ for _, key := range userInfo.Setting.Keys {
+ switch key.Company.Name {
+ case "openai":
+ openaiExists = true
+ case "anthropic":
+ anthropicExists = true
+ case "mistral":
+ mistralExists = true
+ case "groq":
+ groqExists = true
+ case "gooseai":
+ gooseaiExists = true
+ case "nim":
+ nimExists = true
+ case "google":
+ googleExists = true
+ case "perplexity":
+ perplexityExists = true
+ case "fireworks":
+ fireworksExists = true
+ case "together":
+ togetherExists = true
+ case "deepseek":
+ deepseekExists = true
+ }
}
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "mistral"
- );
- `, &mistralExists)
- if err != nil {
- fmt.Println("Error checking if Mistral key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "groq"
- );
- `, &groqExists)
- if err != nil {
- fmt.Println("Error checking if Groq key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "gooseai"
- );
- `, &gooseaiExists)
- if err != nil {
- fmt.Println("Error checking if GooseAI key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "google"
- );
- `, &googleExists)
- if err != nil {
- fmt.Println("Error checking if Google key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "perplexity"
- );
- `, &perplexityExists)
- if err != nil {
- fmt.Println("Error checking if Perplexity key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "fireworks"
- );
- `, &fireworksExists)
- if err != nil {
- fmt.Println("Error checking if Fireworks key exists")
- panic(err)
- }
-
- err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
- select exists (
- select global currentUser.setting.keys
- filter .company.name = "nim"
- );
- `, &nimExists)
- if err != nil {
- fmt.Println("Error checking if Fireworks key exists")
- panic(err)
- }
-
- return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists
+ return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists
}
func Message2RequestMessage(messages []Message, context string) []RequestMessage {
diff --git a/Request.go b/Request.go
index 1c577a7..6d4363c 100644
--- a/Request.go
+++ b/Request.go
@@ -158,6 +158,10 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
addMessageFunc = addFireworkMessage
case "nim":
addMessageFunc = addNimMessage
+ case "together":
+ addMessageFunc = addTogetherMessage
+ case "deepseek":
+ addMessageFunc = addDeepseekMessage
}
var messageID edgedb.UUID
diff --git a/RequestDeepseek.go b/RequestDeepseek.go
new file mode 100644
index 0000000..cb57220
--- /dev/null
+++ b/RequestDeepseek.go
@@ -0,0 +1,203 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/edgedb/edgedb-go"
+ "github.com/gofiber/fiber/v2"
+)
+
+type DeepseekChatCompletionRequest struct {
+ Model string `json:"model"`
+ Messages []RequestMessage `json:"messages"`
+ MaxTokens int `json:"max_tokens"`
+ Temperature float64 `json:"temperature"`
+}
+
+type DeepseekChatCompletionResponse struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int64 `json:"created"`
+ Model string `json:"model"`
+ Usage DeepseekUsage `json:"usage"`
+ Choices []DeepseekChoice `json:"choices"`
+}
+
+type DeepseekUsage struct {
+ PromptTokens int32 `json:"prompt_tokens"`
+ CompletionTokens int32 `json:"completion_tokens"`
+ TotalTokens int32 `json:"total_tokens"`
+}
+
+type DeepseekChoice struct {
+ Message Message `json:"message"`
+ FinishReason string `json:"finish_reason"`
+ Index int `json:"index"`
+}
+
+func addDeepseekMessage(c *fiber.Ctx, llm LLM, selected bool) edgedb.UUID {
+ Messages := getAllSelectedMessages(c)
+
+ chatCompletion, err := RequestDeepseek(c, llm.Model.ModelID, Messages, float64(llm.Temperature), llm.Context, int(llm.MaxToken))
+ if err != nil {
+ fmt.Println("Error requesting Deepseek: ", err)
+ id := insertBotMessage(c, "Error requesting DeepSeek, model may not be available anymore. Better error message in development.", selected, llm.ID)
+ return id
+ } else if len(chatCompletion.Choices) == 0 {
+ fmt.Println("No response from DeepSeek")
+ id := insertBotMessage(c, "No response from DeepSeek", selected, llm.ID)
+ return id
+ } else {
+ Content := chatCompletion.Choices[0].Message.Content
+ id := insertBotMessage(c, Content, selected, llm.ID)
+ return id
+ }
+}
+
+func TestDeepseekKey(apiKey string) bool {
+ url := "https://api.deepseek.com/chat/completions"
+
+ // Convert messages to OpenAI format
+ deepseekMessages := []RequestMessage{
+ {
+ Role: "user",
+ Content: "Hello",
+ },
+ }
+
+ requestBody := DeepseekChatCompletionRequest{
+ Model: "deepseek-chat",
+ Messages: deepseekMessages,
+ Temperature: 0,
+ MaxTokens: 10,
+ }
+
+ jsonBody, err := json.Marshal(requestBody)
+ if err != nil {
+ fmt.Println("Failed to test Deepseek API key - json.Marshal :", err)
+ return false
+ }
+
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
+ if err != nil {
+ fmt.Println("Failed to test Deepseek API key - http.NewRequest :", err)
+ return false
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+apiKey)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ fmt.Println("Failed to test Deepseek API key - client.Do :", err)
+ return false
+ }
+ defer resp.Body.Close()
+
+ fmt.Println(resp.Status)
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ fmt.Println("Failed to test Deepseek API key - io.ReadAll :", err)
+ return false
+ }
+
+ var chatCompletionResponse DeepseekChatCompletionResponse
+ err = json.Unmarshal(body, &chatCompletionResponse)
+ if err != nil {
+ fmt.Println("Failed to test Deepseek API key - json.Marshal :", err)
+ return false
+ }
+
+ if chatCompletionResponse.Usage.CompletionTokens == 0 {
+ fmt.Println("Failed to test Deepseek API key - No completion tokens :", err)
+ return false
+ }
+
+ return true
+}
+
+func RequestDeepseek(c *fiber.Ctx, model string, messages []Message, temperature float64, context string, maxTokens int) (DeepseekChatCompletionResponse, error) {
+ var apiKey string
+ err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ key
+ } filter .company.name =
$0 AND .$0
+ LIMIT 1
+ `, &usedModelInfo, model)
+ if err != nil {
+ return DeepseekChatCompletionResponse{}, fmt.Errorf("error getting model info: %w", err)
+ }
+
+ var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
+ var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
+ addUsage(c, inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)
+
+ return chatCompletionResponse, nil
+}
diff --git a/RequestTogetherai.go b/RequestTogetherai.go
new file mode 100644
index 0000000..151b031
--- /dev/null
+++ b/RequestTogetherai.go
@@ -0,0 +1,188 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/edgedb/edgedb-go"
+ "github.com/gofiber/fiber/v2"
+)
+
+type TogetherChatCompletionRequest struct {
+ Model string `json:"model"`
+ Messages []RequestMessage `json:"messages"`
+ MaxTokens int `json:"max_tokens"`
+ Temperature float64 `json:"temperature"`
+}
+
+type TogetherChatCompletionResponse struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int64 `json:"created"`
+ Model string `json:"model"`
+ Usage TogetherUsage `json:"usage"`
+ Choices []TogetherChoice `json:"choices"`
+}
+
+type TogetherUsage struct {
+ PromptTokens int32 `json:"prompt_tokens"`
+ CompletionTokens int32 `json:"completion_tokens"`
+ TotalTokens int32 `json:"total_tokens"`
+}
+
+type TogetherChoice struct {
+ Text string `json:"text"`
+ FinishReason string `json:"finish_reason"`
+ Index int `json:"index"`
+}
+
+func addTogetherMessage(c *fiber.Ctx, llm LLM, selected bool) edgedb.UUID {
+ Messages := getAllSelectedMessages(c)
+
+ chatCompletion, err := RequestTogether(c, llm.Model.ModelID, Messages, float64(llm.Temperature), llm.Context, int(llm.MaxToken))
+ if err != nil {
+ fmt.Println("Error requesting Together: ", err)
+ id := insertBotMessage(c, "Error requesting Together, model may not be available anymore. Better error message in development.", selected, llm.ID)
+ return id
+ } else if len(chatCompletion.Choices) == 0 {
+ fmt.Println("No response from Together")
+ id := insertBotMessage(c, "No response from Together", selected, llm.ID)
+ return id
+ } else {
+ Content := chatCompletion.Choices[0].Text
+ id := insertBotMessage(c, Content, selected, llm.ID)
+ return id
+ }
+}
+
+func TestTogetherKey(apiKey string) bool {
+ url := "https://api.together.xyz/v1/completions"
+
+ // Convert messages to OpenAI format
+ togetherMessages := []RequestMessage{
+ {
+ Role: "user",
+ Content: "Hello",
+ },
+ }
+
+ requestBody := TogetherChatCompletionRequest{
+ Model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
+ Messages: togetherMessages,
+ Temperature: 0,
+ MaxTokens: 10,
+ }
+
+ jsonBody, err := json.Marshal(requestBody)
+ if err != nil {
+ return false
+ }
+
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
+ if err != nil {
+ return false
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+apiKey)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return false
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return false
+ }
+
+ var chatCompletionResponse TogetherChatCompletionResponse
+ err = json.Unmarshal(body, &chatCompletionResponse)
+ if err != nil {
+ return false
+ }
+ if chatCompletionResponse.Usage.CompletionTokens == 0 {
+ return false
+ }
+ return true
+}
+
+func RequestTogether(c *fiber.Ctx, model string, messages []Message, temperature float64, context string, maxTokens int) (TogetherChatCompletionResponse, error) {
+ var apiKey string
+ err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ key
+ } filter .company.name = $0 AND .$0
+ LIMIT 1
+ `, &usedModelInfo, model)
+ if err != nil {
+ return TogetherChatCompletionResponse{}, fmt.Errorf("error getting model info: %w", err)
+ }
+
+ var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
+ var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
+ addUsage(c, inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)
+
+ return chatCompletionResponse, nil
+}
diff --git a/TODO.md b/TODO.md
index 573a4d2..c69ce5f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,13 +1,27 @@
# Bugs
-[ ] The SSE event that sometime fails after some times. Reconnect when sending a new message.
+[ ] Sometime I can redo or edit but the button is not available
+[X] The SSE event that sometime fails after some times. Reconnect when sending a new message.
[X] 2 selected messages
[X] On first response, code block width are too long
[X] Change Terms of service to say that I use one cookie
[X] Change the lastSelectedLLMs, 2 users can't use the same time...
[X] CTRL + Enter not working
+[ ] Add all Together AI models
# Features
+[X] SSE to WebSocket
[X] Add max tokens
[ ] Errors messages to know what happend
-[ ] Add Deepseek API
+[ ] Check the response status and add a message accordingly (Like 402 Payment required)
+[X] Add Deepseek API
+[X] Add TogetherAI API
[X] Add Nvidia NIM
+[ ] Host the database on Fly.io
+[ ] Add login with email and password
+[ ] Add login with other provider
+[ ] Better temperature settings. (Anthropic from 0-1, OpenAI from 0-2, DeepSeek from -2-2, ect)
+
+# Other
+[ ] Change the terms of service and enter keys page to an HTML
+[ ] Split Chat.go into smaller files
+[ ] Create a Request package
diff --git a/dbschema/default.esdl b/dbschema/default.esdl
index b1f1512..ba9f631 100644
--- a/dbschema/default.esdl
+++ b/dbschema/default.esdl
@@ -28,7 +28,7 @@ module default {
type Key {
required name: str;
- company: Company;
+ required company: Company;
required key: str;
required date: datetime {
default := datetime_current();
diff --git a/dbschema/migrations/00054-m1crb36.edgeql b/dbschema/migrations/00054-m1crb36.edgeql
new file mode 100644
index 0000000..faa0e36
--- /dev/null
+++ b/dbschema/migrations/00054-m1crb36.edgeql
@@ -0,0 +1,9 @@
+CREATE MIGRATION m1crb36qoksqdmqtzncwo6b5gqb66jlgdfkziozqenrkgwowoj5vka
+ ONTO m1eqooh5xjbysafocihnromqeyrleo57w6txsr36tu73rkju2eyfcq
+{
+ ALTER TYPE default::Key {
+ ALTER LINK company {
+ SET REQUIRED USING ({});
+ };
+ };
+};
diff --git a/main.go b/main.go
index ff69c29..566d749 100644
--- a/main.go
+++ b/main.go
@@ -138,6 +138,8 @@ func addKeys(c *fiber.Ctx) error {
"nim": c.FormValue("nim_key"),
"perplexity": c.FormValue("perplexity_key"),
"fireworks": c.FormValue("fireworks_key"),
+ "together": c.FormValue("together_key"),
+ "deepseek": c.FormValue("deepseek_key"),
}
testFunctions := map[string]func(string) bool{
@@ -150,6 +152,8 @@ func addKeys(c *fiber.Ctx) error {
"nim": TestNimKey,
"perplexity": TestPerplexityKey,
"fireworks": TestFireworkKey,
+ "together": TestTogetherKey,
+ "deepseek": TestDeepseekKey,
}
for company, key := range keys {
diff --git a/static/icons/deepseek.png b/static/icons/deepseek.png
new file mode 100644
index 0000000..d314ccb
Binary files /dev/null and b/static/icons/deepseek.png differ
diff --git a/static/icons/together.png b/static/icons/together.png
new file mode 100644
index 0000000..6d3249c
Binary files /dev/null and b/static/icons/together.png differ
diff --git a/views/partials/explain-llm-conv-chat.html b/views/partials/explain-llm-conv-chat.html
index 1dfb5ba..c840719 100644
--- a/views/partials/explain-llm-conv-chat.html
+++ b/views/partials/explain-llm-conv-chat.html
@@ -4,7 +4,7 @@
the settings menu. Once enter you get access to all models from this provider.
Get OpenAI API key
Get Fireworks API key
+Get Together AI API key
+Get DeepSeek API key
Conversations
diff --git a/views/partials/popover-settings.html b/views/partials/popover-settings.html
index 2a1412b..979e56a 100644
--- a/views/partials/popover-settings.html
+++ b/views/partials/popover-settings.html
@@ -115,6 +115,30 @@
diff --git a/views/partials/welcome-chat.html b/views/partials/welcome-chat.html
index 574444e..f1b3307 100644
--- a/views/partials/welcome-chat.html
+++ b/views/partials/welcome-chat.html
@@ -199,20 +199,25 @@
Fireworks - Fireworks AI offer 1$ of free credits when
- creating an account. Firework AI have a lot of open source models. I may
- add fine tuned models in the future.
+ creating an account. Firework AI have a lot of open source models.
+
+
+ Nvidia NIM - Nvidia NIM offer 1000 free API credit when creating an account with a personal email and 5000 with a business email.
+
+
+ Together AI - Together AI offer 5$ credits when creating an account and have a lot of open source models available.
+
+
+ DeepSeek - DeepSeek do not offer free credits.
+
+
+ Goose AI - Chat API will be available soon.
Custom endpoint - You can also use custom endpoints as long as the key is valid and it use
the openai api.
- Nvidia NIM - Available soon.
-
-
- Goose AI - Chat API will be available soon.
-
-