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)
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 {
stripe_id := <str>$0,
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 {
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys(c)
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists := getExistingKeys(c)
var llms []LLM
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)
out, err := modelPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(c),
"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": isPremium || isBasic,
"IsLogin": checkIfLogin(c),
"OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"PerplexityExists": perplexityExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists,
"LLMs": llms,
"ModelInfos": modelInfos,
"DeleteUpdate": refresh,
"IsSub": isPremium || isBasic,
})
if err != nil {
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
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys(c)
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists := getExistingKeys(c)
isPremium, isBasic := IsCurrentUserSubscribed(c)
out, err := settingPopoverTmpl.Execute(pongo2.Context{
"IsLogin": checkIfLogin(c),
"OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists,
"isPremium": isPremium,
"isBasic": isBasic,
"StripeSubLink": stripeSubLink,
"IsLogin": checkIfLogin(c),
"OpenaiExists": openaiExists,
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"PerplexityExists": perplexityExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists,
"isPremium": isPremium,
"isBasic": isBasic,
"StripeSubLink": stripeSubLink,
})
if err != nil {
fmt.Println("Error loading settings")

View File

@ -12,11 +12,6 @@ import (
"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 {
ID edgedb.UUID `edgedb:"id"`
Issuer string `edgedb:"issuer"`
@ -54,10 +49,6 @@ type Conversation struct {
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 {
ID edgedb.UUID `edgedb:"id"`
Position int64 `edgedb:"position"`
@ -68,7 +59,7 @@ type Message struct {
ID edgedb.UUID `edgedb:"id"`
Content string `edgedb:"content"`
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"`
Area Area `edgedb:"area"`
Conv Conversation `edgedb:"conversation"`
@ -85,9 +76,6 @@ type Usage struct {
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 {
ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"`
@ -120,6 +108,9 @@ type CompanyInfo struct {
Icon string `edgedb:"icon"`
}
var edgeCtx context.Context
var edgeGlobalClient *edgedb.Client
func init() {
var ctx = context.Background()
client, err := edgedb.CreateClient(ctx, edgedb.Options{})
@ -288,7 +279,8 @@ func getAllSelectedMessages(c *fiber.Ctx) []Message {
func checkIfHaveKey(c *fiber.Ctx) bool {
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 {
panic(err)
}

View File

@ -78,18 +78,19 @@ func addCodeHeader(htmlContent string, languages []string) string {
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 {
return false, false, false, false, false, false
return false, false, false, false, false, false, false
}
var (
openaiExists bool
anthropicExists bool
mistralExists bool
groqExists bool
gooseaiExists bool
googleExists bool
openaiExists bool
anthropicExists bool
mistralExists bool
groqExists bool
gooseaiExists bool
googleExists bool
perplexityExists bool
)
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)
}
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 {
@ -218,7 +230,9 @@ func GetAvailableModels(c *fiber.Ctx) []ModelInfo {
name,
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)
if err != nil {
fmt.Println("Error getting models")

View File

@ -137,6 +137,8 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
addMessageFunc = addHuggingfaceMessage
case "google":
addMessageFunc = addGoogleMessage
case "perplexity":
addMessageFunc = addPerplexityMessage
}
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) + `">`
go func() {
// I do a ping because of sse size limit
sendEvent(
"swapContent-"+fmt.Sprintf("%d", message.Area.Position),
`<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.Get("/userMessage", GetUserMessageHandler)
app.Post("/editMessage", EditMessageHandler)
app.Post("/archiveDefaultConversation", ArchiveDefaultConversationHandler)
app.Get("/help", generateHelpChatHandler)
// Settings routes
@ -103,14 +102,16 @@ func main() {
// Popovers
app.Get("/loadModelSelection", LoadModelSelectionHandler)
app.Get("/loadConversationSelection", LoadConversationSelectionHandler)
app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
app.Get("/loadUsageKPI", LoadUsageKPIHandler)
app.Get("/loadSettings", LoadSettingsHandler)
app.Post("/updateLLMPositionBatch", updateLLMPositionBatch)
app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
// Conversation routes
app.Get("/createConversation", CreateConversationHandler)
app.Get("/deleteConversation", DeleteConversationHandler)
app.Get("/selectConversation", SelectConversationHandler)
app.Post("/updateConversationPositionBatch", updateConversationPositionBatch)
app.Post("/archiveDefaultConversation", ArchiveDefaultConversationHandler)
// Authentication
app.Get("/signin", handleUiSignIn)
@ -119,8 +120,9 @@ func main() {
app.Get("/callbackSignup", handleCallbackSignup)
// LLM
app.Get("deleteLLM", deleteLLM)
app.Get("/deleteLLM", deleteLLM)
app.Post("/createLLM", createLLM)
app.Post("/updateLLMPositionBatch", updateLLMPositionBatch)
// Add static files
app.Static("/", "./static")
@ -170,310 +172,73 @@ func main() {
}
func addKeys(c *fiber.Ctx) error {
openaiKey := c.FormValue("openai_key")
anthropicKey := c.FormValue("anthropic_key")
mistralKey := c.FormValue("mistral_key")
groqKey := c.FormValue("groq_key")
gooseaiKey := c.FormValue("goose_key")
googleKey := c.FormValue("google_key")
var Exists bool
// 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)
}
}
keys := map[string]string{
"openai": c.FormValue("openai_key"),
"anthropic": c.FormValue("anthropic_key"),
"mistral": c.FormValue("mistral_key"),
"groq": c.FormValue("groq_key"),
"gooseai": c.FormValue("goose_key"),
"google": c.FormValue("google_key"),
"perplexity": c.FormValue("perplexity_key"),
}
// Handle Anthropic key
if anthropicKey != "" {
if !TestAnthropicKey(anthropicKey) {
return c.SendString("Invalid Anthropic 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 = "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)
}
}
testFunctions := map[string]func(string) bool{
"openai": TestOpenaiKey,
"anthropic": TestAnthropicKey,
"mistral": TestMistralKey,
"groq": TestGroqKey,
"gooseai": TestGooseaiKey,
"google": TestGoogleKey,
"perplexity": TestPerplexityKey,
}
// Handle Mistral key
if mistralKey != "" {
if !TestMistralKey(mistralKey) {
return c.SendString("Invalid Mistral 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 = "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,
for company, key := range keys {
if key != "" {
if !testFunctions[company](key) {
return c.SendString(fmt.Sprintf("Invalid %s API Key\n", company))
}
`, 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 {
fmt.Println("Error updating Mistral key")
fmt.Println("Error checking if key exists")
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 groqKey != "" {
if !TestGroqKey(groqKey) {
return c.SendString("Invalid Groq 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 = "groq"
);
`, &Exists)
if err != nil {
fmt.Println("Error checking if Groq 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 = "groq" AND .key = <str>$0
SET {
key := <str>$0,
}
`, groqKey)
if err != nil {
fmt.Println("Error updating Groq 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 = "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)
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,
}
`, company, key)
if err != nil {
fmt.Println("Error updating 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 = <str>$0 LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$1,
name := <str>$2 ++ " API Key",
}
)
}`, company, key, company)
if err != nil {
fmt.Println("Error adding 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);
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() {

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
unnecessary features like importing files or images. This focus on simplicity allows us to improve how we use AI
chatbots in other ways.</p>
@ -78,6 +78,7 @@
<li>llama3-8b-8192</li>
<li>llama3-70b-8192</li>
<li>gemma-7b-it</li>
<li>mixtral-8x7b-32768</li>
</ul>
</li>
<li>Google:
@ -88,4 +89,7 @@
</ul>
</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="message-content" id="content-{{ 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'>
<p>
Waiting...

View File

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

View File

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

View File

@ -1,20 +1,31 @@
<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
unnecessary features like importing files or images. This focus on simplicity allows us to improve how we use AI
chatbots in other ways.</p>
<p>The world of Large Language Models (LLMs) is vast and exciting, with each model boasting unique strengths and
weaknesses. However, this variety presents a challenge: using all available LLMs is practically impossible due to
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
all of them can be impractical and very expensive.</p>
<p>This is precisely why JADE was built. With a focus on simplicity, JADE eliminates unnecessary features like file or
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>
<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>
<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
from GPT-4 can be used by Claude Haiku in the next interaction.</li>
<li>The selected response can be used as the basis for the next message across all models.</li>
</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">
Sign in
</a>