Barely working per request response

This commit is contained in:
Adrien Bouvais 2024-05-15 18:55:20 +02:00
parent bb8f53a018
commit 1b73a97ff1
15 changed files with 295 additions and 282 deletions

30
Chat.go
View File

@ -15,11 +15,6 @@ import (
func ChatPageHandler(c *fiber.Ctx) error {
authCookie := c.Cookies("jade-edgedb-auth-token", "")
go func() {
fmt.Println("Sending test event")
sseChanel.SendEvent("test", "Hello from server")
}()
if authCookie != "" && !checkIfLogin() {
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie})
}
@ -141,19 +136,16 @@ func generateChatHTML() string {
func GetMessageContentHandler(c *fiber.Ctx) error {
messageId := c.FormValue("id")
onlyContent := c.FormValue("onlyContent")
onlyContent := c.FormValue("onlyContent") // To init the text area of the edit message form
messageUUID, err := edgedb.ParseUUID(messageId)
if err != nil {
fmt.Println("Error in uuid.FromString: in DeleteMessageHandler")
fmt.Println(err)
}
messageUUID, _ := edgedb.ParseUUID(messageId)
var selectedMessage Message
err = edgeClient.QuerySingle(context.Background(), `
err := edgeClient.QuerySingle(context.Background(), `
SELECT Message {
model_id,
content
content,
area
}
FILTER
.id = <uuid>$0;
@ -162,12 +154,11 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
log.Fatal(err)
}
modelID, _ := selectedMessage.ModelID.Get()
if onlyContent == "true" {
return c.SendString(markdownToHTML(selectedMessage.Content))
}
modelID, _ := selectedMessage.ModelID.Get()
out := "<div class='message-header'>"
out += "<p>"
out += model2Name(modelID)
@ -303,15 +294,6 @@ func RedoMessageHandler(c *fiber.Ctx) error {
})
}
selectedModelIds := []string{}
for ModelInfo := range ModelsInfos {
out := c.FormValue("model-check-" + ModelsInfos[ModelInfo].ID)
if out != "" {
selectedModelIds = append(selectedModelIds, ModelsInfos[ModelInfo].ID)
}
}
lastSelectedModelIds = removeDuplicate(selectedModelIds)
return c.SendString(messageOut)
}

View File

@ -28,62 +28,69 @@ type CompanyInfo struct {
ModelInfos []ModelInfo
}
type SelectedModel struct {
ID string
Name string
Icon string
}
var CompanyInfos []CompanyInfo
var ModelsInfos []ModelInfo
type MultipleModelsCompletionRequest struct {
ModelIds []string
Messages []Message
Message string
}
type BotContentMessage struct {
Content string
Hidden bool
}
var lastSelectedModelIds []string
func addUsage(inputCost float32, outputCost float32, inputToken int32, outputToken int32, modelID string) {
// Create a new usage
err := edgeClient.Execute(edgeCtx, `
INSERT Usage {
input_cost := <float32>$0,
output_cost := <float32>$1,
input_token := <int32>$2,
output_token := <int32>$3,
model_id := <str>$4,
user := global currentUser
}
`, inputCost, outputCost, inputToken, outputToken, modelID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addUsage")
log.Fatal(err)
}
}
func GenerateMultipleMessages(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)
}
}
_, position := insertArea()
messageID := insertUserMessage(message)
out := ""
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})
out += messageOut
go HandleGenerateMultipleMessages(selectedModelIds)
return c.SendString(out)
}
func HandleGenerateMultipleMessages(selectedModelIds []string) {
insertArea()
// Create a wait group to synchronize the goroutines
var wg sync.WaitGroup
// Add the length of lastSelectedModelIds goroutines to the wait group
wg.Add(len(lastSelectedModelIds))
// Add the length of selectedModelIds goroutines to the wait group
wg.Add(len(selectedModelIds))
for i := range lastSelectedModelIds {
// Create a channel to receive the index of the first completed goroutine
firstDone := make(chan int, 1)
for i := range selectedModelIds {
idx := i
go func() {
defer wg.Done()
// Create a context with a 1-minute timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel() // Ensure the context is cancelled to free resources
// Use a channel to signal the completion of addxxxMessage
done := make(chan struct{}, 1)
// Determine which message function to call based on the model
var addMessageFunc func(modelID string, selected bool) edgedb.UUID
switch model2Icon(lastSelectedModelIds[idx]) {
switch model2Icon(selectedModelIds[idx]) {
case "openai":
addMessageFunc = addOpenaiMessage
case "anthropic":
@ -94,62 +101,110 @@ func GenerateMultipleMessages(c *fiber.Ctx) error {
addMessageFunc = addGroqMessage
}
// Call the selected addMessageFunc in a goroutine
go func() {
defer wg.Done()
if addMessageFunc != nil {
addMessageFunc(lastSelectedModelIds[idx], idx == 0)
}
done <- struct{}{}
}()
var messageID edgedb.UUID
if addMessageFunc != nil {
messageID = addMessageFunc(selectedModelIds[idx], idx == 0)
}
// Use select to wait on multiple channel operations
var message Message
err := edgeClient.QuerySingle(edgeCtx, `
SELECT Message {
model_id,
content,
area
}
FILTER .id = <uuid>$0;
`, &message, messageID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in GenerateMultipleMessages")
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)
// Check if the context's deadline is exceeded
select {
case <-ctx.Done(): // Context's deadline is exceeded
// Insert a bot message indicating a timeout
insertBotMessage(lastSelectedModelIds[idx]+" too long to answer", idx == 0, lastSelectedModelIds[idx])
case <-done: // addMessageFunc completed within the deadline
// No action needed, the function completed successfully
case <-ctx.Done():
// The context's deadline was exceeded
fmt.Printf("Goroutine %d timed out\n", idx)
default:
// Send the index of the completed goroutine to the firstDone channel
select {
case firstDone <- idx:
// Generate the HTML content
out := "<div class='message-header'>"
out += "<p>"
out += model2Name(modelID)
out += " </p>"
out += "</div>"
out += "<div class='message-body'>"
out += " <ct class='content'>"
out += markdownToHTML(message.Content)
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),
out,
)
out, err := modelSelecBtnTmpl.Execute(map[string]interface{}{
"message": message,
})
if err != nil {
fmt.Println("Error in modelSelecBtnTmpl.Execute: in GenerateMultipleMessages")
log.Fatal(err)
}
// Send Content event
sseChanel.SendEvent(
"swapSelectionBtn-"+modelID,
out,
)
// Send Icon Swap event
sseChanel.SendEvent(
"swapIcon-"+fmt.Sprintf("%d", area.Position),
`<img src="icons/`+model2Icon(modelID)+`.png" alt="User Image">`,
)
default:
out, err := modelSelecBtnTmpl.Execute(map[string]interface{}{
"message": message,
})
if err != nil {
fmt.Println("Error in modelSelecBtnTmpl.Execute: in GenerateMultipleMessages")
log.Fatal(err)
}
fmt.Println(("Sending event"))
// Send Content event
sseChanel.SendEvent(
"swapSelectionBtn-"+modelID,
out,
)
}
}
}()
}
// Wait for all goroutines to finish
wg.Wait()
fmt.Println("Done!")
return c.SendString(generateChatHTML())
}
func RequestMultipleMessagesHandler(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)
}
}
lastSelectedModelIds = selectedModelIds
out := RequestMultipleMessages(message, selectedModelIds)
return c.SendString(out)
}
func RequestMultipleMessages(message string, selectedModelIds []string) string {
// Add an Area with the user message inside
insertArea()
messageID := insertUserMessage(message)
out := ""
messageOut, _ := userTmpl.Execute(pongo2.Context{"Content": markdownToHTML(message), "ID": messageID.String()})
out += messageOut
messageOut, _ = botTmpl.Execute(pongo2.Context{"IsPlaceholder": true})
out += messageOut
return out
}

View File

@ -11,15 +11,10 @@ import (
)
type AnthropicChatCompletionRequest struct {
Model string `json:"model"`
Messages []AnthropicMessage `json:"messages"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
}
type AnthropicMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
}
type AnthropicChatCompletionResponse struct {
@ -104,30 +99,10 @@ func addAnthropicMessage(modelID string, selected bool) edgedb.UUID {
return edgedb.UUID{}
}
func EdgeMessages2AnthropicMessages(messages []Message) []AnthropicMessage {
AnthropicMessages := make([]AnthropicMessage, len(messages))
for i, msg := range messages {
var role string
switch msg.Role {
case "user":
role = "user"
case "bot":
role = "assistant"
default:
role = "system"
}
AnthropicMessages[i] = AnthropicMessage{
Role: role,
Content: msg.Content,
}
}
return AnthropicMessages
}
func TestAnthropicKey(apiKey string) bool {
url := "https://api.anthropic.com/v1/messages"
AnthropicMessages := []AnthropicMessage{
AnthropicMessages := []Message{
{
Role: "user",
Content: "Hello",
@ -195,11 +170,9 @@ func RequestAnthropic(model string, messages []Message, maxTokens int, temperatu
url := "https://api.anthropic.com/v1/messages"
AnthropicMessages := EdgeMessages2AnthropicMessages(messages)
requestBody := AnthropicChatCompletionRequest{
Model: model,
Messages: AnthropicMessages,
Messages: ChangeRoleBot2Assistant(messages),
MaxTokens: maxTokens,
Temperature: temperature,
}

View File

@ -11,14 +11,9 @@ import (
)
type GroqChatCompletionRequest struct {
Model string `json:"model"`
Messages []GroqMessage `json:"messages"`
Temperature float64 `json:"temperature"`
}
type GroqMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature float64 `json:"temperature"`
}
type GroqChatCompletionResponse struct {
@ -37,9 +32,9 @@ type GroqUsage struct {
}
type GroqChoice struct {
Message GroqMessage `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func init() {
@ -106,31 +101,11 @@ func addGroqMessage(modelID string, selected bool) edgedb.UUID {
return edgedb.UUID{}
}
func EdgeMessages2GroqMessages(messages []Message) []GroqMessage {
groqMessages := make([]GroqMessage, len(messages))
for i, msg := range messages {
var role string
switch msg.Role {
case "user":
role = "user"
case "bot":
role = "assistant"
default:
role = "system"
}
groqMessages[i] = GroqMessage{
Role: role,
Content: msg.Content,
}
}
return groqMessages
}
func TestGroqKey(apiKey string) bool {
url := "https://api.groq.com/openai/v1/chat/completions"
// Convert messages to Qroq format
groqMessages := []GroqMessage{
groqMessages := []Message{
{
Role: "user",
Content: "Hello",
@ -199,12 +174,9 @@ func RequestGroq(model string, messages []Message, temperature float64) (GroqCha
url := "https://api.groq.com/openai/v1/chat/completions"
// Convert messages to Qroq format
groqMessages := EdgeMessages2GroqMessages(messages)
requestBody := GroqChatCompletionRequest{
Model: model,
Messages: groqMessages,
Messages: ChangeRoleBot2Assistant(messages),
Temperature: temperature,
}

View File

@ -11,16 +11,10 @@ import (
)
type MistralChatCompletionRequest struct {
Model string `json:"model"`
Messages []MistralMessage `json:"messages"`
Temperature float64 `json:"temperature"`
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature float64 `json:"temperature"`
}
type MistralMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type MistralChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
@ -37,9 +31,9 @@ type MistralUsage struct {
}
type MistralChoice struct {
Message MistralMessage `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func init() {
@ -139,31 +133,11 @@ func addMistralMessage(modelID string, selected bool) edgedb.UUID {
return edgedb.UUID{}
}
func EdgeMessages2MistralMessages(messages []Message) []MistralMessage {
mistralMessages := make([]MistralMessage, len(messages))
for i, msg := range messages {
var role string
switch msg.Role {
case "user":
role = "user"
case "bot":
role = "assistant"
default:
role = "system"
}
mistralMessages[i] = MistralMessage{
Role: role,
Content: msg.Content,
}
}
return mistralMessages
}
func TestMistralKey(apiKey string) bool {
url := "https://api.mistral.ai/v1/chat/completions"
// Convert messages to Mistral format
mistralMessages := []MistralMessage{
mistralMessages := []Message{
{
Role: "user",
Content: "Hello",
@ -237,12 +211,9 @@ func RequestMistral(model string, messages []Message, temperature float64) (Mist
url := "https://api.mistral.ai/v1/chat/completions"
// Convert messages to OpenAI format
mistralMessages := EdgeMessages2MistralMessages(messages)
requestBody := MistralChatCompletionRequest{
Model: model,
Messages: mistralMessages,
Messages: ChangeRoleBot2Assistant(messages),
Temperature: temperature,
}

View File

@ -11,14 +11,9 @@ import (
)
type OpenaiChatCompletionRequest struct {
Model string `json:"model"`
Messages []OpenaiMessage `json:"messages"`
Temperature float64 `json:"temperature"`
}
type OpenaiMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature float64 `json:"temperature"`
}
type OpenaiChatCompletionResponse struct {
@ -37,9 +32,9 @@ type OpenaiUsage struct {
}
type OpenaiChoice struct {
Message OpenaiMessage `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func init() {
@ -117,31 +112,11 @@ func addOpenaiMessage(modelID string, selected bool) edgedb.UUID {
return edgedb.UUID{}
}
func EdgeMessages2OpenaiMessages(messages []Message) []OpenaiMessage {
openaiMessages := make([]OpenaiMessage, len(messages))
for i, msg := range messages {
var role string
switch msg.Role {
case "user":
role = "user"
case "bot":
role = "assistant"
default:
role = "system"
}
openaiMessages[i] = OpenaiMessage{
Role: role,
Content: msg.Content,
}
}
return openaiMessages
}
func TestOpenaiKey(apiKey string) bool {
url := "https://api.openai.com/v1/chat/completions"
// Convert messages to OpenAI format
openaiMessages := []OpenaiMessage{
openaiMessages := []Message{
{
Role: "user",
Content: "Hello",
@ -208,12 +183,9 @@ func RequestOpenai(model string, messages []Message, temperature float64) (Opena
url := "https://api.openai.com/v1/chat/completions"
// Convert messages to OpenAI format
openaiMessages := EdgeMessages2OpenaiMessages(messages)
requestBody := OpenaiChatCompletionRequest{
Model: model,
Messages: openaiMessages,
Messages: ChangeRoleBot2Assistant(messages),
Temperature: temperature,
}

View File

@ -40,7 +40,7 @@ type Conversation struct {
type Area struct {
ID edgedb.UUID `edgedb:"id"`
Position int `edgedb:"position"`
Position int64 `edgedb:"position"`
Conv Conversation `edgedb:"conversation"`
}
@ -111,6 +111,24 @@ func checkIfLogin() bool {
return err == nil
}
func addUsage(inputCost float32, outputCost float32, inputToken int32, outputToken int32, modelID string) {
// Create a new usage
err := edgeClient.Execute(edgeCtx, `
INSERT Usage {
input_cost := <float32>$0,
output_cost := <float32>$1,
input_token := <int32>$2,
output_token := <int32>$3,
model_id := <str>$4,
user := global currentUser
}
`, inputCost, outputCost, inputToken, outputToken, modelID)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addUsage")
log.Fatal(err)
}
}
func insertNewConversation() edgedb.UUID {
var inserted struct{ id edgedb.UUID }
err := edgeClient.QuerySingle(edgeCtx, `
@ -126,7 +144,7 @@ func insertNewConversation() edgedb.UUID {
return inserted.id
}
func insertArea() edgedb.UUID {
func insertArea() (edgedb.UUID, int64) {
// If the Default conversation doesn't exist, create it.
var convExists bool
edgeClient.QuerySingle(edgeCtx, `
@ -157,7 +175,21 @@ func insertArea() edgedb.UUID {
log.Fatal(err)
}
return inserted.id
var positionSet struct{ position int64 }
err = edgeClient.QuerySingle(edgeCtx, `
SELECT Area {
position
}
FILTER .conversation.name = 'Default' AND .conversation.user = global currentUser
ORDER BY .position
LIMIT 1
`, &positionSet)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in insertArea")
log.Fatal(err)
}
return inserted.id, positionSet.position
}
func insertUserMessage(content string) edgedb.UUID {

View File

@ -12,11 +12,13 @@ import (
var userTmpl *pongo2.Template
var botTmpl *pongo2.Template
var modelSelecBtnTmpl *pongo2.Template
var sseChanel *ssefiber.FiberSSEChannel
func main() {
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
userTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-user.html"))
modelSelecBtnTmpl = pongo2.Must(pongo2.FromFile("views/partials/model-selection-btn.html"))
// Import HTML using django engine/template
engine := django.New("./views", ".html")
@ -28,7 +30,7 @@ func main() {
EnablePrintRoutes: true,
})
sse := ssefiber.New(app, "/sse")
sseChanel = sse.CreateChannel("sse", "/sse")
sseChanel = sse.CreateChannel("sse", "")
// Add default logger
app.Use(logger.New())
@ -41,7 +43,6 @@ func main() {
app.Get("/loadChat", LoadChatHandler)
// Chat routes
app.Post("/requestMultipleMessages", RequestMultipleMessagesHandler)
app.Post("/deleteMessage", DeleteMessageHandler)
app.Get("/generateMultipleMessages", GenerateMultipleMessages)
app.Get("/messageContent", GetMessageContentHandler)
@ -66,6 +67,10 @@ func main() {
app.Get("/test", func(c *fiber.Ctx) error {
fmt.Println("Hello from test")
go sseChanel.SendEvent(
"swapIcon-1",
`<img src="icons/groq.png" alt="User Image">`,
)
return c.SendString("")
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -47,10 +47,6 @@ html {
--bulma-primary: #126d0f;
}
.button.is-primary {
background-color: #1ebc19;
}
/* Chat input stuff */
.chat-input-container {
display: flex;

View File

@ -124,3 +124,23 @@ func removeDuplicate(s []string) []string {
}
return result
}
func ChangeRoleBot2Assistant(messages []Message) []Message {
openaiMessages := make([]Message, len(messages))
for i, msg := range messages {
var role string
switch msg.Role {
case "user":
role = "user"
case "bot":
role = "assistant"
default:
role = "system"
}
openaiMessages[i] = Message{
Role: role,
Content: msg.Content,
}
}
return openaiMessages
}

View File

@ -18,8 +18,13 @@
<i class="fa-solid fa-broom"></i>
</span>
</button>
<button type="submit" class="button is-small" hx-get="/test" hx-swap="beforeend">
<span class="icon">
<i class="fa-solid fa-vials"></i>
</span>
</button>
<button disabled type="submit" class="send-button button is-primary is-small"
hx-post="/requestMultipleMessages" hx-swap="beforeend settle:200ms" hx-target="#chat-messages"
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()">
<span class="icon">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html data-theme="dark" lang="en">
<head>
<meta charset="utf-8">
@ -11,11 +11,12 @@
<link rel="stylesheet" href="/animations.css">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/sse.js"></script>
</head>
<body hx-ext="sse" sse-connect="/sse">
{{embed}}

View File

@ -1,24 +1,33 @@
<div class="message-bot mt-3">
<div class="columns is-mobile">
<div class="column is-narrow" id="icon-column">
<!-- Left column with the icon -->
{% if IsPlaceholder %}
<figure class="image is-48x48" style="flex-shrink: 0;" id="selectedIcon-{{ ConversationAreaId }}"
sse-swap="swapIcon-{{ ConversationAreaId }}">
<img src="icons/bouvai2.png" alt="User Image">
</figure>
{% else %}
{% for message in Messages %}
{% if not message.Hidden %}
<figure class="image is-48x48" style="flex-shrink: 0;">
<img id="selectedIcon-{{ ConversationAreaId }}" src="icons/{{ message.Icon }}.png" alt="User Image">
<figure class="image is-48x48" style="flex-shrink: 0;" id="selectedIcon-{{ ConversationAreaId }}"
sse-swap="swapIcon-{{ ConversationAreaId }}">
<img src="icons/{{ message.Icon }}.png" alt="User Image">
</figure>
{% endif %}
{% endfor %}
{% if IsPlaceholder %}
<figure class="image is-48x48" style="flex-shrink: 0;">
<img src="icons/bouvai2.png" alt="User Image">
</figure>
{% endif %}
</div>
<div class="column" id="content-column">
{% if not IsPlaceholder %}
<div class="is-flex is-align-items-start">
<div class="message-content" id="content-{{ ConversationAreaId }}">
<div class="message-content" id="content-{{ ConversationAreaId }}"
sse-swap="swapContent-{{ ConversationAreaId }}">
{% for message in Messages %}
{% if not message.Hidden %}
<div class="message-header">
@ -55,21 +64,34 @@
</button>
{% endfor %}
{% endif %}
</div>
{% elif IsPlaceholder %}
<hx hx-get="/generateMultipleMessages" hx-trigger="load" hx-swap="outerHTML" hx-indicator="#spinner"
hx-target="#chat-container">
</hx>
<div class="message-content" {% if message.Hidden %}style="display: none;" {% endif %}>
{% elif IsPlaceholder %}
<div class="is-flex is-align-items-start">
<div class="message-content" id="content-{{ ConversationAreaId }}"
sse-swap="swapContent-{{ ConversationAreaId }}">
<div class="message-body">
<div class="content">
<img id="spinner" class="htmx-indicator" src="/puff.svg" />
<img src="/puff.svg" />
</div>
</div>
</div>
{% endif %}
</div>
<div class="is-flex is-justify-content mt-2">
{% for selectedModel in selectedModels %}
<button disable class="button is-small is-primary message-button is-outlined mr-1"
sse-swap="swapSelectionBtn-{{ selectedModel.ID }}" hx-swap="outerHTML">
<span class="icon is-small">
<!--img src="icons/{{ selectedModel.Icon }}.png" alt="{{ selectedModel.Name }}"
style="max-height: 100%; max-width: 100%;"-->
<img src="/puff.svg" />
</span>
</button>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -0,0 +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('{{ message.Icon }}', '{{ ConversationAreaId }}')"
title="{{ message.Name }}">
<span class="icon is-small">
<img src="icons/{{ message.Icon }}.png" alt="{{ message.Name }}" style="max-height: 100%; max-width: 100%;">
</span>
</button>