SSE per user
This commit is contained in:
parent
ec7fe75216
commit
1125f86331
18
Chat.go
18
Chat.go
@ -126,7 +126,16 @@ func generateChatHTML(c *fiber.Ctx) string {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
|
var user User
|
||||||
|
err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
|
||||||
|
SELECT global currentUser { id } LIMIT 1
|
||||||
|
`, &user)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting user")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlString := "<div class='columns is-centered' id='chat-container' sse-connect='/sse?userID=" + user.ID.String() + "'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
|
||||||
|
|
||||||
var templateMessages []TemplateMessage
|
var templateMessages []TemplateMessage
|
||||||
|
|
||||||
@ -177,13 +186,6 @@ func generateChatHTML(c *fiber.Ctx) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// out, err := messagesPlaceholderTmpl.Execute(pongo2.Context{})
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Println("Error executing message user placeholder template")
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// htmlString += out
|
|
||||||
|
|
||||||
htmlString += "</div></div>"
|
htmlString += "</div></div>"
|
||||||
|
|
||||||
// Render the HTML template with the messages
|
// Render the HTML template with the messages
|
||||||
|
15
Request.go
15
Request.go
@ -230,18 +230,20 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
|
|||||||
outIcon := `<img src="` + selectedLLMs[idx].Model.Company.Icon + `" alt="User Image" id="selectedIcon-` + fmt.Sprintf("%d", message.Area.Position) + `">`
|
outIcon := `<img src="` + selectedLLMs[idx].Model.Company.Icon + `" alt="User Image" id="selectedIcon-` + fmt.Sprintf("%d", message.Area.Position) + `">`
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// I do a ping because of sse size limit
|
// I do a ping because of sse size limit. Do see if it's possible to do without it TODO
|
||||||
fmt.Println("Sending event: ", "swapContent-"+fmt.Sprintf("%d", message.Area.Position)+"-"+user.ID.String())
|
|
||||||
sendEvent(
|
sendEvent(
|
||||||
"swapContent-"+fmt.Sprintf("%d", message.Area.Position)+"-"+user.ID.String(),
|
user.ID.String(),
|
||||||
|
"swapContent-"+fmt.Sprintf("%d", message.Area.Position),
|
||||||
`<hx hx-get="/messageContent?id=`+message.ID.String()+`" hx-trigger="load" hx-swap="outerHTML"></hx>`,
|
`<hx hx-get="/messageContent?id=`+message.ID.String()+`" hx-trigger="load" hx-swap="outerHTML"></hx>`,
|
||||||
)
|
)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
"swapSelectionBtn-"+selectedLLMs[idx].ID.String()+"-"+user.ID.String(),
|
user.ID.String(),
|
||||||
|
"swapSelectionBtn-"+selectedLLMs[idx].ID.String(),
|
||||||
outBtn,
|
outBtn,
|
||||||
)
|
)
|
||||||
sendEvent(
|
sendEvent(
|
||||||
"swapIcon-"+fmt.Sprintf("%d", message.Area.Position)+"-"+user.ID.String(),
|
user.ID.String(),
|
||||||
|
"swapIcon-"+fmt.Sprintf("%d", message.Area.Position),
|
||||||
outIcon,
|
outIcon,
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
@ -261,7 +263,8 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
|
|||||||
// Send Content event
|
// Send Content event
|
||||||
go func() {
|
go func() {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
"swapSelectionBtn-"+selectedLLMs[idx].ID.String()+"-"+user.ID.String(),
|
user.ID.String(),
|
||||||
|
"swapSelectionBtn-"+selectedLLMs[idx].ID.String(),
|
||||||
outBtn,
|
outBtn,
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
|
@ -154,12 +154,12 @@ func RequestGoogle(c *fiber.Ctx, model string, messages []Message, temperature f
|
|||||||
if message.Role == "user" {
|
if message.Role == "user" {
|
||||||
googleMessages = append(googleMessages, GoogleRequestMessage{
|
googleMessages = append(googleMessages, GoogleRequestMessage{
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Parts: []GooglePart{GooglePart{Text: message.Content}},
|
Parts: []GooglePart{{Text: message.Content}}, // Changed something here, to test
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
googleMessages = append(googleMessages, GoogleRequestMessage{
|
googleMessages = append(googleMessages, GoogleRequestMessage{
|
||||||
Role: "model",
|
Role: "model",
|
||||||
Parts: []GooglePart{GooglePart{Text: message.Content}},
|
Parts: []GooglePart{{Text: message.Content}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
main.go
47
main.go
@ -27,9 +27,9 @@ var (
|
|||||||
welcomeChatTmpl *pongo2.Template
|
welcomeChatTmpl *pongo2.Template
|
||||||
chatInputTmpl *pongo2.Template
|
chatInputTmpl *pongo2.Template
|
||||||
explainLLMconvChatTmpl *pongo2.Template
|
explainLLMconvChatTmpl *pongo2.Template
|
||||||
messagesPlaceholderTmpl *pongo2.Template
|
|
||||||
clients = make(map[chan SSE]bool)
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
app *fiber.App
|
||||||
|
userSSEChannels = make(map[string]chan SSE)
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSE event structure
|
// SSE event structure
|
||||||
@ -39,13 +39,16 @@ type SSE struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to send events to all clients
|
// Function to send events to all clients
|
||||||
func sendEvent(event, data string) {
|
func sendEvent(userID string, event string, data string) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
for client := range clients {
|
userEvents, ok := userSSEChannels[userID]
|
||||||
client <- SSE{Event: event, Data: data}
|
if !ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userEvents <- SSE{Event: event, Data: data}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -63,13 +66,12 @@ func main() {
|
|||||||
welcomeChatTmpl = pongo2.Must(pongo2.FromFile("views/partials/welcome-chat.html"))
|
welcomeChatTmpl = pongo2.Must(pongo2.FromFile("views/partials/welcome-chat.html"))
|
||||||
chatInputTmpl = pongo2.Must(pongo2.FromFile("views/partials/chat-input.html"))
|
chatInputTmpl = pongo2.Must(pongo2.FromFile("views/partials/chat-input.html"))
|
||||||
explainLLMconvChatTmpl = pongo2.Must(pongo2.FromFile("views/partials/explain-llm-conv-chat.html"))
|
explainLLMconvChatTmpl = pongo2.Must(pongo2.FromFile("views/partials/explain-llm-conv-chat.html"))
|
||||||
messagesPlaceholderTmpl = pongo2.Must(pongo2.FromFile("views/partials/messages-placeholder.html"))
|
|
||||||
|
|
||||||
// Import HTML using django engine/template
|
// Import HTML using django engine/template
|
||||||
engine := django.New("./views", ".html")
|
engine := django.New("./views", ".html")
|
||||||
|
|
||||||
// Create new Fiber instance
|
// Create new Fiber instance
|
||||||
app := fiber.New(fiber.Config{
|
app = fiber.New(fiber.Config{
|
||||||
Views: engine,
|
Views: engine,
|
||||||
AppName: "JADE",
|
AppName: "JADE",
|
||||||
})
|
})
|
||||||
@ -133,14 +135,23 @@ func main() {
|
|||||||
return c.SendString("")
|
return c.SendString("")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/sse", func(c *fiber.Ctx) error {
|
app.Get("/sse", handleSSE)
|
||||||
c.Set("Content-Type", "text/event-stream")
|
|
||||||
c.Set("Cache-Control", "no-cache")
|
// Start server
|
||||||
c.Set("Connection", "keep-alive")
|
if err := app.Listen(":8080"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSSE(c *fiber.Ctx) error {
|
||||||
|
userID := c.Query("userID") // Get userID from query parameter
|
||||||
|
if userID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Missing userID")
|
||||||
|
}
|
||||||
|
|
||||||
events := make(chan SSE, 500)
|
events := make(chan SSE, 500)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
clients[events] = true
|
userSSEChannels[userID] = events
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
// Create a context copy to use in the goroutine
|
// Create a context copy to use in the goroutine
|
||||||
@ -149,11 +160,15 @@ func main() {
|
|||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
delete(clients, events)
|
delete(userSSEChannels, userID)
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
close(events)
|
close(events)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/event-stream")
|
||||||
|
c.Set("Cache-Control", "no-cache")
|
||||||
|
c.Set("Connection", "keep-alive")
|
||||||
|
|
||||||
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
for event := range events {
|
for event := range events {
|
||||||
if _, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Event, event.Data); err != nil {
|
if _, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Event, event.Data); err != nil {
|
||||||
@ -165,12 +180,6 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
|
|
||||||
// Start server
|
|
||||||
if err := app.Listen(":8080"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addKeys(c *fiber.Ctx) error {
|
func addKeys(c *fiber.Ctx) error {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<div class="chat-container mt-5" style="padding-bottom: 155px;" hx-indicator="#textarea-control">
|
<div class="chat-container mt-5" style="padding-bottom: 155px;" hx-indicator="#textarea-control" hx-ext="sse">
|
||||||
<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML"></hx>
|
<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML"></hx>
|
||||||
|
|
||||||
<hx hx-get="/loadChatInput" hx-trigger="load once" hx-swap="outerHTML"></hx>
|
<hx hx-get="/loadChatInput" hx-trigger="load once" hx-swap="outerHTML"></hx>
|
||||||
</div>
|
</div>
|
@ -6,7 +6,7 @@
|
|||||||
{% if IsPlaceholder %}
|
{% if IsPlaceholder %}
|
||||||
|
|
||||||
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;"
|
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;"
|
||||||
sse-swap="swapIcon-{{ ConversationAreaId }}-{{ userID }}">
|
sse-swap="swapIcon-{{ ConversationAreaId }}">
|
||||||
<img src="icons/bouvai2.png" alt="User Image" id="selectedIcon-{{ ConversationAreaId }}">
|
<img src="icons/bouvai2.png" alt="User Image" id="selectedIcon-{{ ConversationAreaId }}">
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
@ -72,7 +72,7 @@
|
|||||||
{% elif IsPlaceholder %}
|
{% elif IsPlaceholder %}
|
||||||
<div class="is-flex is-align-items-start">
|
<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 }}-{{ userID }}">
|
sse-swap="swapContent-{{ ConversationAreaId }}">
|
||||||
<hx hx-trigger="load" hx-get="/generateMultipleMessages" id="generate-multiple-messages"></hx>
|
<hx hx-trigger="load" hx-get="/generateMultipleMessages" id="generate-multiple-messages"></hx>
|
||||||
<div class='message-header'>
|
<div class='message-header'>
|
||||||
<p>
|
<p>
|
||||||
@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
{% for selectedLLM in SelectedLLMs %}
|
{% for selectedLLM in SelectedLLMs %}
|
||||||
<button disable class="button is-small is-primary message-button is-outlined mr-1"
|
<button disable class="button is-small is-primary message-button is-outlined mr-1"
|
||||||
sse-swap="swapSelectionBtn-{{ selectedLLM.ID.String() }}-{{ userID }}" hx-swap="outerHTML"
|
sse-swap="swapSelectionBtn-{{ selectedLLM.ID.String() }}" hx-swap="outerHTML"
|
||||||
hx-target="this">
|
hx-target="this">
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<!--img src="icons/{{ selectedLLM.Company }}.png" alt="{{ selectedLLM.Name }}"
|
<!--img src="icons/{{ selectedLLM.Company }}.png" alt="{{ selectedLLM.Name }}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user