Added together ai and DeepSeek
This commit is contained in:
parent
751e72dcd7
commit
6c3e4d4b8a
@ -1,3 +1,5 @@
|
|||||||
data
|
data
|
||||||
wasm
|
wasm
|
||||||
dbschema
|
dbschema
|
||||||
|
.git
|
||||||
|
TODO.md
|
||||||
|
21
Chat.go
21
Chat.go
@ -206,8 +206,6 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateMessageContentHTML(authCookie string, messageId string, onlyContent string, withDiv bool) string {
|
func GenerateMessageContentHTML(authCookie string, messageId string, onlyContent string, withDiv bool) string {
|
||||||
fmt.Println("Generating message for:", authCookie)
|
|
||||||
|
|
||||||
messageUUID, _ := edgedb.ParseUUID(messageId)
|
messageUUID, _ := edgedb.ParseUUID(messageId)
|
||||||
|
|
||||||
var selectedMessage Message
|
var selectedMessage Message
|
||||||
@ -372,7 +370,7 @@ func generateEnterKeyChatHTML() string {
|
|||||||
<p>To get a key and learn more about the different LLM providers and their offerings, check out their websites:</p>
|
<p>To get a key and learn more about the different LLM providers and their offerings, check out their websites:</p>
|
||||||
<a
|
<a
|
||||||
class="button is-small is-primary is-outlined mt-1"
|
class="button is-small is-primary is-outlined mt-1"
|
||||||
href="https://openai.com/index/openai-api/"
|
href="https://platform.openai.com/account/api-keys"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Get OpenAI API key
|
Get OpenAI API key
|
||||||
@ -422,15 +420,22 @@ func generateEnterKeyChatHTML() string {
|
|||||||
>
|
>
|
||||||
Get Fireworks API key
|
Get Fireworks API key
|
||||||
</a>
|
</a>
|
||||||
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
|
href="https://www.together.ai/"
|
||||||
|
target="_blank">Get Together AI API key</a>
|
||||||
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
|
href="https://platform.deepseek.com/"
|
||||||
|
target="_blank">Get DeepSeek API key</a>
|
||||||
|
<br/>
|
||||||
<p>Note: Key are encrypted and saved on a secure database link only with the app.</p>`
|
<p>Note: Key are encrypted and saved on a secure database link only with the app.</p>`
|
||||||
|
|
||||||
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
|
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
|
||||||
|
|
||||||
NextMessages := []TemplateMessage{}
|
NextMessages := []TemplateMessage{}
|
||||||
nextMsg := TemplateMessage{
|
nextMsg := TemplateMessage{
|
||||||
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
|
Icon: "icons/bouvai2.png",
|
||||||
Content: welcomeMessage,
|
Content: welcomeMessage,
|
||||||
Hidden: false, // Assuming Hidden is a field you want to include from Message
|
Hidden: false,
|
||||||
Id: "0",
|
Id: "0",
|
||||||
Name: "JADE",
|
Name: "JADE",
|
||||||
}
|
}
|
||||||
@ -929,7 +934,7 @@ 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, perplexityExists, fireworksExists, nimExists := getExistingKeys(c)
|
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists := getExistingKeysNew(c)
|
||||||
isPremium, isBasic := IsCurrentUserSubscribed(c)
|
isPremium, isBasic := IsCurrentUserSubscribed(c)
|
||||||
|
|
||||||
out, err := settingPopoverTmpl.Execute(pongo2.Context{
|
out, err := settingPopoverTmpl.Execute(pongo2.Context{
|
||||||
@ -943,7 +948,9 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
|
|||||||
"NimExists": nimExists,
|
"NimExists": nimExists,
|
||||||
"PerplexityExists": perplexityExists,
|
"PerplexityExists": perplexityExists,
|
||||||
"FireworksExists": fireworksExists,
|
"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,
|
"isPremium": isPremium,
|
||||||
"isBasic": isBasic,
|
"isBasic": isBasic,
|
||||||
"StripeSubLink": stripeSubLink,
|
"StripeSubLink": stripeSubLink,
|
||||||
|
164
MyUtils.go
164
MyUtils.go
@ -75,123 +75,73 @@ func addCodeHeader(htmlContent string, languages []string) string {
|
|||||||
return updatedHTML
|
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 {
|
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 (
|
var userInfo User
|
||||||
openaiExists bool
|
|
||||||
anthropicExists bool
|
|
||||||
mistralExists bool
|
|
||||||
groqExists bool
|
|
||||||
gooseaiExists bool
|
|
||||||
nimExists bool
|
|
||||||
googleExists bool
|
|
||||||
perplexityExists bool
|
|
||||||
fireworksExists 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, `
|
||||||
select exists (
|
SELECT global currentUser {
|
||||||
select global currentUser.setting.keys
|
setting: {
|
||||||
filter .company.name = "openai"
|
keys: {
|
||||||
);
|
name,
|
||||||
`, &openaiExists)
|
key,
|
||||||
|
company: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LIMIT 1;
|
||||||
|
`, &userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error checking if OpenAI key exists")
|
fmt.Println("Error getting user keys", err)
|
||||||
panic(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, `
|
openaiExists := false
|
||||||
select exists (
|
anthropicExists := false
|
||||||
select global currentUser.setting.keys
|
mistralExists := false
|
||||||
filter .company.name = "anthropic"
|
groqExists := false
|
||||||
);
|
gooseaiExists := false
|
||||||
`, &anthropicExists)
|
nimExists := false
|
||||||
if err != nil {
|
googleExists := false
|
||||||
fmt.Println("Error checking if Anthropic key exists")
|
perplexityExists := false
|
||||||
panic(err)
|
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, `
|
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Message2RequestMessage(messages []Message, context string) []RequestMessage {
|
func Message2RequestMessage(messages []Message, context string) []RequestMessage {
|
||||||
|
@ -158,6 +158,10 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
|
|||||||
addMessageFunc = addFireworkMessage
|
addMessageFunc = addFireworkMessage
|
||||||
case "nim":
|
case "nim":
|
||||||
addMessageFunc = addNimMessage
|
addMessageFunc = addNimMessage
|
||||||
|
case "together":
|
||||||
|
addMessageFunc = addTogetherMessage
|
||||||
|
case "deepseek":
|
||||||
|
addMessageFunc = addDeepseekMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
var messageID edgedb.UUID
|
var messageID edgedb.UUID
|
||||||
|
203
RequestDeepseek.go
Normal file
203
RequestDeepseek.go
Normal file
@ -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 = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
|
||||||
|
)
|
||||||
|
select filtered_keys.key limit 1
|
||||||
|
`, &apiKey, "deepseek")
|
||||||
|
if err != nil {
|
||||||
|
return DeepseekChatCompletionResponse{}, fmt.Errorf("error getting DeepSeek API key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "https://api.deepseek.com/chat/completions"
|
||||||
|
|
||||||
|
requestBody := DeepseekChatCompletionRequest{
|
||||||
|
Model: model,
|
||||||
|
Messages: Message2RequestMessage(messages, context),
|
||||||
|
MaxTokens: maxTokens,
|
||||||
|
Temperature: temperature,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBody, err := json.Marshal(requestBody)
|
||||||
|
if err != nil {
|
||||||
|
return DeepseekChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
|
||||||
|
if err != nil {
|
||||||
|
return DeepseekChatCompletionResponse{}, 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 DeepseekChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// TODO: Add a message to the user and do it for all 400 things
|
||||||
|
if resp.Status == "402 Payment Required" {
|
||||||
|
return DeepseekChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return DeepseekChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatCompletionResponse DeepseekChatCompletionResponse
|
||||||
|
err = json.Unmarshal(body, &chatCompletionResponse)
|
||||||
|
if err != nil {
|
||||||
|
return DeepseekChatCompletionResponse{}, 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 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
|
||||||
|
}
|
188
RequestTogetherai.go
Normal file
188
RequestTogetherai.go
Normal file
@ -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 = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
|
||||||
|
)
|
||||||
|
select filtered_keys.key limit 1
|
||||||
|
`, &apiKey, "together")
|
||||||
|
if err != nil {
|
||||||
|
return TogetherChatCompletionResponse{}, fmt.Errorf("error getting Together AI API key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "https://api.together.xyz/v1/completions"
|
||||||
|
|
||||||
|
requestBody := TogetherChatCompletionRequest{
|
||||||
|
Model: model,
|
||||||
|
Messages: Message2RequestMessage(messages, context),
|
||||||
|
MaxTokens: maxTokens,
|
||||||
|
Temperature: temperature,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBody, err := json.Marshal(requestBody)
|
||||||
|
if err != nil {
|
||||||
|
return TogetherChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
|
||||||
|
if err != nil {
|
||||||
|
return TogetherChatCompletionResponse{}, 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 TogetherChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return TogetherChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatCompletionResponse TogetherChatCompletionResponse
|
||||||
|
err = json.Unmarshal(body, &chatCompletionResponse)
|
||||||
|
if err != nil {
|
||||||
|
return TogetherChatCompletionResponse{}, 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 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
|
||||||
|
}
|
18
TODO.md
18
TODO.md
@ -1,13 +1,27 @@
|
|||||||
# Bugs
|
# 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] 2 selected messages
|
||||||
[X] On first response, code block width are too long
|
[X] On first response, code block width are too long
|
||||||
[X] Change Terms of service to say that I use one cookie
|
[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] Change the lastSelectedLLMs, 2 users can't use the same time...
|
||||||
[X] CTRL + Enter not working
|
[X] CTRL + Enter not working
|
||||||
|
[ ] Add all Together AI models
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
[X] SSE to WebSocket
|
||||||
[X] Add max tokens
|
[X] Add max tokens
|
||||||
[ ] Errors messages to know what happend
|
[ ] 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
|
[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
|
||||||
|
@ -28,7 +28,7 @@ module default {
|
|||||||
|
|
||||||
type Key {
|
type Key {
|
||||||
required name: str;
|
required name: str;
|
||||||
company: Company;
|
required company: Company;
|
||||||
required key: str;
|
required key: str;
|
||||||
required date: datetime {
|
required date: datetime {
|
||||||
default := datetime_current();
|
default := datetime_current();
|
||||||
|
9
dbschema/migrations/00054-m1crb36.edgeql
Normal file
9
dbschema/migrations/00054-m1crb36.edgeql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE MIGRATION m1crb36qoksqdmqtzncwo6b5gqb66jlgdfkziozqenrkgwowoj5vka
|
||||||
|
ONTO m1eqooh5xjbysafocihnromqeyrleo57w6txsr36tu73rkju2eyfcq
|
||||||
|
{
|
||||||
|
ALTER TYPE default::Key {
|
||||||
|
ALTER LINK company {
|
||||||
|
SET REQUIRED USING (<default::Company>{});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
4
main.go
4
main.go
@ -138,6 +138,8 @@ func addKeys(c *fiber.Ctx) error {
|
|||||||
"nim": c.FormValue("nim_key"),
|
"nim": c.FormValue("nim_key"),
|
||||||
"perplexity": c.FormValue("perplexity_key"),
|
"perplexity": c.FormValue("perplexity_key"),
|
||||||
"fireworks": c.FormValue("fireworks_key"),
|
"fireworks": c.FormValue("fireworks_key"),
|
||||||
|
"together": c.FormValue("together_key"),
|
||||||
|
"deepseek": c.FormValue("deepseek_key"),
|
||||||
}
|
}
|
||||||
|
|
||||||
testFunctions := map[string]func(string) bool{
|
testFunctions := map[string]func(string) bool{
|
||||||
@ -150,6 +152,8 @@ func addKeys(c *fiber.Ctx) error {
|
|||||||
"nim": TestNimKey,
|
"nim": TestNimKey,
|
||||||
"perplexity": TestPerplexityKey,
|
"perplexity": TestPerplexityKey,
|
||||||
"fireworks": TestFireworkKey,
|
"fireworks": TestFireworkKey,
|
||||||
|
"together": TestTogetherKey,
|
||||||
|
"deepseek": TestDeepseekKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
for company, key := range keys {
|
for company, key := range keys {
|
||||||
|
BIN
static/icons/deepseek.png
Normal file
BIN
static/icons/deepseek.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
static/icons/together.png
Normal file
BIN
static/icons/together.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
@ -4,7 +4,7 @@
|
|||||||
the settings menu. Once enter you get access to all models from this provider.
|
the settings menu. Once enter you get access to all models from this provider.
|
||||||
</p>
|
</p>
|
||||||
<a class="button is-small is-primary is-outlined mt-1"
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
href="https://openai.com/index/openai-api/"
|
href="https://platform.openai.com/account/api-keys"
|
||||||
target="_blank">Get OpenAI API key</a>
|
target="_blank">Get OpenAI API key</a>
|
||||||
<a class="button is-small is-primary is-outlined mt-1"
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
href="https://console.anthropic.com/"
|
href="https://console.anthropic.com/"
|
||||||
@ -27,6 +27,12 @@
|
|||||||
<a class="button is-small is-primary is-outlined mt-1"
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
href="https://fireworks.ai/login"
|
href="https://fireworks.ai/login"
|
||||||
target="_blank">Get Fireworks API key</a>
|
target="_blank">Get Fireworks API key</a>
|
||||||
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
|
href="https://www.together.ai/"
|
||||||
|
target="_blank">Get Together AI API key</a>
|
||||||
|
<a class="button is-small is-primary is-outlined mt-1"
|
||||||
|
href="https://platform.deepseek.com/"
|
||||||
|
target="_blank">Get DeepSeek API key</a>
|
||||||
|
|
||||||
<h2>Conversations</h2>
|
<h2>Conversations</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -115,6 +115,30 @@
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field has-addons is-hidden" id="together-field">
|
||||||
|
<p class="control has-icons-left is-expanded">
|
||||||
|
<input class="input is-small {% if TogetherExists %}is-success{% endif %}"
|
||||||
|
type="text"
|
||||||
|
placeholder="Together AI API key"
|
||||||
|
name="together_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="deepseek-field">
|
||||||
|
<p class="control has-icons-left is-expanded">
|
||||||
|
<input class="input is-small {% if DeepseekExists %}is-success{% endif %}"
|
||||||
|
type="text"
|
||||||
|
placeholder="DeepSeek API key"
|
||||||
|
name="deepseek_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"
|
<div class="field has-addons is-hidden"
|
||||||
id="goose-field"
|
id="goose-field"
|
||||||
title="GooseAI chat API will be available soon">
|
title="GooseAI chat API will be available soon">
|
||||||
|
@ -199,20 +199,25 @@
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<strong>Fireworks</strong> - Fireworks AI offer 1$ of free credits when
|
<strong>Fireworks</strong> - Fireworks AI offer 1$ of free credits when
|
||||||
creating an account. Firework AI have a lot of open source models. I may
|
creating an account. Firework AI have a lot of open source models.
|
||||||
add fine tuned models in the future.
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>Nvidia NIM</strong> - Nvidia NIM offer 1000 free API credit when creating an account with a personal email and 5000 with a business email.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>Together AI</strong> - Together AI offer 5$ credits when creating an account and have a lot of open source models available.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>DeepSeek</strong> - DeepSeek do not offer free credits.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>Goose AI</strong> - Chat API will be available soon.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<strong>Custom endpoint</strong> - You can also use custom endpoints as long as the key is valid and it use
|
<strong>Custom endpoint</strong> - You can also use custom endpoints as long as the key is valid and it use
|
||||||
the openai api.
|
the openai api.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<strong>Nvidia NIM</strong> - Available soon.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<strong>Goose AI</strong> - Chat API will be available soon.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user