Schema update start working

This commit is contained in:
Adrien Bouvais 2024-05-15 23:10:16 +02:00
parent e72ec1af78
commit a30a2f2ccd
22 changed files with 393 additions and 512 deletions

88
Chat.go
View File

@ -90,22 +90,14 @@ func generateChatHTML() string {
// Reset NextMessages when a user message is encountered
templateMessages = []TemplateMessage{}
} else {
modelID, exist := message.ModelID.Get()
if !exist {
modelID = "gpt-3.5-turbo"
}
selected, exist := message.Selected.Get()
if !exist {
selected = false
}
// For bot messages, add them to NextMessages with only the needed fields
templateMessage := TemplateMessage{
Icon: model2Icon(modelID), // Assuming Icon is a field you want to include from Message
Icon: message.LLM.Model.Company.Icon, // Assuming Icon is a field you want to include from Message
Content: markdownToHTML(message.Content),
Hidden: !selected, // Assuming Hidden is a field you want to include from Message
Hidden: !message.Selected, // Assuming Hidden is a field you want to include from Message
Id: message.ID.String(),
Name: model2Name(modelID),
ModelID: modelID,
Name: message.LLM.Model.Name,
ModelID: message.LLM.Model.ModelID,
}
templateMessages = append(templateMessages, templateMessage)
@ -146,9 +138,12 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
var selectedMessage Message
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message {
model_id,
content,
area
llm : {
modelInfo : {
modelID
}
}
}
FILTER
.id = <uuid>$0;
@ -161,10 +156,9 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
return c.SendString(markdownToHTML(selectedMessage.Content))
}
modelID, _ := selectedMessage.ModelID.Get()
out := "<div class='message-header'>"
out += "<p>"
out += model2Name(modelID)
out += selectedMessage.LLM.Model.Name
out += " </p>"
out += "</div>"
out += "<div class='message-body'>"
@ -257,7 +251,7 @@ func GetEditMessageFormHandler(c *fiber.Ctx) error {
id := c.FormValue("id")
idUUID, _ := edgedb.ParseUUID(id)
var message MessageContent
var message Message
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message { content }
FILTER .id = <uuid>$0;
@ -410,63 +404,9 @@ func LoadModelSelectionHandler(c *fiber.Ctx) error {
if !checkIfLogin() {
return c.SendString("")
}
openaiExists, anthropicExists, mistralExists, groqExists := getExistingKeys()
var CompanyInfosAvailable []CompanyInfo
if openaiExists {
var openaiCompanyInfo CompanyInfo
for _, info := range CompanyInfos {
if info.ID == "openai" {
openaiCompanyInfo = info
break
}
}
CompanyInfosAvailable = append(CompanyInfosAvailable, openaiCompanyInfo)
}
if anthropicExists {
var anthropicCompanyInfo CompanyInfo
for _, info := range CompanyInfos {
if info.ID == "anthropic" {
anthropicCompanyInfo = info
break
}
}
CompanyInfosAvailable = append(CompanyInfosAvailable, anthropicCompanyInfo)
}
if mistralExists {
var mistralCompanyInfo CompanyInfo
for _, info := range CompanyInfos {
if info.ID == "mistral" {
mistralCompanyInfo = info
break
}
}
CompanyInfosAvailable = append(CompanyInfosAvailable, mistralCompanyInfo)
}
if groqExists {
var groqCompanyInfo CompanyInfo
for _, info := range CompanyInfos {
if info.ID == "groq" {
groqCompanyInfo = info
break
}
}
CompanyInfosAvailable = append(CompanyInfosAvailable, groqCompanyInfo)
}
CheckedModels := []string{"gpt-3.5-turbo"} // Default model
out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-models.html")).Execute(pongo2.Context{
"CompanyInfos": CompanyInfosAvailable,
"CheckedModels": CheckedModels,
})
if err != nil {
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error rendering template",
})
}
return c.SendString(out)
// openaiExists, anthropicExists, mistralExists, groqExists := getExistingKeys()
// TODO: Add model selection
return c.SendString("")
}
func LoadSettingsHandler(c *fiber.Ctx) error {

View File

@ -12,41 +12,41 @@ import (
"github.com/gofiber/fiber/v2"
)
type ModelInfo struct {
ID string
Name string
Icon string
MaxToken int
InputPrice float32
OutputPrice float32
}
type CompanyInfo struct {
ID string
Name string
Icon string
ModelInfos []ModelInfo
}
type SelectedModel struct {
ID string
Name string
Icon string
}
var CompanyInfos []CompanyInfo
var ModelsInfos []ModelInfo
func GenerateMultipleMessages(c *fiber.Ctx) error {
func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
message := c.FormValue("message", "")
selectedModelIds := []string{}
for ModelInfo := range ModelsInfos {
out := c.FormValue("model-check-" + ModelsInfos[ModelInfo].ID)
if out != "" {
selectedModelIds = append(selectedModelIds, ModelsInfos[ModelInfo].ID)
selectedLLMIds := []string{"1e5a07c4-12fe-11ef-8da6-67d29b408c53"} // TODO Hanle in the UI
var selectedLLMs []LLM
var selectedLLM LLM
for _, id := range selectedLLMIds {
idUUID, _ := edgedb.ParseUUID(id)
err := edgeClient.QuerySingle(context.Background(), `
SELECT LLM {
id,
name,
context,
temperature,
modelInfo : {
modelID,
company : {
icon,
name
}
}
}
FILTER
.id = <uuid>$0;
`, &selectedLLM, idUUID)
if err != nil {
fmt.Println("Error trying to select the unique LLM")
log.Fatal(err)
}
selectedLLMs = append(selectedLLMs, selectedLLM)
}
fmt.Println("Selected LLMs: ", selectedLLMs)
_, position := insertArea()
messageID := insertUserMessage(message)
@ -54,32 +54,27 @@ func GenerateMultipleMessages(c *fiber.Ctx) error {
messageOut, _ := userTmpl.Execute(pongo2.Context{"Content": markdownToHTML(message), "ID": messageID.String()})
out += messageOut
var selectedModels []SelectedModel
for i := range selectedModelIds {
selectedModels = append(selectedModels, SelectedModel{ID: selectedModelIds[i], Name: model2Name(selectedModelIds[i]), Icon: model2Icon(selectedModelIds[i])})
}
messageOut, _ = botTmpl.Execute(pongo2.Context{"IsPlaceholder": true, "selectedModels": selectedModels, "ConversationAreaId": position + 1})
messageOut, _ = botTmpl.Execute(pongo2.Context{"IsPlaceholder": true, "SelectedLLMs": selectedLLMs, "ConversationAreaId": position + 1})
out += messageOut
go HandleGenerateMultipleMessages(selectedModelIds)
go HandleGenerateMultipleMessages(selectedLLMs)
return c.SendString(out)
}
func HandleGenerateMultipleMessages(selectedModelIds []string) {
func HandleGenerateMultipleMessages(selectedLLMs []LLM) {
insertArea()
// Create a wait group to synchronize the goroutines
var wg sync.WaitGroup
// Add the length of selectedModelIds goroutines to the wait group
wg.Add(len(selectedModelIds))
wg.Add(len(selectedLLMs))
// Create a channel to receive the index of the first completed goroutine
firstDone := make(chan int, 1)
for i := range selectedModelIds {
for i := range selectedLLMs {
idx := i
go func() {
defer wg.Done()
@ -89,8 +84,8 @@ func HandleGenerateMultipleMessages(selectedModelIds []string) {
defer cancel() // Ensure the context is cancelled to free resources
// Determine which message function to call based on the model
var addMessageFunc func(modelID string, selected bool) edgedb.UUID
switch model2Icon(selectedModelIds[idx]) {
var addMessageFunc func(selectedLLM LLM, selected bool) edgedb.UUID
switch selectedLLMs[idx].Model.Company.Name {
case "openai":
addMessageFunc = addOpenaiMessage
case "anthropic":
@ -103,46 +98,42 @@ func HandleGenerateMultipleMessages(selectedModelIds []string) {
var messageID edgedb.UUID
if addMessageFunc != nil {
messageID = addMessageFunc(selectedModelIds[idx], idx == 0)
messageID = addMessageFunc(selectedLLMs[idx], idx == 0)
} else {
fmt.Println("Invalid model: ", selectedLLMs[idx].Model.Company.Name)
}
fmt.Println("Message ID: ", messageID)
var message Message
err := edgeClient.QuerySingle(edgeCtx, `
SELECT Message {
model_id,
id,
content,
area,
id
area : {
id,
position
},
llm : {
modelInfo : {
modelID
}
}
}
FILTER .id = <uuid>$0;
`, &message, messageID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in GenerateMultipleMessages")
fmt.Println("Error in edgedb.QuerySingle: in HandleGenerateMultipleMessages 1")
log.Fatal(err)
}
modelID, _ := message.ModelID.Get()
var area Area
err = edgeClient.QuerySingle(edgeCtx, `
SELECT Area {
position
}
FILTER .id = <uuid>$0;
`, &area, message.Area.ID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in GenerateMultipleMessages")
log.Fatal(err)
}
fmt.Println(area)
templateMessage := TemplateMessage{
Icon: model2Icon(modelID),
Icon: message.LLM.Model.Company.Icon,
Content: markdownToHTML(message.Content),
Hidden: false,
Id: message.ID.String(),
Name: model2Name(modelID),
ModelID: modelID,
Name: message.LLM.Model.Name,
ModelID: message.LLM.Model.ModelID,
}
// Check if the context's deadline is exceeded
@ -157,7 +148,7 @@ func HandleGenerateMultipleMessages(selectedModelIds []string) {
// Generate the HTML content
out := "<div class='message-header'>"
out += "<p>"
out += model2Name(templateMessage.ModelID)
out += templateMessage.Name
out += " </p>"
out += "</div>"
out += "<div class='message-body'>"
@ -166,23 +157,26 @@ func HandleGenerateMultipleMessages(selectedModelIds []string) {
out += " </ct>"
out += "</div>"
fmt.Println("Sending event from first")
fmt.Println("swapContent-" + fmt.Sprintf("%d", area.Position))
// Send Content event
sseChanel.SendEvent(
"swapContent-"+fmt.Sprintf("%d", area.Position),
"swapContent-"+fmt.Sprintf("%d", message.Area.Position),
out,
)
fmt.Println(templateMessage)
out, err := modelSelecBtnTmpl.Execute(map[string]interface{}{
"message": templateMessage,
"message": templateMessage,
"ConversationAreaId": message.Area.Position,
})
if err != nil {
fmt.Println("Error in modelSelecBtnTmpl.Execute: in GenerateMultipleMessages")
fmt.Println("Error in modelSelecBtnTmpl.Execute: in HandleGenerateMultipleMessages 3")
log.Fatal(err)
}
fmt.Println("Sending event: swapSelectionBtn-" + selectedLLMs[idx].ID.String())
fmt.Println(out)
// Send Content event
sseChanel.SendEvent(
"swapSelectionBtn-"+templateMessage.ModelID,
@ -191,20 +185,19 @@ func HandleGenerateMultipleMessages(selectedModelIds []string) {
// Send Icon Swap event
sseChanel.SendEvent(
"swapIcon-"+fmt.Sprintf("%d", area.Position),
`<img src="icons/`+model2Icon(templateMessage.ModelID)+`.png" alt="User Image">`,
"swapIcon-"+fmt.Sprintf("%d", message.Area.Position),
`<img src="icons/`+selectedLLMs[idx].Model.Company.Name+`.png" alt="User Image">`,
)
default:
out, err := modelSelecBtnTmpl.Execute(map[string]interface{}{
"message": templateMessage,
"message": templateMessage,
"ConversationAreaId": message.Area.Position,
})
if err != nil {
fmt.Println("Error in modelSelecBtnTmpl.Execute: in GenerateMultipleMessages")
fmt.Println("Error in modelSelecBtnTmpl.Execute: in HandleGenerateMultipleMessages 4")
log.Fatal(err)
}
fmt.Println(("Sending event"))
// Send Content event
sseChanel.SendEvent(
"swapSelectionBtn-"+templateMessage.ModelID,

View File

@ -35,65 +35,19 @@ type AnthropicUsage struct {
OutputTokens int32 `json:"output_tokens"`
}
func init() {
var ModelInfosList = []ModelInfo{}
modelInfo := ModelInfo{
ID: "claude-3-haiku-20240307",
Name: "Haiku",
Icon: "anthropic",
MaxToken: 8192,
InputPrice: 0.50 / 1000000,
OutputPrice: 1.50 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "claude-3-sonnet-20240229",
Name: "Sonnet",
Icon: "anthropic",
MaxToken: 8192,
InputPrice: 0.50 / 1000000,
OutputPrice: 1.50 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "claude-3-opus-20240229",
Name: "Opus",
Icon: "anthropic",
MaxToken: 8192,
InputPrice: 0.50 / 1000000,
OutputPrice: 1.50 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
companyInfo := CompanyInfo{
ID: "anthropic",
Name: "Anthropic",
ModelInfos: ModelInfosList,
}
CompanyInfos = append(CompanyInfos, companyInfo)
}
func addAnthropicMessage(modelID string, selected bool) edgedb.UUID {
func addAnthropicMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestAnthropic(modelID, Messages, 2048, 0.7) // TODO CHange parameters
chatCompletion, err := RequestAnthropic(llm.Model.ModelID, Messages, 2048, float64(llm.Temperature))
if err != nil {
// Print error
fmt.Println("Error:", err)
} else if len(chatCompletion.Content) == 0 {
fmt.Println(chatCompletion)
fmt.Println("No response from Anthropic")
id := insertBotMessage("No response from Anthropic", selected, modelID)
id := insertBotMessage("No response from Anthropic", selected, llm.ID)
return id
} else {
Content := chatCompletion.Content[0].Text
id := insertBotMessage(Content, selected, modelID)
id := insertBotMessage(chatCompletion.Content[0].Text, selected, llm.ID)
return id
}
return edgedb.UUID{}
@ -160,7 +114,7 @@ func RequestAnthropic(model string, messages []Message, maxTokens int, temperatu
filtered_keys := (
select Key {
key
} filter .company = <str>$0
} filter .company.name = <str>$0
)
select filtered_keys.key limit 1
`, &apiKey, "anthropic")
@ -210,11 +164,14 @@ func RequestAnthropic(model string, messages []Message, maxTokens int, temperatu
}
var usedModelInfo ModelInfo
for mi := range ModelsInfos {
if ModelsInfos[mi].ID == model {
usedModelInfo = ModelsInfos[mi]
edgeClient.QuerySingle(edgeCtx, `
Select ModelInfo {
inputPrice,
outputPrice
}
}
FILTER .name = <str>$0
`, &usedModelInfo, model)
var inputCost float32 = float32(chatCompletionResponse.Usage.InputTokens) * usedModelInfo.InputPrice
var outputCost float32 = float32(chatCompletionResponse.Usage.OutputTokens) * usedModelInfo.OutputPrice
addUsage(inputCost, outputCost, chatCompletionResponse.Usage.InputTokens, chatCompletionResponse.Usage.OutputTokens, model)

View File

@ -37,65 +37,20 @@ type GroqChoice struct {
Index int `json:"index"`
}
func init() {
var ModelInfosList = []ModelInfo{}
modelInfo := ModelInfo{
ID: "llama3-8b-8192",
Name: "Llama 3 8B",
Icon: "groq",
MaxToken: 8192,
InputPrice: 0.00 / 1000000,
OutputPrice: 0.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "llama3-70b-8192",
Name: "Llama 3 70B",
Icon: "groq",
MaxToken: 8192,
InputPrice: 0.00 / 1000000,
OutputPrice: 0.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "gemma-7b-it",
Name: "Gemma 7B",
Icon: "groq",
MaxToken: 8192,
InputPrice: 0.00 / 1000000,
OutputPrice: 0.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
companyInfo := CompanyInfo{
ID: "groq",
Name: "Groq",
Icon: "icons/groq.png",
ModelInfos: ModelInfosList,
}
CompanyInfos = append(CompanyInfos, companyInfo)
}
func addGroqMessage(modelID string, selected bool) edgedb.UUID {
func addGroqMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestGroq(modelID, Messages, 0.7)
chatCompletion, err := RequestGroq(llm.Model.ModelID, Messages, float64(llm.Temperature))
if err != nil {
fmt.Println("Error:", err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println(chatCompletion)
fmt.Println("No response from Groq")
id := insertBotMessage("No response from Groq", selected, modelID)
id := insertBotMessage("No response from Groq", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Message.Content
id := insertBotMessage(Content, selected, modelID)
id := insertBotMessage(Content, selected, llm.ID)
return id
}
return edgedb.UUID{}
@ -162,7 +117,7 @@ func RequestGroq(model string, messages []Message, temperature float64) (GroqCha
filtered_keys := (
select Key {
key
} filter .company = <str>$0
} filter .company.name = <str>$0
)
select filtered_keys.key limit 1
`, &apiKey, "groq")
@ -214,11 +169,14 @@ func RequestGroq(model string, messages []Message, temperature float64) (GroqCha
}
var usedModelInfo ModelInfo
for mi := range ModelsInfos {
if ModelsInfos[mi].ID == model {
usedModelInfo = ModelsInfos[mi]
edgeClient.QuerySingle(edgeCtx, `
Select ModelInfo {
inputPrice,
outputPrice
}
}
Filter ModelInfo.model = <str>$0
`, &usedModelInfo, model)
var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
addUsage(inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)

View File

@ -36,98 +36,20 @@ type MistralChoice struct {
Index int `json:"index"`
}
func init() {
var ModelInfosList = []ModelInfo{}
modelInfo := ModelInfo{
ID: "open-mistral-7b",
Name: "Mistral 7b",
Icon: "mistral",
MaxToken: 32000,
InputPrice: 0.25 / 1000000,
OutputPrice: 1.25 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "open-mixtral-8x7b",
Name: "Mistral 8x7b",
Icon: "mistral",
MaxToken: 32000,
InputPrice: 0.7 / 1000000,
OutputPrice: 0.7 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "open-mixtral-8x22b",
Name: "Mistral 8x22b",
Icon: "mistral",
MaxToken: 64000,
InputPrice: 2.0 / 1000000,
OutputPrice: 6.0 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "mistral-small-latest",
Name: "Mistral Small",
Icon: "mistral",
MaxToken: 32000,
InputPrice: 1.0 / 1000000,
OutputPrice: 3.0 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "mistral-medium-latest",
Name: "Mistral Medium",
Icon: "mistral",
MaxToken: 32000,
InputPrice: 2.7 / 1000000,
OutputPrice: 8.1 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "mistral-large-latest",
Name: "Mistral Large",
Icon: "mistral",
MaxToken: 32000,
InputPrice: 4.0 / 1000000,
OutputPrice: 12.0 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
companyInfo := CompanyInfo{
ID: "mistral",
Name: "Mistral",
Icon: "icons/mistral.png",
ModelInfos: ModelInfosList,
}
CompanyInfos = append(CompanyInfos, companyInfo)
}
func addMistralMessage(modelID string, selected bool) edgedb.UUID {
func addMistralMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestMistral(modelID, Messages, 0.7)
chatCompletion, err := RequestMistral(llm.Model.ModelID, Messages, float64(llm.Temperature))
if err != nil {
fmt.Println("Error:", err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println(chatCompletion)
fmt.Println("No response from Mistral")
id := insertBotMessage("No response from Mistral", selected, modelID)
id := insertBotMessage("No response from Mistral", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Message.Content
id := insertBotMessage(Content, selected, modelID)
id := insertBotMessage(Content, selected, llm.ID)
return id
}
return edgedb.UUID{}
@ -201,7 +123,7 @@ func RequestMistral(model string, messages []Message, temperature float64) (Mist
filtered_keys := (
select Key {
key
} filter .company = <str>$0
} filter .company.name = <str>$0
)
select filtered_keys.key limit 1
`, &apiKey, "mistral")
@ -250,11 +172,14 @@ func RequestMistral(model string, messages []Message, temperature float64) (Mist
}
var usedModelInfo ModelInfo
for mi := range ModelsInfos {
if ModelsInfos[mi].ID == model {
usedModelInfo = ModelsInfos[mi]
edgeClient.QuerySingle(edgeCtx, `
Select ModelInfo {
inputPrice,
outputPrice
}
}
Filter ModelInfo.model = <str>$0
`, &usedModelInfo, model)
var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
addUsage(inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)

View File

@ -37,76 +37,21 @@ type OpenaiChoice struct {
Index int `json:"index"`
}
func init() {
var ModelInfosList = []ModelInfo{}
modelInfo := ModelInfo{
ID: "gpt-3.5-turbo",
Name: "GPT-3.5 Turbo",
Icon: "openai",
MaxToken: 16385,
InputPrice: 0.50 / 1000000,
OutputPrice: 1.50 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "gpt-4",
Name: "GPT-4",
Icon: "openai",
MaxToken: 8192,
InputPrice: 30.00 / 1000000,
OutputPrice: 60.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "gpt-4-turbo",
Name: "GPT-4 Turbo",
Icon: "openai",
MaxToken: 128000,
InputPrice: 10.00 / 1000000,
OutputPrice: 30.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
modelInfo = ModelInfo{
ID: "gpt-4o",
Name: "GPT-4 Omni",
Icon: "openai",
MaxToken: 128000,
InputPrice: 5.00 / 1000000,
OutputPrice: 15.00 / 1000000,
}
ModelInfosList = append(ModelInfosList, modelInfo)
ModelsInfos = append(ModelsInfos, modelInfo)
companyInfo := CompanyInfo{
ID: "openai",
Name: "OpenAI",
Icon: "icons/openai.png",
ModelInfos: ModelInfosList,
}
CompanyInfos = append(CompanyInfos, companyInfo)
}
func addOpenaiMessage(modelID string, selected bool) edgedb.UUID {
func addOpenaiMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestOpenai(modelID, Messages, 0.7)
chatCompletion, err := RequestOpenai(llm.Model.ModelID, Messages, float64(llm.Temperature))
fmt.Println(chatCompletion)
if err != nil {
fmt.Println("Error:", err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println(chatCompletion)
fmt.Println("No response from OpenAI")
id := insertBotMessage("No response from OpenAI", selected, modelID)
id := insertBotMessage("No response from OpenAI", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Message.Content
id := insertBotMessage(Content, selected, modelID)
id := insertBotMessage(Content, selected, llm.ID)
return id
}
return edgedb.UUID{}
@ -173,7 +118,7 @@ func RequestOpenai(model string, messages []Message, temperature float64) (Opena
filtered_keys := (
select Key {
key
} filter .company = <str>$0
} filter .company.name = <str>$0
)
select filtered_keys.key limit 1
`, &apiKey, "openai")
@ -221,11 +166,14 @@ func RequestOpenai(model string, messages []Message, temperature float64) (Opena
}
var usedModelInfo ModelInfo
for mi := range ModelsInfos {
if ModelsInfos[mi].ID == model {
usedModelInfo = ModelsInfos[mi]
edgeClient.QuerySingle(edgeCtx, `
Select ModelInfo {
inputPrice,
outputPrice
}
}
Filter ModelInfo.model = <str>$0
`, &usedModelInfo, model)
var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
addUsage(inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)

View File

@ -20,7 +20,7 @@ type User struct {
type Key struct {
ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"`
Company string `edgedb:"company"`
Company CompanyInfo `edgedb:"company"`
Key string `edgedb:"key"`
Date time.Time `edgedb:"date"`
}
@ -45,28 +45,48 @@ type Area struct {
}
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"`
Date time.Time `edgedb:"date"`
Area Area `edgedb:"area"`
Conv Conversation `edgedb:"conversation"`
}
type MessageContent struct {
Content string `edgedb:"content"`
ID edgedb.UUID `edgedb:"id"`
Content string `edgedb:"content"`
Role string `edgedb:"role"`
Selected bool `edgedb:"selected"`
Date time.Time `edgedb:"date"`
Area Area `edgedb:"area"`
Conv Conversation `edgedb:"conversation"`
LLM LLM `edgedb:"llm"`
}
type Usage struct {
ID edgedb.UUID `edgedb:"id"`
ModelID string `edgedb:"model_id"`
Date time.Time `edgedb:"date"`
InputCost edgedb.OptionalFloat32 `edgedb:"input_cost"`
OutputCost edgedb.OptionalFloat32 `edgedb:"output_cost"`
InputToken edgedb.OptionalInt32 `edgedb:"input_token"`
OutputToken edgedb.OptionalInt32 `edgedb:"output_token"`
ID edgedb.UUID `edgedb:"id"`
ModelID string `edgedb:"model_id"`
Date time.Time `edgedb:"date"`
InputCost float32 `edgedb:"input_cost"`
OutputCost float32 `edgedb:"output_cost"`
InputToken int32 `edgedb:"input_token"`
OutputToken int32 `edgedb:"output_token"`
}
type LLM struct {
ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"`
Context string `edgedb:"context"`
Temperature float32 `edgedb:"temperature"`
Model ModelInfo `edgedb:"modelInfo"`
}
type ModelInfo struct {
ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"`
MaxToken int32 `edgedb:"max_token"`
InputPrice float32 `edgedb:"inputPrice"`
OutputPrice float32 `edgedb:"outputPrice"`
ModelID string `edgedb:"modelID"`
Company CompanyInfo `edgedb:"company"`
}
type CompanyInfo struct {
ID edgedb.UUID `edgedb:"id"`
Name string `edgedb:"name"`
Icon string `edgedb:"icon"`
}
func init() {
@ -209,7 +229,10 @@ func insertUserMessage(content string) edgedb.UUID {
FILTER .name = 'Default' AND .user = global currentUser
LIMIT 1
),
selected := true
selected := true,
llm := (
SELECT LLM FILTER .id = <uuid>"a32c43ec-12fc-11ef-9dc9-b38e0de8bff0"
)
}
`, &inserted, "user", content, lastAreaID)
if err != nil {
@ -219,13 +242,12 @@ func insertUserMessage(content string) edgedb.UUID {
return inserted.id
}
func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
func insertBotMessage(content string, selected bool, llmUUID edgedb.UUID) edgedb.UUID {
lastAreaID := getLastArea()
var inserted struct{ id edgedb.UUID }
err := edgeClient.QuerySingle(edgeCtx, `
INSERT Message {
role := <str>$0,
model_id := <str>$1,
content := <str>$2,
selected := <bool>$3,
conversation := (
@ -237,9 +259,14 @@ func insertBotMessage(content string, selected bool, model string) edgedb.UUID {
SELECT Area
FILTER .id = <uuid>$4
LIMIT 1
),
llm := (
SELECT LLM
FILTER .id = <uuid>$1
LIMIT 1
)
}
`, &inserted, "bot", model, content, selected, lastAreaID)
`, &inserted, "bot", llmUUID, content, selected, lastAreaID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in insertBotMessage")
log.Fatal(err)
@ -258,13 +285,21 @@ func getAllMessages() []Message {
err := edgeClient.Query(edgeCtx, `
SELECT Message {
id,
model_id,
selected,
role,
content,
date
} FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser
ORDER BY .date ASC
date,
llm : {
modelInfo : {
modelID,
company : {
icon
}
}
}
}
FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser
ORDER BY .date ASC
`, &messages)
if err != nil {
fmt.Println("Error in edgedb.Query: in getAllMessages")
@ -285,11 +320,18 @@ func getAllSelectedMessages() []Message {
err := edgeClient.Query(edgeCtx, `
SELECT Message {
id,
model_id,
selected,
role,
content,
date
date,
llm : {
modelInfo : {
modelID,
company : {
icon
}
}
}
} FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser AND .selected = true
ORDER BY .date ASC
`, &messages)

View File

@ -15,7 +15,7 @@ module default {
type Key {
required name: str;
required company: str;
company: Company;
required key: str;
required date: datetime {
default := datetime_current();
@ -47,10 +47,10 @@ module default {
}
type Message {
model_id: str;
selected: bool;
required selected: bool;
required role: str;
required content: str;
required llm: LLM;
required area: Area {
on target delete delete source;
};
@ -65,12 +65,34 @@ module default {
type Usage {
required model_id: str;
user: User;
input_cost: float32;
output_cost: float32;
input_token: int32;
output_token: int32;
required input_cost: float32;
required output_cost: float32;
required input_token: int32;
required output_token: int32;
required date: datetime {
default := datetime_current();
}
}
type LLM {
required name: str;
required context: str;
required temperature: float32;
required modelInfo: ModelInfo;
required user: User;
}
type Company {
required name: str;
required icon: str;
}
type ModelInfo {
required modelID: str;
required name: str;
required maxToken: int32;
required inputPrice: float32;
required outputPrice: float32;
required company: Company;
}
}

View File

@ -0,0 +1,11 @@
CREATE MIGRATION m1zwsg5vmpsby2nsvnefn22kiebgm6ue7txxuuymltfqtbfc6vxl4q
ONTO m1uadgxoeuekkwaessyysetg27ov3wcdfmach7cq5k7vvqn6x6zmrq
{
CREATE TYPE default::LLM {
CREATE REQUIRED PROPERTY company: std::str;
CREATE REQUIRED PROPERTY context: std::str;
CREATE REQUIRED PROPERTY model_id: std::str;
CREATE REQUIRED PROPERTY name: std::str;
CREATE REQUIRED PROPERTY temperature: std::float32;
};
};

View File

@ -0,0 +1,28 @@
CREATE MIGRATION m1rt6anebdhp27aye2so5m72wpeieic5fncldmkmm43tif65p7keea
ONTO m1zwsg5vmpsby2nsvnefn22kiebgm6ue7txxuuymltfqtbfc6vxl4q
{
CREATE TYPE default::Company {
CREATE REQUIRED PROPERTY icon: std::str;
CREATE REQUIRED PROPERTY name: std::str;
};
ALTER TYPE default::LLM {
DROP PROPERTY company;
};
ALTER TYPE default::LLM {
CREATE LINK company: default::Company;
};
CREATE TYPE default::ModelInfo {
CREATE REQUIRED PROPERTY icon: std::str;
CREATE REQUIRED PROPERTY inputPrice: std::float32;
CREATE REQUIRED PROPERTY maxToken: std::int32;
CREATE REQUIRED PROPERTY modelID: std::str;
CREATE REQUIRED PROPERTY name: std::str;
CREATE REQUIRED PROPERTY outputPrice: std::float32;
};
ALTER TYPE default::LLM {
CREATE LINK modelInfo: default::ModelInfo;
};
ALTER TYPE default::LLM {
DROP PROPERTY model_id;
};
};

View File

@ -0,0 +1,30 @@
CREATE MIGRATION m1kp3u333wnhx7bqlt45ntwulvvu4lotivmz7wxyu652r57qbefucq
ONTO m1rt6anebdhp27aye2so5m72wpeieic5fncldmkmm43tif65p7keea
{
ALTER TYPE default::Message {
CREATE LINK llm: default::LLM;
};
ALTER TYPE default::Message {
DROP PROPERTY model_id;
ALTER PROPERTY selected {
SET REQUIRED USING (<std::bool>{true});
};
};
ALTER TYPE default::Usage {
ALTER LINK user {
SET REQUIRED USING (<default::User>{});
};
ALTER PROPERTY input_cost {
SET REQUIRED USING (<std::float32>{});
};
ALTER PROPERTY input_token {
SET REQUIRED USING (<std::int32>{});
};
ALTER PROPERTY output_cost {
SET REQUIRED USING (<std::float32>{});
};
ALTER PROPERTY output_token {
SET REQUIRED USING (<std::int32>{});
};
};
};

View File

@ -0,0 +1,9 @@
CREATE MIGRATION m13w67akitbmbbbeunycsu3hataxr2v35zi7ysyakztdwqtzhs6zbq
ONTO m1kp3u333wnhx7bqlt45ntwulvvu4lotivmz7wxyu652r57qbefucq
{
ALTER TYPE default::Usage {
ALTER LINK user {
RESET OPTIONALITY;
};
};
};

View File

@ -0,0 +1,10 @@
CREATE MIGRATION m17sngo3y2bgw5jpg7s7pvszst5ly2cgsqz3mikuqjexveow2xhjpa
ONTO m13w67akitbmbbbeunycsu3hataxr2v35zi7ysyakztdwqtzhs6zbq
{
ALTER TYPE default::Key {
DROP PROPERTY company;
};
ALTER TYPE default::Key {
CREATE LINK company: default::Company;
};
};

View File

@ -0,0 +1,10 @@
CREATE MIGRATION m1s7qnwe6t2flqdzjqsojznuqdpitur4bpj53na7rzrnwk4evrahfa
ONTO m17sngo3y2bgw5jpg7s7pvszst5ly2cgsqz3mikuqjexveow2xhjpa
{
ALTER TYPE default::ModelInfo {
CREATE LINK company: default::Company;
};
ALTER TYPE default::ModelInfo {
DROP PROPERTY icon;
};
};

View File

@ -0,0 +1,15 @@
CREATE MIGRATION m1kjqpsc4o27o33qufgkiieu3h5kfpc4rxf2czwx7bar27tfr4lkvq
ONTO m1s7qnwe6t2flqdzjqsojznuqdpitur4bpj53na7rzrnwk4evrahfa
{
ALTER TYPE default::LLM {
DROP LINK company;
ALTER LINK modelInfo {
SET REQUIRED USING (<default::ModelInfo>{});
};
};
ALTER TYPE default::ModelInfo {
ALTER LINK company {
SET REQUIRED USING (<default::Company>{});
};
};
};

View File

@ -0,0 +1,14 @@
CREATE MIGRATION m1cokoqujl36nbvnldmofdwszcmsp6ulre23r5gx52d65aqnr64cca
ONTO m1kjqpsc4o27o33qufgkiieu3h5kfpc4rxf2czwx7bar27tfr4lkvq
{
ALTER TYPE default::LLM {
CREATE REQUIRED LINK user: default::User {
SET REQUIRED USING (<default::User>{});
};
};
ALTER TYPE default::Message {
ALTER LINK llm {
SET REQUIRED USING (<default::LLM>{});
};
};
};

26
main.go
View File

@ -44,7 +44,7 @@ func main() {
// Chat routes
app.Post("/deleteMessage", DeleteMessageHandler)
app.Get("/generateMultipleMessages", GenerateMultipleMessages)
app.Get("/generateMultipleMessages", GenerateMultipleMessagesHandler)
app.Get("/messageContent", GetMessageContentHandler)
app.Get("/editMessageForm", GetEditMessageFormHandler)
app.Post("/redoMessage", RedoMessageHandler)
@ -95,7 +95,7 @@ func addKeys(c *fiber.Ctx) error {
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "openai" AND .key = <str>$0
filter .company.name = "openai" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
@ -116,7 +116,7 @@ func addKeys(c *fiber.Ctx) error {
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "openai"
filter .company.name = "openai"
);
`, &Exists)
if err != nil {
@ -127,7 +127,7 @@ func addKeys(c *fiber.Ctx) error {
if Exists {
fmt.Println("Company key already exists")
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company = <str>$0 AND .key = <str>$1
UPDATE Key filter .company.name = <str>$0 AND .key = <str>$1
SET {
key := <str>$1,
}
@ -161,7 +161,7 @@ func addKeys(c *fiber.Ctx) error {
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "anthropic" AND .key = <str>$0
filter .company.name = "anthropic" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
@ -182,7 +182,7 @@ func addKeys(c *fiber.Ctx) error {
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "anthropic"
filter .company.name = "anthropic"
);
`, &Exists)
if err != nil {
@ -192,7 +192,7 @@ func addKeys(c *fiber.Ctx) error {
if Exists {
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company = "anthropic" AND .key = <str>$0
UPDATE Key filter .company.name = "anthropic" AND .key = <str>$0
SET {
key := <str>$0,
}
@ -226,7 +226,7 @@ func addKeys(c *fiber.Ctx) error {
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "mistral" AND .key = <str>$0
filter .company.name = "mistral" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
@ -247,7 +247,7 @@ func addKeys(c *fiber.Ctx) error {
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "mistral"
filter .company.name = "mistral"
);
`, &Exists)
if err != nil {
@ -257,7 +257,7 @@ func addKeys(c *fiber.Ctx) error {
if Exists {
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company = "mistral" AND .key = <str>$0
UPDATE Key filter .company.name = "mistral" AND .key = <str>$0
SET {
key := <str>$0,
}
@ -291,7 +291,7 @@ func addKeys(c *fiber.Ctx) error {
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "groq" AND .key = <str>$0
filter .company.name = "groq" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
@ -312,7 +312,7 @@ func addKeys(c *fiber.Ctx) error {
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "groq"
filter .company.name = "groq"
);
`, &Exists)
if err != nil {
@ -322,7 +322,7 @@ func addKeys(c *fiber.Ctx) error {
if Exists {
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company = "groq" AND .key = <str>$0
UPDATE Key filter .company.name = "groq" AND .key = <str>$0
SET {
key := <str>$0,
}

View File

@ -38,24 +38,6 @@ func addCopyButtonsToCode(htmlContent string) string {
return updatedHTML
}
func model2Icon(model string) string {
for i := range ModelsInfos {
if ModelsInfos[i].ID == model {
return ModelsInfos[i].Icon
}
}
return "bouvai2"
}
func model2Name(model string) string {
for i := range ModelsInfos {
if ModelsInfos[i].ID == model {
return ModelsInfos[i].Name
}
}
return "You"
}
func getExistingKeys() (bool, bool, bool, bool) {
if edgeClient == nil {
return false, false, false, false
@ -69,62 +51,51 @@ func getExistingKeys() (bool, bool, bool, bool) {
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "openai"
filter .company.name = "openai"
);
`, &openaiExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for openai: ", err)
openaiExists = false
//fatal error
panic(err)
}
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "anthropic"
filter .company.name = "anthropic"
);
`, &anthropicExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for anthropic: ", err)
anthropicExists = false
panic(err)
}
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "mistral"
filter .company.name = "mistral"
);
`, &mistralExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for mistral: ", err)
mistralExists = false
panic(err)
}
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company = "groq"
filter .company.name = "groq"
);
`, &groqExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for mistral: ", err)
groqExists = false
panic(err)
}
return openaiExists, anthropicExists, mistralExists, groqExists
}
func removeDuplicate(s []string) []string {
m := make(map[string]bool)
var result []string
for _, str := range s {
if !m[str] {
m[str] = true
result = append(result, str)
}
}
return result
}
func ChangeRoleBot2Assistant(messages []Message) []Message {
openaiMessages := make([]Message, len(messages))
for i, msg := range messages {

View File

@ -10,7 +10,7 @@
<hx hx-get="/loadSettings" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadKeys" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadUsageKPI" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<!--hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx-->
<button {% if not IsLogin or not HaveKey %}style="display: none;" {%endif%} class="button is-small"
onclick="clearTextArea()" id="clear-button" hx-post="/clearChat" hx-swap="outerHTML"
hx-target="#chat-container">
@ -25,8 +25,7 @@
</button>
<button disabled type="submit" class="send-button button is-primary is-small"
hx-get="/generateMultipleMessages" hx-swap="beforeend settle:200ms" hx-target="#chat-messages"
id="chat-input-send-btn" class="chat-input" hx-include="[name='message'], [name^='model-check-']"
onclick="clearTextArea()">
id="chat-input-send-btn" class="chat-input" hx-include="[name='message']" onclick="clearTextArea()">
<span class="icon">
<i class="fa-solid fa-chevron-right"></i>
</span>

View File

@ -79,11 +79,11 @@
</div>
<div class="is-flex is-justify-content mt-2">
{% for selectedModel in selectedModels %}
{% for selectedLLM in SelectedLLMs %}
<button disable class="button is-small is-primary message-button is-outlined mr-1"
sse-swap="swapSelectionBtn-{{ selectedModel.ID }}" hx-swap="outerHTML">
sse-swap="swapSelectionBtn-{{ selectedLLM.ID }}" hx-swap="outerHTML">
<span class="icon is-small">
<!--img src="icons/{{ selectedModel.Icon }}.png" alt="{{ selectedModel.Name }}"
<!--img src="icons/{{ selectedLLM.Company }}.png" alt="{{ selectedLLM.Name }}"
style="max-height: 100%; max-width: 100%;"-->
<img src="/puff.svg" />
</span>

View File

@ -29,8 +29,7 @@
</span>
</button>
<button id="redo-button-{{ ID }}" class="button is-small is-primary message-button is-outlined mr-1"
hx-post="/redoMessage?id={{ ID }}" hx-swap="innerHTML settle:200ms" hx-target="next .message-bot"
hx-include="[name^='model-check-']">
hx-post="/redoMessage?id={{ ID }}" hx-swap="innerHTML settle:200ms" hx-target="next .message-bot">
<span class="icon">
<i class="fa-solid fa-arrows-rotate"></i>
</span>

View File

@ -1,7 +1,7 @@
<button class="button is-small is-primary message-button is-outlined mr-1" hx-get="/messageContent?id={{ message.Id }}"
hx-target="#content-{{ ConversationAreaId }}" onclick="updateIcon('{{ Icon }}', '{{ ConversationAreaId }}')"
hx-target="#content-{{ ConversationAreaId }}" onclick="updateIcon('{{ message.Icon }}', '{{ ConversationAreaId }}')"
title="{{ message.Name }}">
<span class="icon is-small">
<img src="icons/{{ Icon }}.png" alt="{{ message.Name }}" style="max-height: 100%; max-width: 100%;">
<img src="icons/{{ message.Icon }}.png" alt="{{ message.Name }}" style="max-height: 100%; max-width: 100%;">
</span>
</button>