"
+
+ NextMessages := []NextMessage{}
+ nextMsg := NextMessage{
+ Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
+ Content: "
" + markdownToHTML(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 {
panic(err)
}
diff --git a/RequestAnthropic.go b/RequestAnthropic.go
index 99c20d1..6b2e6df 100644
--- a/RequestAnthropic.go
+++ b/RequestAnthropic.go
@@ -8,6 +8,8 @@ import (
"net/http"
"github.com/edgedb/edgedb-go"
+ "github.com/flosch/pongo2"
+ "github.com/gofiber/fiber/v2"
)
type AnthropicChatCompletionRequest struct {
@@ -124,8 +126,69 @@ func EdgeMessages2AnthropicMessages(messages []Message) []AnthropicMessage {
return AnthropicMessages
}
+func TestAnthropicKey(apiKey string) bool {
+ url := "https://api.anthropic.com/v1/messages"
+
+ AnthropicMessages := []AnthropicMessage{
+ {
+ Role: "user",
+ Content: "Hello",
+ },
+ }
+
+ requestBody := AnthropicChatCompletionRequest{
+ Model: "claude-3-haiku-20240307",
+ Messages: AnthropicMessages,
+ MaxTokens: 10,
+ 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("anthropic-version", "2023-06-01")
+ req.Header.Set("x-api-key", 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 AnthropicChatCompletionResponse
+ err = json.Unmarshal(body, &chatCompletionResponse)
+ return err == nil
+}
+
func RequestAnthropic(model string, messages []Message, maxTokens int, temperature float64) (AnthropicChatCompletionResponse, error) {
- apiKey := "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" // TODO Use env variable
+ var apiKey string
+ err := edgeClient.QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ key
+ } filter .company =
$0
+ )
+ select filtered_keys.key limit 1
+ `, &apiKey, "anthropic")
+ if err != nil {
+ return AnthropicChatCompletionResponse{}, fmt.Errorf("error getting OpenAI API key: %w", err)
+ }
+
url := "https://api.anthropic.com/v1/messages"
AnthropicMessages := EdgeMessages2AnthropicMessages(messages)
@@ -181,3 +244,74 @@ func RequestAnthropic(model string, messages []Message, maxTokens int, temperatu
return chatCompletionResponse, nil
}
+
+func addAnthropicKey(c *fiber.Ctx) error {
+ key := c.FormValue("key")
+
+ // Check if the key already exists
+ err := edgeClient.QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ key
+ } filter .key = $0 and .company = "anthropic"
+ )
+ select filtered_keys.key limit 1
+ `, &key, key)
+ if err == nil {
+ return c.SendString("")
+ }
+
+ if !TestAnthropicKey(key) {
+ fmt.Println("Invalid Anthropic API Key")
+
+ NextMessages := []NextMessage{}
+ nextMsg := NextMessage{
+ Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
+ Content: "
" + markdownToHTML("Invalid Anthropic API Key"),
+ 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})
+ if err != nil {
+ panic(err)
+ }
+ return c.SendString(botOut)
+ }
+
+ err = edgeClient.Execute(edgeCtx, `
+ UPDATE global currentUser.setting
+ SET {
+ keys += (
+ INSERT Key {
+ company := $0,
+ key := $1,
+ name := $2,
+ }
+ )
+ }`, "anthropic", key, "Anthropic API Key")
+ if err != nil {
+ fmt.Println("Error in edgedb.QuerySingle: in addOpenaiKey")
+ fmt.Println(err)
+ }
+
+ NextMessages := []NextMessage{}
+ nextMsg := NextMessage{
+ Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
+ Content: "
" + markdownToHTML("Key added successfully!"),
+ 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})
+ if err != nil {
+ panic(err)
+ }
+
+ return c.SendString(botOut)
+}
diff --git a/RequestOpenai.go b/RequestOpenai.go
index 197a255..eabf6a3 100644
--- a/RequestOpenai.go
+++ b/RequestOpenai.go
@@ -8,6 +8,8 @@ import (
"net/http"
"github.com/edgedb/edgedb-go"
+ "github.com/flosch/pongo2"
+ "github.com/gofiber/fiber/v2"
)
type OpenaiChatCompletionRequest struct {
@@ -115,8 +117,76 @@ func EdgeMessages2OpenaiMessages(messages []Message) []OpenaiMessage {
return openaiMessages
}
+func TestOpenaiKey(apiKey string) bool {
+ url := "https://api.openai.com/v1/chat/completions"
+
+ // Convert messages to OpenAI format
+ openaiMessages := []OpenaiMessage{
+ {
+ Role: "user",
+ Content: "Hello",
+ },
+ }
+
+ requestBody := OpenaiChatCompletionRequest{
+ Model: "gpt-3.5-turbo",
+ Messages: openaiMessages,
+ 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 OpenaiChatCompletionResponse
+ err = json.Unmarshal(body, &chatCompletionResponse)
+ if err != nil {
+ return false
+ }
+ if chatCompletionResponse.Choices == nil {
+ return false
+ }
+ return false
+}
+
func RequestOpenai(model string, messages []Message, temperature float64) (OpenaiChatCompletionResponse, error) {
- apiKey := "sk-proj-f7StCvXCtcmiOOayiVmgT3BlbkFJlVtAcOo3JcrnGq1cPa5o" // TODO Use env variable
+ var apiKey string
+ err := edgeClient.QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ key
+ } filter .company = $0
+ )
+ select filtered_keys.key limit 1
+ `, &apiKey, "openai")
+ if err != nil {
+ return OpenaiChatCompletionResponse{}, fmt.Errorf("error getting OpenAI API key: %w", err)
+ }
+
+ fmt.Println("OpenAI API key: ", apiKey)
+
url := "https://api.openai.com/v1/chat/completions"
// Convert messages to OpenAI format
@@ -171,3 +241,103 @@ func RequestOpenai(model string, messages []Message, temperature float64) (Opena
return chatCompletionResponse, nil
}
+
+func addOpenaiKey(c *fiber.Ctx) error {
+ key := c.FormValue("key")
+
+ // Check if the key already exists
+ var keyUUID edgedb.UUID
+ err := edgeClient.QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ id
+ } filter .key = $0 and .company = "openai"
+ )
+ select filtered_keys.key limit 1
+ `, &keyUUID, key)
+ if err == nil {
+ fmt.Println("Error in edgedb.Query: in addOpenaiKey")
+ return c.SendString("")
+ }
+
+ if !TestOpenaiKey(key) {
+ fmt.Println("Invalid OpenAI API Key")
+ NextMessages := []NextMessage{}
+ nextMsg := NextMessage{
+ Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
+ Content: "
" + markdownToHTML("Invalid OpenAI API Key"),
+ 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})
+ if err != nil {
+ panic(err)
+ }
+ return c.SendString(botOut)
+ }
+
+ // Check if the company key already exists
+ err = edgeClient.QuerySingle(edgeCtx, `
+ with
+ filtered_keys := (
+ select Key {
+ id
+ } filter .company = "openai"
+ )
+ select filtered_keys.key limit 1
+ `, &keyUUID, key)
+ if err != nil {
+ fmt.Println("Company key already exists")
+ err = edgeClient.Execute(edgeCtx, `
+ UPDATE Key filter .company = $0 AND .key = $1
+ SET {
+ key := $1,
+ }
+ `, "openai", key)
+ if err != nil {
+ fmt.Println("Error in edgedb.QuerySingle: in addOpenaiKey")
+ fmt.Println(err)
+ }
+
+ return c.SendString("")
+ }
+
+ fmt.Println("OpenAI API key: ", key)
+
+ err = edgeClient.Execute(edgeCtx, `
+ UPDATE global currentUser.setting
+ SET {
+ keys += (
+ INSERT Key {
+ company := $0,
+ key := $1,
+ name := $2,
+ }
+ )
+ }`, "openai", key, "OpenAI API Key")
+ if err != nil {
+ fmt.Println("Error in edgedb.QuerySingle: in addOpenaiKey")
+ fmt.Println(err)
+ }
+
+ NextMessages := []NextMessage{}
+ nextMsg := NextMessage{
+ Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
+ Content: "
Key added successfully!",
+ 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})
+ if err != nil {
+ panic(err)
+ }
+
+ return c.SendString(botOut)
+}
diff --git a/database.go b/database.go
index e3a31d6..a79ea3a 100644
--- a/database.go
+++ b/database.go
@@ -13,19 +13,16 @@ var edgeCtx context.Context
var edgeClient *edgedb.Client
type User struct {
- ID edgedb.UUID `edgedb:"id"`
- Email string `edgedb:"email"`
- Name string `edgedb:"name"`
- Setting Setting `edgedb:"setting"`
- Conversations []Conversation `edgedb:"conversations"`
- Usages []Usage `edgedb:"usages"`
+ ID edgedb.UUID `edgedb:"id"`
+ Setting Setting `edgedb:"setting"`
}
type Key struct {
- ID edgedb.UUID `edgedb:"id"`
- Name string `edgedb:"name"`
- Key string `edgedb:"key"`
- Date time.Time `edgedb:"date"`
+ ID edgedb.UUID `edgedb:"id"`
+ Name string `edgedb:"name"`
+ Company string `edgedb:"company"`
+ Key string `edgedb:"key"`
+ Date time.Time `edgedb:"date"`
}
type Setting struct {
@@ -35,24 +32,27 @@ type Setting struct {
}
type Conversation struct {
- ID edgedb.UUID `edgedb:"id"`
- Name string `edgedb:"name"`
- Areas []Area `edgedb:"areas"`
+ ID edgedb.UUID `edgedb:"id"`
+ Name string `edgedb:"name"`
+ Date time.Time `edgedb:"date"`
+ User User `edgedb:"user"`
}
type Area struct {
- ID edgedb.UUID `edgedb:"id"`
- Position int `edgedb:"position"`
- Messages []Message `edgedb:"messages"`
+ ID edgedb.UUID `edgedb:"id"`
+ Position int `edgedb:"position"`
+ Conv Conversation `edgedb:"conversation"`
}
type Message struct {
ID edgedb.UUID `edgedb:"id"`
+ Content string `edgedb:"content"`
+ Role string `edgedb:"role"`
ModelID edgedb.OptionalStr `edgedb:"model_id"`
Selected edgedb.OptionalBool `edgedb:"selected"`
- Role string `edgedb:"role"`
- Content string `edgedb:"content"`
Date time.Time `edgedb:"date"`
+ Area Area `edgedb:"area"`
+ Conv Conversation `edgedb:"conversation"`
}
type Usage struct {
@@ -109,10 +109,34 @@ func checkIfLogin() bool {
return err == nil
}
-func insertArea() edgedb.UUID {
- // Insert a new area.
+func insertNewConversation() edgedb.UUID {
var inserted struct{ id edgedb.UUID }
err := edgeClient.QuerySingle(edgeCtx, `
+ INSERT Conversation {
+ name := 'Default',
+ user := global currentUser
+ }
+ `, &inserted)
+ if err != nil {
+ fmt.Println("Error in edgedb.QuerySingle: in insertNewConversation")
+ log.Fatal(err)
+ }
+ return inserted.id
+}
+
+func insertArea() edgedb.UUID {
+ // If the Default conversation doesn't exist, create it.
+ err := edgeClient.QuerySingle(edgeCtx, `
+ SELECT Conversation
+ FILTER .name = 'Default' AND .user = global currentUser
+ LIMIT 1
+ `, nil)
+ if err != nil {
+ insertNewConversation()
+ }
+ // Insert a new area.
+ var inserted struct{ id edgedb.UUID }
+ err = edgeClient.QuerySingle(edgeCtx, `
WITH
positionVar := count((SELECT Area FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser)) + 1
INSERT Area {
@@ -168,10 +192,10 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
content := $2,
selected := $3,
conversation := (
- SELECT Conversation
- FILTER .name = 'Default' AND .user = global currentUser
+ SELECT Area
+ FILTER .id = $4
LIMIT 1
- ),
+ ).conversation,
area := (
SELECT Area
FILTER .id = $4
@@ -212,3 +236,18 @@ func getAllMessages() []Message {
return messages
}
+
+func getCurrentUserKeys() []Key {
+ var result []Key
+ err := edgeClient.Query(edgeCtx, "SELECT global currentUser.setting.keys", &result)
+ if err != nil {
+ fmt.Println("Error in edgedb.Query: in getCurrentUserKeys")
+ fmt.Println(err)
+ }
+ return result
+}
+
+func checkIfHaveKey() bool {
+ keys := getCurrentUserKeys()
+ return keys != nil && len(keys) > 0
+}
diff --git a/main.go b/main.go
index 4663818..d64227e 100644
--- a/main.go
+++ b/main.go
@@ -40,9 +40,14 @@ func main() {
app.Get("/generateMultipleMessages", GenerateMultipleMessages)
app.Get("/messageContent", GetMessageContentHandler)
+ // Settings routes
+ app.Post("/addOpenaiKey", addOpenaiKey)
+ app.Post("/addAnthropicKey", addAnthropicKey)
+
// Popovers
app.Get("/loadModelSelection", LoadModelSelectionHandler)
app.Get("/loadUsageKPI", LoadUsageKPIHandler)
+ app.Get("/loadSettings", LoadSettingsHandler)
// Authentication
app.Get("/signin", handleUiSignIn)
diff --git a/views/chat.html b/views/chat.html
index c1a69b5..a65c365 100644
--- a/views/chat.html
+++ b/views/chat.html
@@ -1,14 +1,18 @@