This commit is contained in:
Adrien Bouvais 2024-06-08 10:27:50 +02:00
parent 419eacc0bb
commit 85e3fc2ec0
16 changed files with 352 additions and 367 deletions

View File

@ -233,7 +233,7 @@ func handleCallbackSignup(c *fiber.Ctx) error {
stripCustID := CreateNewStripeCustomer(providerName, providerEmail) stripCustID := CreateNewStripeCustomer(providerName, providerEmail)
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, ` err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": tokenResponse.AuthToken}).Execute(edgeCtx, `
INSERT User { INSERT User {
stripe_id := <str>$0, stripe_id := <str>$0,
email := <str>$1, email := <str>$1,

52
Chat.go
View File

@ -717,7 +717,7 @@ func LoadUsageKPIHandler(c *fiber.Ctx) error {
} }
func GenerateModelPopoverHTML(refresh bool, c *fiber.Ctx) string { func GenerateModelPopoverHTML(refresh bool, c *fiber.Ctx) string {
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys(c) openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists := getExistingKeys(c)
var llms []LLM var llms []LLM
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, ` err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
@ -747,18 +747,19 @@ func GenerateModelPopoverHTML(refresh bool, c *fiber.Ctx) string {
isPremium, isBasic := IsCurrentUserSubscribed(c) isPremium, isBasic := IsCurrentUserSubscribed(c)
out, err := modelPopoverTmpl.Execute(pongo2.Context{ out, err := modelPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(c), "IsLogin": checkIfLogin(c),
"OpenaiExists": openaiExists, "OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists, "AnthropicExists": anthropicExists,
"MistralExists": mistralExists, "MistralExists": mistralExists,
"GroqExists": groqExists, "GroqExists": groqExists,
"GooseaiExists": gooseaiExists, "GooseaiExists": gooseaiExists,
"GoogleExists": googleExists, "GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists, "PerplexityExists": perplexityExists,
"LLMs": llms, "AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists,
"ModelInfos": modelInfos, "LLMs": llms,
"DeleteUpdate": refresh, "ModelInfos": modelInfos,
"IsSub": isPremium || isBasic, "DeleteUpdate": refresh,
"IsSub": isPremium || isBasic,
}) })
if err != nil { if err != nil {
fmt.Println("Error generating model popover") fmt.Println("Error generating model popover")
@ -845,21 +846,22 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
stripeSubLink := "https://billing.stripe.com/p/login/6oE6sc0PTfvq1Hi288?prefilled_email=" + user.Email stripeSubLink := "https://billing.stripe.com/p/login/6oE6sc0PTfvq1Hi288?prefilled_email=" + user.Email
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys(c) openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists := getExistingKeys(c)
isPremium, isBasic := IsCurrentUserSubscribed(c) isPremium, isBasic := IsCurrentUserSubscribed(c)
out, err := settingPopoverTmpl.Execute(pongo2.Context{ out, err := settingPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(c), "IsLogin": checkIfLogin(c),
"OpenaiExists": openaiExists, "OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists, "AnthropicExists": anthropicExists,
"MistralExists": mistralExists, "MistralExists": mistralExists,
"GroqExists": groqExists, "GroqExists": groqExists,
"GooseaiExists": gooseaiExists, "GooseaiExists": gooseaiExists,
"GoogleExists": googleExists, "GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists, "PerplexityExists": perplexityExists,
"isPremium": isPremium, "AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists,
"isBasic": isBasic, "isPremium": isPremium,
"StripeSubLink": stripeSubLink, "isBasic": isBasic,
"StripeSubLink": stripeSubLink,
}) })
if err != nil { if err != nil {
fmt.Println("Error loading settings") fmt.Println("Error loading settings")

View File

@ -12,11 +12,6 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// So I have one client and one context for the database. All query use the same and it work well so far.
var edgeCtx context.Context
var edgeGlobalClient *edgedb.Client
// I will not put a comment on all type, I think they are self-explaining.
type Identity struct { type Identity struct {
ID edgedb.UUID `edgedb:"id"` ID edgedb.UUID `edgedb:"id"`
Issuer string `edgedb:"issuer"` Issuer string `edgedb:"issuer"`
@ -54,10 +49,6 @@ type Conversation struct {
User User `edgedb:"user"` User User `edgedb:"user"`
} }
// An area is in between messages and conversation.
// In a normal chat, you have a list of message, easy. By here you need to add, kind of a new dimension.
// All "message" can have multiple messages. So I created a new type named Area.
// A conversation is a list of Area and an Area is a list of Message. Easy enough.
type Area struct { type Area struct {
ID edgedb.UUID `edgedb:"id"` ID edgedb.UUID `edgedb:"id"`
Position int64 `edgedb:"position"` Position int64 `edgedb:"position"`
@ -68,7 +59,7 @@ type Message struct {
ID edgedb.UUID `edgedb:"id"` ID edgedb.UUID `edgedb:"id"`
Content string `edgedb:"content"` Content string `edgedb:"content"`
Role string `edgedb:"role"` Role string `edgedb:"role"`
Selected bool `edgedb:"selected"` // Selected can also be seen as "Active". This is the message that will be use for the request. Selected bool `edgedb:"selected"`
Date time.Time `edgedb:"date"` Date time.Time `edgedb:"date"`
Area Area `edgedb:"area"` Area Area `edgedb:"area"`
Conv Conversation `edgedb:"conversation"` Conv Conversation `edgedb:"conversation"`
@ -85,9 +76,6 @@ type Usage struct {
OutputToken int32 `edgedb:"output_token"` OutputToken int32 `edgedb:"output_token"`
} }
// A LLM is a bad name but I like it.
// It is more one instance of a model with it parameters.
// Maybe I will add more options later.
type LLM struct { type LLM struct {
ID edgedb.UUID `edgedb:"id"` ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"` Name string `edgedb:"name"`
@ -120,6 +108,9 @@ type CompanyInfo struct {
Icon string `edgedb:"icon"` Icon string `edgedb:"icon"`
} }
var edgeCtx context.Context
var edgeGlobalClient *edgedb.Client
func init() { func init() {
var ctx = context.Background() var ctx = context.Background()
client, err := edgedb.CreateClient(ctx, edgedb.Options{}) client, err := edgedb.CreateClient(ctx, edgedb.Options{})
@ -288,7 +279,8 @@ func getAllSelectedMessages(c *fiber.Ctx) []Message {
func checkIfHaveKey(c *fiber.Ctx) bool { func checkIfHaveKey(c *fiber.Ctx) bool {
var keys []Key var keys []Key
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, "SELECT global currentUser.setting.keys", &keys) err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx,
"SELECT global currentUser.setting.keys", &keys)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -78,18 +78,19 @@ func addCodeHeader(htmlContent string, languages []string) string {
return updatedHTML return updatedHTML
} }
func getExistingKeys(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool) { func getExistingKeys(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool, bool) {
if edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}) == nil { if edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}) == nil {
return false, false, false, false, false, false return false, false, false, false, false, false, false
} }
var ( var (
openaiExists bool openaiExists bool
anthropicExists bool anthropicExists bool
mistralExists bool mistralExists bool
groqExists bool groqExists bool
gooseaiExists bool gooseaiExists bool
googleExists bool googleExists bool
perplexityExists bool
) )
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, ` err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
@ -158,7 +159,18 @@ func getExistingKeys(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool) {
panic(err) panic(err)
} }
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists 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)
}
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists
} }
func Message2RequestMessage(messages []Message, context string) []RequestMessage { func Message2RequestMessage(messages []Message, context string) []RequestMessage {
@ -218,7 +230,9 @@ func GetAvailableModels(c *fiber.Ctx) []ModelInfo {
name, name,
icon icon
} }
} FILTER .modelID != 'none' AND .company.name != 'huggingface' AND .company IN global currentUser.setting.keys.company }
FILTER .modelID != 'none' AND .company.name != 'huggingface' AND .company IN global currentUser.setting.keys.company
ORDER BY .company.name ASC THEN .name ASC
`, &models) `, &models)
if err != nil { if err != nil {
fmt.Println("Error getting models") fmt.Println("Error getting models")

View File

@ -137,6 +137,8 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
addMessageFunc = addHuggingfaceMessage addMessageFunc = addHuggingfaceMessage
case "google": case "google":
addMessageFunc = addGoogleMessage addMessageFunc = addGoogleMessage
case "perplexity":
addMessageFunc = addPerplexityMessage
} }
var messageID edgedb.UUID var messageID edgedb.UUID
@ -203,6 +205,7 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
outIcon := `<img src="` + selectedLLMs[idx].Model.Company.Icon + `" alt="User Image" id="selectedIcon-` + fmt.Sprintf("%d", message.Area.Position) + `">` outIcon := `<img src="` + selectedLLMs[idx].Model.Company.Icon + `" alt="User Image" id="selectedIcon-` + fmt.Sprintf("%d", message.Area.Position) + `">`
go func() { go func() {
// I do a ping because of sse size limit
sendEvent( sendEvent(
"swapContent-"+fmt.Sprintf("%d", message.Area.Position), "swapContent-"+fmt.Sprintf("%d", message.Area.Position),
`<hx hx-get="/messageContent?id=`+message.ID.String()+`" hx-trigger="load" hx-swap="outerHTML"></hx>`, `<hx hx-get="/messageContent?id=`+message.ID.String()+`" hx-trigger="load" hx-swap="outerHTML"></hx>`,

184
RequestPerplexity.go Normal file
View File

@ -0,0 +1,184 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/edgedb/edgedb-go"
"github.com/gofiber/fiber/v2"
)
type PerplexityChatCompletionRequest struct {
Model string `json:"model"`
Messages []RequestMessage `json:"messages"`
Temperature float64 `json:"temperature"`
}
type PerplexityChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Usage PerplexityUsage `json:"usage"`
Choices []PerplexityChoice `json:"choices"`
}
type PerplexityUsage struct {
PromptTokens int32 `json:"prompt_tokens"`
CompletionTokens int32 `json:"completion_tokens"`
TotalTokens int32 `json:"total_tokens"`
}
type PerplexityChoice struct {
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func addPerplexityMessage(c *fiber.Ctx, llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages(c)
chatCompletion, err := RequestPerplexity(c, llm.Model.ModelID, Messages, float64(llm.Temperature), llm.Context)
if err != nil {
fmt.Println("Error fetching user profile")
panic(err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println("No response from Perplexity")
id := insertBotMessage(c, "No response from Perplexity", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Message.Content
id := insertBotMessage(c, Content, selected, llm.ID)
return id
}
}
func TestPerplexityKey(apiKey string) bool {
url := "https://api.perplexity.ai/chat/completions"
// Convert messages to OpenAI format
perplexityMessages := []RequestMessage{
{
Role: "user",
Content: "Hello",
},
}
requestBody := PerplexityChatCompletionRequest{
Model: "llama-3-8b-instruct",
Messages: perplexityMessages,
Temperature: 0,
}
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 PerplexityChatCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return false
}
if chatCompletionResponse.Usage.CompletionTokens == 0 {
return false
}
return true
}
func RequestPerplexity(c *fiber.Ctx, model string, messages []Message, temperature float64, context string) (PerplexityChatCompletionResponse, 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 = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey, "perplexity")
if err != nil {
return PerplexityChatCompletionResponse{}, fmt.Errorf("error getting Perplexity API key: %w", err)
}
url := "https://api.perplexity.ai/chat/completions"
requestBody := PerplexityChatCompletionRequest{
Model: model,
Messages: Message2RequestMessage(messages, context),
Temperature: temperature,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return PerplexityChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
return PerplexityChatCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
}
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 PerplexityChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return PerplexityChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
}
var chatCompletionResponse PerplexityChatCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return PerplexityChatCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
}
var usedModelInfo ModelInfo
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
SELECT ModelInfo {
inputPrice,
outputPrice
}
FILTER .modelID = <str>$0
LIMIT 1
`, &usedModelInfo, model)
if err != nil {
return PerplexityChatCompletionResponse{}, 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
}

363
main.go
View File

@ -94,7 +94,6 @@ func main() {
app.Post("/clearChat", ClearChatHandler) app.Post("/clearChat", ClearChatHandler)
app.Get("/userMessage", GetUserMessageHandler) app.Get("/userMessage", GetUserMessageHandler)
app.Post("/editMessage", EditMessageHandler) app.Post("/editMessage", EditMessageHandler)
app.Post("/archiveDefaultConversation", ArchiveDefaultConversationHandler)
app.Get("/help", generateHelpChatHandler) app.Get("/help", generateHelpChatHandler)
// Settings routes // Settings routes
@ -103,14 +102,16 @@ func main() {
// Popovers // Popovers
app.Get("/loadModelSelection", LoadModelSelectionHandler) app.Get("/loadModelSelection", LoadModelSelectionHandler)
app.Get("/loadConversationSelection", LoadConversationSelectionHandler) app.Get("/loadConversationSelection", LoadConversationSelectionHandler)
app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
app.Get("/loadUsageKPI", LoadUsageKPIHandler) app.Get("/loadUsageKPI", LoadUsageKPIHandler)
app.Get("/loadSettings", LoadSettingsHandler) app.Get("/loadSettings", LoadSettingsHandler)
app.Post("/updateLLMPositionBatch", updateLLMPositionBatch) app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
// Conversation routes
app.Get("/createConversation", CreateConversationHandler) app.Get("/createConversation", CreateConversationHandler)
app.Get("/deleteConversation", DeleteConversationHandler) app.Get("/deleteConversation", DeleteConversationHandler)
app.Get("/selectConversation", SelectConversationHandler) app.Get("/selectConversation", SelectConversationHandler)
app.Post("/updateConversationPositionBatch", updateConversationPositionBatch) app.Post("/updateConversationPositionBatch", updateConversationPositionBatch)
app.Post("/archiveDefaultConversation", ArchiveDefaultConversationHandler)
// Authentication // Authentication
app.Get("/signin", handleUiSignIn) app.Get("/signin", handleUiSignIn)
@ -119,8 +120,9 @@ func main() {
app.Get("/callbackSignup", handleCallbackSignup) app.Get("/callbackSignup", handleCallbackSignup)
// LLM // LLM
app.Get("deleteLLM", deleteLLM) app.Get("/deleteLLM", deleteLLM)
app.Post("/createLLM", createLLM) app.Post("/createLLM", createLLM)
app.Post("/updateLLMPositionBatch", updateLLMPositionBatch)
// Add static files // Add static files
app.Static("/", "./static") app.Static("/", "./static")
@ -170,310 +172,73 @@ func main() {
} }
func addKeys(c *fiber.Ctx) error { func addKeys(c *fiber.Ctx) error {
openaiKey := c.FormValue("openai_key") keys := map[string]string{
anthropicKey := c.FormValue("anthropic_key") "openai": c.FormValue("openai_key"),
mistralKey := c.FormValue("mistral_key") "anthropic": c.FormValue("anthropic_key"),
groqKey := c.FormValue("groq_key") "mistral": c.FormValue("mistral_key"),
gooseaiKey := c.FormValue("goose_key") "groq": c.FormValue("groq_key"),
googleKey := c.FormValue("google_key") "gooseai": c.FormValue("goose_key"),
var Exists bool "google": c.FormValue("google_key"),
"perplexity": c.FormValue("perplexity_key"),
// Handle OpenAI key
if openaiKey != "" {
if !TestOpenaiKey(openaiKey) {
return c.SendString("Invalid OpenAI API Key\n")
}
// Check if the company key already exists
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"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if OpenAI key exists")
panic(err)
}
if Exists {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
UPDATE Key filter .company.name = <str>$0 AND .key = <str>$1
SET {
key := <str>$1,
}
`, "openai", openaiKey)
if err != nil {
fmt.Println("Error updating OpenAI key")
panic(err)
}
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "openai" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "OpenAI API Key",
}
)
}`, openaiKey)
if err != nil {
fmt.Println("Error adding OpenAI key")
panic(err)
}
}
} }
// Handle Anthropic key testFunctions := map[string]func(string) bool{
if anthropicKey != "" { "openai": TestOpenaiKey,
if !TestAnthropicKey(anthropicKey) { "anthropic": TestAnthropicKey,
return c.SendString("Invalid Anthropic API Key\n") "mistral": TestMistralKey,
} "groq": TestGroqKey,
"gooseai": TestGooseaiKey,
// Check if the company key already exists "google": TestGoogleKey,
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, ` "perplexity": TestPerplexityKey,
select exists (
select global currentUser.setting.keys
filter .company.name = "anthropic"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if Anthropic key exists")
panic(err)
}
if Exists {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
UPDATE Key filter .company.name = "anthropic" AND .key = <str>$0
SET {
key := <str>$0,
}
`, anthropicKey)
if err != nil {
fmt.Println("Error updating Anthropic key")
panic(err)
}
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "anthropic" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Anthropic API Key",
}
)
}`, anthropicKey)
if err != nil {
fmt.Println("Error adding Anthropic key")
panic(err)
}
}
} }
// Handle Mistral key for company, key := range keys {
if mistralKey != "" { if key != "" {
if !TestMistralKey(mistralKey) { if !testFunctions[company](key) {
return c.SendString("Invalid Mistral API Key\n") return c.SendString(fmt.Sprintf("Invalid %s API Key\n", company))
}
// Check if the company key already exists
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"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if Mistral key exists")
panic(err)
}
if Exists {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
UPDATE Key filter .company.name = "mistral" AND .key = <str>$0
SET {
key := <str>$0,
} }
`, mistralKey)
var Exists bool
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 = <str>$0
);
`, &Exists, company)
if err != nil { if err != nil {
fmt.Println("Error updating Mistral key") fmt.Println("Error checking if key exists")
panic(err) panic(err)
} }
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "mistral" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Mistral API Key",
}
)
}`, mistralKey)
if err != nil {
fmt.Println("Error adding Mistral key")
panic(err)
}
}
}
// Handle Groq key if Exists {
if groqKey != "" { err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
if !TestGroqKey(groqKey) { UPDATE Key filter .company.name = <str>$0 AND .key = <str>$1
return c.SendString("Invalid Groq API Key\n") SET {
} key := <str>$1,
}
// Check if the company key already exists `, company, key)
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, ` if err != nil {
select exists ( fmt.Println("Error updating key")
select global currentUser.setting.keys panic(err)
filter .company.name = "groq" }
); } else {
`, &Exists) err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
if err != nil { WITH
fmt.Println("Error checking if Groq key exists") c := (SELECT Company FILTER .name = <str>$0 LIMIT 1)
panic(err) UPDATE global currentUser.setting
} SET {
keys += (
if Exists { INSERT Key {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, ` company := c,
UPDATE Key filter .company.name = "groq" AND .key = <str>$0 key := <str>$1,
SET { name := <str>$2 ++ " API Key",
key := <str>$0, }
} )
`, groqKey) }`, company, key, company)
if err != nil { if err != nil {
fmt.Println("Error updating Groq key") fmt.Println("Error adding key")
panic(err) panic(err)
} }
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "groq" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Groq API Key",
}
)
}`, groqKey)
if err != nil {
fmt.Println("Error adding Groq key")
panic(err)
}
}
}
// Handle Gooseai key
if gooseaiKey != "" {
if !TestGooseaiKey(gooseaiKey) {
return c.SendString("Invalid Gooseai API Key\n")
}
// Check if the company key already exists
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"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if Gooseai key exists")
panic(err)
}
if Exists {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
UPDATE Key filter .company.name = "gooseai" AND .key = <str>$0
SET {
key := <str>$0,
}
`, gooseaiKey)
if err != nil {
fmt.Println("Error updating Gooseai key")
panic(err)
}
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "gooseai" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Gooseai API Key",
}
)
}`, gooseaiKey)
if err != nil {
fmt.Println("Error adding Gooseai key")
panic(err)
}
}
}
// Handle Google key
if googleKey != "" {
if !TestGoogleKey(googleKey) {
return c.SendString("Invalid Google API Key\n")
}
// Check if the company key already exists
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"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if Google key exists")
panic(err)
}
if Exists {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
UPDATE Key filter .company.name = "google" AND .key = <str>$0
SET {
key := <str>$0,
}
`, googleKey)
if err != nil {
fmt.Println("Error updating Google key")
panic(err)
}
} else {
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "google" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Google API Key",
}
)
}`, googleKey)
if err != nil {
fmt.Println("Error adding Google key")
panic(err)
} }
} }
} }

BIN
static/icons/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
static/icons/perplexity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -86,7 +86,9 @@
textarea.addEventListener('keydown', handleTextareaKeydown); textarea.addEventListener('keydown', handleTextareaKeydown);
function toggleSendButton() { function toggleSendButton() {
document.getElementById('chat-input-send-btn').disabled = textarea.value.trim().length === 0 || document.getElementsByClassName('selected icon-llm').length === 0; // check if generate-multiple-messages exists
var generateMultipleMessages = document.getElementById('generate-multiple-messages');
document.getElementById('chat-input-send-btn').disabled = textarea.value.trim().length === 0 || document.getElementsByClassName('selected icon-llm').length === 0 || generateMultipleMessages !== null;
} }
function clearTextArea() { function clearTextArea() {

View File

@ -1,4 +1,4 @@
<h2>JADE: The First Multi-Model Chatbot</h2> <h1>JADE: The First Multi-Model Chatbot</h1>
<p>JADE was built with simplicity in mind. The goal is to have a minimalist chatbot that supports all models without <p>JADE was built with simplicity in mind. The goal is to have a minimalist chatbot that supports all models without
unnecessary features like importing files or images. This focus on simplicity allows us to improve how we use AI unnecessary features like importing files or images. This focus on simplicity allows us to improve how we use AI
chatbots in other ways.</p> chatbots in other ways.</p>
@ -78,6 +78,7 @@
<li>llama3-8b-8192</li> <li>llama3-8b-8192</li>
<li>llama3-70b-8192</li> <li>llama3-70b-8192</li>
<li>gemma-7b-it</li> <li>gemma-7b-it</li>
<li>mixtral-8x7b-32768</li>
</ul> </ul>
</li> </li>
<li>Google: <li>Google:
@ -88,4 +89,7 @@
</ul> </ul>
</li> </li>
<li>Inference Endpoints (More custom to come)</li> <li>Inference Endpoints (More custom to come)</li>
</ul> </ul>
<p>You can contact me at adrien.bouvais.pro@gmail.com if you want to add a new provider or if you have any questions.
</p>

View File

@ -73,7 +73,7 @@
<div class="is-flex is-align-items-start"> <div class="is-flex is-align-items-start">
<div class="message-content" id="content-{{ ConversationAreaId }}" <div class="message-content" id="content-{{ ConversationAreaId }}"
sse-swap="swapContent-{{ ConversationAreaId }}"> sse-swap="swapContent-{{ ConversationAreaId }}">
<hx hx-trigger="load" hx-get="/generateMultipleMessages"></hx> <hx hx-trigger="load" hx-get="/generateMultipleMessages" id="generate-multiple-messages"></hx>
<div class='message-header'> <div class='message-header'>
<p> <p>
Waiting... Waiting...

View File

@ -28,11 +28,11 @@
</span> </span>
</button> </button>
<div> <div>
<button disabled class="button is-small is-primary mr-2 ml-5"> <!--button disabled class="button is-small is-primary mr-2 ml-5">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-pen"></i> <i class="fa-solid fa-pen"></i>
</span> </span>
</button> </button-->
<button class="button is-small is-success is-outlined" id="create-model-button"> <button class="button is-small is-success is-outlined" id="create-model-button">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus"></i>
@ -48,7 +48,7 @@
<div class="select is-fullwidth is-small mb-3" id="model-id-input"> <div class="select is-fullwidth is-small mb-3" id="model-id-input">
<select name="selectedLLMId"> <select name="selectedLLMId">
{% for modelInfo in ModelInfos %} {% for modelInfo in ModelInfos %}
<option value="{{ modelInfo.ModelID }}">{{ modelInfo.ModelID }}</option> <option value="{{ modelInfo.ModelID }}">{{ modelInfo.Name }}</option>
{% endfor %} {% endfor %}
<option value="{% if IsSub %}custom{% else %}none{% endif %}">Inference Endpoints</option> <option value="{% if IsSub %}custom{% else %}none{% endif %}">Inference Endpoints</option>
</select> </select>

View File

@ -51,8 +51,7 @@
</span> </span>
</p> </p>
</div> </div>
<div class="field has-addons is-hidden" title="Gemini is unavailable because of Europe" <div class="field has-addons is-hidden" id="gemini-field">
id="gemini-field">
<p class="control has-icons-left is-expanded"> <p class="control has-icons-left is-expanded">
<input class="input is-small {% if GoogleExists %}is-success{% endif %}" type="text" <input class="input is-small {% if GoogleExists %}is-success{% endif %}" type="text"
placeholder="Google API key" name="google_key" autocomplete="off"> placeholder="Google API key" name="google_key" autocomplete="off">
@ -61,6 +60,15 @@
</span> </span>
</p> </p>
</div> </div>
<div class="field has-addons is-hidden" id="perplexity-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if PerplexityExists %}is-success{% endif %}" type="text"
placeholder="Perplexity API key" name="perplexity_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="goose-field" <div class="field has-addons is-hidden" id="goose-field"
title="GooseAI chat API will be available soon"> title="GooseAI chat API will be available soon">
<p class="control has-icons-left is-expanded"> <p class="control has-icons-left is-expanded">

View File

@ -1,20 +1,31 @@
<h1>JADE: The First Multi-Model Chatbot</h1> <h1>JADE: The First Multi-Model Chatbot</h1>
<p>JADE was built with simplicity in mind. The goal is to have a minimalist chatbot that supports all models without <p>The world of Large Language Models (LLMs) is vast and exciting, with each model boasting unique strengths and
unnecessary features like importing files or images. This focus on simplicity allows us to improve how we use AI weaknesses. However, this variety presents a challenge: using all available LLMs is practically impossible due to
chatbots in other ways.</p> cost and complexity. Wouldn't it be incredible to have an easy way to experiment with different models, compare
their responses, and even choose the best model for a specific task?</p>
<p>One of my main concerns was the variety of models available, each excelling in different areas. Unfortunately using <p>This is precisely why JADE was built. With a focus on simplicity, JADE eliminates unnecessary features like file or
all of them can be impractical and very expensive.</p> image uploads, allowing you to seamlessly interact with a variety of LLMs. This streamlined approach unlocks the
potential to compare models, leverage their individual strengths, and even mitigate biases through multi-message
conversations.</p>
<h2>Multi-Models</h2> <h2>Multi-Models</h2>
<p>To address this, I created the first Multi-Model chatbot. The idea is to use multiple models within the same
conversation. Here are the key points:</p> <p>JADE is the first Multi-Model chatbot. The idea is to use multiple models within the same conversation. Here are the
key points:</p>
<ol> <ol>
<li>When asking a question, you can query multiple models and compare their responses to choose the best one.</li> <li>When asking a question, you can query multiple models and compare their responses to choose the best one.</li>
<li>The selected response can be used as the basis for the next message across all models. For example, a response <li>The selected response can be used as the basis for the next message across all models.</li>
from GPT-4 can be used by Claude Haiku in the next interaction.</li>
</ol> </ol>
<p>For example, a response from GPT-4 can be used by Claude Haiku in the next interaction</p>
<p>This approach offers several benefits. First, it ensures you always have access to the best possible response by
leveraging the strengths of different models. Second, it provides a more comprehensive understanding of a topic by
considering various perspectives. Finally, using responses from one model as context for another can lead to more
engaging and insightful conversations.</p>
<a class="button is-primary mt-2 mb-2" href="/signin"> <a class="button is-primary mt-2 mb-2" href="/signin">
Sign in Sign in
</a> </a>