Added together ai and DeepSeek

This commit is contained in:
Adrien Bouvais 2024-08-07 10:49:18 +02:00
parent 751e72dcd7
commit 6c3e4d4b8a
15 changed files with 543 additions and 127 deletions

View File

@ -1,3 +1,5 @@
data
wasm
dbschema
dbschema
.git
TODO.md

21
Chat.go
View File

@ -206,8 +206,6 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
}
func GenerateMessageContentHTML(authCookie string, messageId string, onlyContent string, withDiv bool) string {
fmt.Println("Generating message for:", authCookie)
messageUUID, _ := edgedb.ParseUUID(messageId)
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>
<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
@ -422,15 +420,22 @@ func generateEnterKeyChatHTML() string {
>
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>
<br/>
<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'>"
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Icon: "icons/bouvai2.png",
Content: welcomeMessage,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Hidden: false,
Id: "0",
Name: "JADE",
}
@ -929,7 +934,7 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
stripeSubLink := "https://billing.stripe.com/p/login/6oE6sc0PTfvq1Hi288?prefilled_email=" + user.Email
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists := getExistingKeys(c)
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists := getExistingKeysNew(c)
isPremium, isBasic := IsCurrentUserSubscribed(c)
out, err := settingPopoverTmpl.Execute(pongo2.Context{
@ -943,7 +948,9 @@ func LoadSettingsHandler(c *fiber.Ctx) error {
"NimExists": nimExists,
"PerplexityExists": perplexityExists,
"FireworksExists": fireworksExists,
"AnyExists": fireworksExists || openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists || nimExists,
"TogetherExists": togetherExists,
"DeepseekExists": deepseekExists,
"AnyExists": fireworksExists || openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists || perplexityExists || nimExists || togetherExists || deepseekExists,
"isPremium": isPremium,
"isBasic": isBasic,
"StripeSubLink": stripeSubLink,

View File

@ -75,123 +75,73 @@ func addCodeHeader(htmlContent string, languages []string) string {
return updatedHTML
}
func getExistingKeys(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool, bool, bool, bool) {
func getExistingKeysNew(c *fiber.Ctx) (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool) {
// TODO: Optimize to use only one query
if edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}) == nil {
return false, false, false, false, false, false, false, false, false
return false, false, false, false, false, false, false, false, false, false, false
}
var (
openaiExists bool
anthropicExists bool
mistralExists bool
groqExists bool
gooseaiExists bool
nimExists bool
googleExists bool
perplexityExists bool
fireworksExists bool
)
var userInfo User
err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "openai"
);
`, &openaiExists)
SELECT global currentUser {
setting: {
keys: {
name,
key,
company: {
name
}
}
}
}
LIMIT 1;
`, &userInfo)
if err != nil {
fmt.Println("Error checking if OpenAI key exists")
panic(err)
fmt.Println("Error getting user keys", err)
return false, false, false, false, false, false, false, false, false, false, false
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "anthropic"
);
`, &anthropicExists)
if err != nil {
fmt.Println("Error checking if Anthropic key exists")
panic(err)
openaiExists := false
anthropicExists := false
mistralExists := false
groqExists := false
gooseaiExists := false
nimExists := false
googleExists := false
perplexityExists := false
fireworksExists := false
togetherExists := false
deepseekExists := false
for _, key := range userInfo.Setting.Keys {
switch key.Company.Name {
case "openai":
openaiExists = true
case "anthropic":
anthropicExists = true
case "mistral":
mistralExists = true
case "groq":
groqExists = true
case "gooseai":
gooseaiExists = true
case "nim":
nimExists = true
case "google":
googleExists = true
case "perplexity":
perplexityExists = true
case "fireworks":
fireworksExists = true
case "together":
togetherExists = true
case "deepseek":
deepseekExists = true
}
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "mistral"
);
`, &mistralExists)
if err != nil {
fmt.Println("Error checking if Mistral key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "groq"
);
`, &groqExists)
if err != nil {
fmt.Println("Error checking if Groq key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "gooseai"
);
`, &gooseaiExists)
if err != nil {
fmt.Println("Error checking if GooseAI key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "google"
);
`, &googleExists)
if err != nil {
fmt.Println("Error checking if Google key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "perplexity"
);
`, &perplexityExists)
if err != nil {
fmt.Println("Error checking if Perplexity key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "fireworks"
);
`, &fireworksExists)
if err != nil {
fmt.Println("Error checking if Fireworks key exists")
panic(err)
}
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "nim"
);
`, &nimExists)
if err != nil {
fmt.Println("Error checking if Fireworks key exists")
panic(err)
}
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists
}
func Message2RequestMessage(messages []Message, context string) []RequestMessage {

View File

@ -158,6 +158,10 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
addMessageFunc = addFireworkMessage
case "nim":
addMessageFunc = addNimMessage
case "together":
addMessageFunc = addTogetherMessage
case "deepseek":
addMessageFunc = addDeepseekMessage
}
var messageID edgedb.UUID

203
RequestDeepseek.go Normal file
View 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
View 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
View File

@ -1,13 +1,27 @@
# Bugs
[ ] The SSE event that sometime fails after some times. Reconnect when sending a new message.
[ ] Sometime I can redo or edit but the button is not available
[X] The SSE event that sometime fails after some times. Reconnect when sending a new message.
[X] 2 selected messages
[X] On first response, code block width are too long
[X] Change Terms of service to say that I use one cookie
[X] Change the lastSelectedLLMs, 2 users can't use the same time...
[X] CTRL + Enter not working
[ ] Add all Together AI models
# Features
[X] SSE to WebSocket
[X] Add max tokens
[ ] Errors messages to know what happend
[ ] Add Deepseek API
[ ] Check the response status and add a message accordingly (Like 402 Payment required)
[X] Add Deepseek API
[X] Add TogetherAI API
[X] Add Nvidia NIM
[ ] Host the database on Fly.io
[ ] Add login with email and password
[ ] Add login with other provider
[ ] Better temperature settings. (Anthropic from 0-1, OpenAI from 0-2, DeepSeek from -2-2, ect)
# Other
[ ] Change the terms of service and enter keys page to an HTML
[ ] Split Chat.go into smaller files
[ ] Create a Request package

View File

@ -28,7 +28,7 @@ module default {
type Key {
required name: str;
company: Company;
required company: Company;
required key: str;
required date: datetime {
default := datetime_current();

View File

@ -0,0 +1,9 @@
CREATE MIGRATION m1crb36qoksqdmqtzncwo6b5gqb66jlgdfkziozqenrkgwowoj5vka
ONTO m1eqooh5xjbysafocihnromqeyrleo57w6txsr36tu73rkju2eyfcq
{
ALTER TYPE default::Key {
ALTER LINK company {
SET REQUIRED USING (<default::Company>{});
};
};
};

View File

@ -138,6 +138,8 @@ func addKeys(c *fiber.Ctx) error {
"nim": c.FormValue("nim_key"),
"perplexity": c.FormValue("perplexity_key"),
"fireworks": c.FormValue("fireworks_key"),
"together": c.FormValue("together_key"),
"deepseek": c.FormValue("deepseek_key"),
}
testFunctions := map[string]func(string) bool{
@ -150,6 +152,8 @@ func addKeys(c *fiber.Ctx) error {
"nim": TestNimKey,
"perplexity": TestPerplexityKey,
"fireworks": TestFireworkKey,
"together": TestTogetherKey,
"deepseek": TestDeepseekKey,
}
for company, key := range keys {

BIN
static/icons/deepseek.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
static/icons/together.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -4,7 +4,7 @@
the settings menu. Once enter you get access to all models from this provider.
</p>
<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>
<a class="button is-small is-primary is-outlined mt-1"
href="https://console.anthropic.com/"
@ -27,6 +27,12 @@
<a class="button is-small is-primary is-outlined mt-1"
href="https://fireworks.ai/login"
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>
<p>

View File

@ -115,6 +115,30 @@
</span>
</p>
</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"
id="goose-field"
title="GooseAI chat API will be available soon">

View File

@ -199,20 +199,25 @@
<br />
<br />
<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
add fine tuned models in the future.
creating an account. Firework AI have a lot of open source models.
<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 />
<strong>Custom endpoint</strong> - You can also use custom endpoints as long as the key is valid and it use
the openai api.
<br />
<br />
<strong>Nvidia NIM</strong> - Available soon.
<br />
<br />
<strong>Goose AI</strong> - Chat API will be available soon.
<br />
<br />
</div>
</li>
</ul>