SSE working demo
This commit is contained in:
parent
947ac9c713
commit
bb8f53a018
14
Chat.go
14
Chat.go
@ -15,6 +15,11 @@ 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})
|
||||
}
|
||||
@ -400,7 +405,14 @@ func LoadKeysHandler(c *fiber.Ctx) error {
|
||||
}
|
||||
openaiExists, anthropicExists, mistralExists, groqExists := getExistingKeys()
|
||||
|
||||
out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-keys.html")).Execute(pongo2.Context{"IsLogin": checkIfLogin(), "OpenaiExists": openaiExists, "AnthropicExists": anthropicExists, "MistralExists": mistralExists, "GroqExists": groqExists})
|
||||
out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-keys.html")).Execute(pongo2.Context{
|
||||
"IsLogin": checkIfLogin(),
|
||||
"OpenaiExists": openaiExists,
|
||||
"AnthropicExists": anthropicExists,
|
||||
"MistralExists": mistralExists,
|
||||
"GroqExists": groqExists,
|
||||
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists,
|
||||
})
|
||||
if err != nil {
|
||||
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Error rendering template",
|
||||
|
20
login.go
20
login.go
@ -32,8 +32,6 @@ func generatePKCE() (string, string) {
|
||||
func handleUiSignIn(c *fiber.Ctx) error {
|
||||
verifier, challenge := generatePKCE()
|
||||
|
||||
fmt.Println("Challenge: ", challenge)
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "jade-edgedb-pkce-verifier",
|
||||
Value: verifier,
|
||||
@ -49,11 +47,11 @@ func handleUiSignIn(c *fiber.Ctx) error {
|
||||
func handleCallbackSignup(c *fiber.Ctx) error {
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
error := c.Query("error")
|
||||
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", error))
|
||||
err := c.Query("error")
|
||||
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", err))
|
||||
}
|
||||
|
||||
verifier := c.Cookies("jade-edgedb-pkce-verifier")
|
||||
verifier := c.Cookies("jade-edgedb-pkce-verifier", "")
|
||||
if verifier == "" {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
|
||||
}
|
||||
@ -111,17 +109,17 @@ func handleCallbackSignup(c *fiber.Ctx) error {
|
||||
|
||||
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": tokenResponse.AuthToken})
|
||||
|
||||
return c.Redirect("/", fiber.StatusTemporaryRedirect)
|
||||
return c.Redirect("/", fiber.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func handleCallback(c *fiber.Ctx) error {
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
error := c.Query("error")
|
||||
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", error))
|
||||
err := c.Query("error")
|
||||
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("OAuth callback is missing 'code'. OAuth provider responded with error: %s", err))
|
||||
}
|
||||
|
||||
verifier := c.Cookies("jade-edgedb-pkce-verifier")
|
||||
verifier := c.Cookies("jade-edgedb-pkce-verifier", "")
|
||||
if verifier == "" {
|
||||
fmt.Println("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Could not find 'verifier' in the cookie store. Is this the same user agent/browser that started the authorization flow?")
|
||||
@ -158,7 +156,7 @@ func handleCallback(c *fiber.Ctx) error {
|
||||
|
||||
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": tokenResponse.AuthToken})
|
||||
|
||||
return c.Redirect("/", fiber.StatusTemporaryRedirect)
|
||||
return c.Redirect("/", fiber.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func handleSignOut(c *fiber.Ctx) error {
|
||||
@ -175,5 +173,5 @@ func handleSignOut(c *fiber.Ctx) error {
|
||||
edgeCtx = ctx
|
||||
edgeClient = client
|
||||
|
||||
return c.Redirect("/", fiber.StatusTemporaryRedirect)
|
||||
return c.Redirect("/", fiber.StatusPermanentRedirect)
|
||||
}
|
||||
|
4
main.go
4
main.go
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/MrBounty/JADE2.0/ssefiber"
|
||||
"github.com/flosch/pongo2"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
|
||||
var userTmpl *pongo2.Template
|
||||
var botTmpl *pongo2.Template
|
||||
var sseChanel *ssefiber.FiberSSEChannel
|
||||
|
||||
func main() {
|
||||
botTmpl = pongo2.Must(pongo2.FromFile("views/partials/message-bot.html"))
|
||||
@ -25,6 +27,8 @@ func main() {
|
||||
AppName: "JADE 2.0",
|
||||
EnablePrintRoutes: true,
|
||||
})
|
||||
sse := ssefiber.New(app, "/sse")
|
||||
sseChanel = sse.CreateChannel("sse", "/sse")
|
||||
|
||||
// Add default logger
|
||||
app.Use(logger.New())
|
||||
|
250
ssefiber/sse.go
Normal file
250
ssefiber/sse.go
Normal file
@ -0,0 +1,250 @@
|
||||
package ssefiber
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// SSEEvent is a struct that represents an SSE event
|
||||
type FiberSSEEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ID string `json:"id"`
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
Retry string `json:"retry"`
|
||||
OnChannel *FiberSSEChannel
|
||||
}
|
||||
|
||||
// # TypeDef
|
||||
//
|
||||
// Handler for channel events
|
||||
type FiberSSEEventHandler func(ctx *fiber.Ctx, sseChannel *FiberSSEChannel)
|
||||
|
||||
// # TypeDef
|
||||
//
|
||||
// Handler for specific events on a channel
|
||||
type FiberSSEOnEventHandler func(ctx *fiber.Ctx, sseChannel *FiberSSEChannel, sseEvent *FiberSSEEvent)
|
||||
type FiberSSEEvents interface {
|
||||
OnConnect(handlers ...FiberSSEEventHandler)
|
||||
OnDisconnect(handlers ...FiberSSEEventHandler)
|
||||
OnEvent(eventName string, handlers ...FiberSSEOnEventHandler)
|
||||
FireOnEventHandlers(fiberCtx *fiber.Ctx, event string)
|
||||
}
|
||||
|
||||
/*
|
||||
A channel with a name, and a sub-base-path
|
||||
*/
|
||||
type FiberSSEChannel struct {
|
||||
FiberSSEEvents
|
||||
Name string
|
||||
Base string
|
||||
Events chan *FiberSSEEvent
|
||||
ParentSSEApp *FiberSSEApp
|
||||
Handlers map[string]([]FiberSSEEventHandler)
|
||||
EventHandlers map[string]([]FiberSSEOnEventHandler)
|
||||
}
|
||||
type FiberSSEHandler func(c *fiber.Ctx, w *bufio.Writer) error
|
||||
|
||||
/*
|
||||
The SSE Information Structure includes a list of channels and the fiber application
|
||||
*/
|
||||
type FiberSSEApp struct {
|
||||
IFiberSSEApp
|
||||
Base string
|
||||
Router *fiber.Router
|
||||
Channels map[string]*FiberSSEChannel
|
||||
FiberApp *fiber.App
|
||||
}
|
||||
|
||||
// FiberSSEApp Interface
|
||||
type IFiberSSEApp interface {
|
||||
ServeHTTP(ctx *fiber.Ctx) error
|
||||
CreateChannel(name, base string) *FiberSSEChannel
|
||||
ListChannels() map[string]*FiberSSEChannel
|
||||
GetChannel(name string) *FiberSSEChannel
|
||||
}
|
||||
|
||||
/*
|
||||
New initializes a base SSE route group at `base`.
|
||||
|
||||
The base route is the base path for all channels.
|
||||
|
||||
The channels parameter is a list of channels that will be created.
|
||||
Each channel has a name, a base route, and a channel for sending events.
|
||||
|
||||
// Create a new SSE app
|
||||
app := fiber.New()
|
||||
// Create a new SSE app on the fiber app
|
||||
sseApp := ssefiber.New(app, "/sse")
|
||||
// Add a channel to the SSE app
|
||||
testChan := sseApp.CreateChannel("test", "/test") // Channel at /sse/test
|
||||
// Events Channel
|
||||
eventsChan := testChan.Events
|
||||
*/
|
||||
func New(app *fiber.App, base string) *FiberSSEApp {
|
||||
// Add the base route
|
||||
fiberRouter := app.Group(base, func(c *fiber.Ctx) error {
|
||||
// Set the headers for SSE
|
||||
c.Set("Cache-Control", "no-cache")
|
||||
c.Set("Content-Type", "text/event-stream")
|
||||
c.Set("Connection", "keep-alive")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
// Create a new SSE App
|
||||
newFSSEApp := &FiberSSEApp{
|
||||
Base: base,
|
||||
Router: &fiberRouter,
|
||||
FiberApp: app,
|
||||
Channels: make(map[string]*FiberSSEChannel),
|
||||
}
|
||||
return newFSSEApp
|
||||
}
|
||||
|
||||
/*
|
||||
CreateChannel creates a new channel with the given name and base path.
|
||||
Functions as a shortcut for making a new chan each time
|
||||
|
||||
Example:
|
||||
|
||||
app := fiber.New()
|
||||
sseApp := ssefiber.New(app, "/sse")
|
||||
chanOne := sseApp.CreateChannel("Channel One", "/one")
|
||||
chanTwo := sseApp.CreateChannel("Channel Two", "/two")
|
||||
*/
|
||||
func (app *FiberSSEApp) CreateChannel(name, base string) *FiberSSEChannel {
|
||||
newChannel := &FiberSSEChannel{
|
||||
Name: name,
|
||||
Base: base,
|
||||
Events: make(chan *FiberSSEEvent),
|
||||
ParentSSEApp: app,
|
||||
Handlers: make(map[string][]FiberSSEEventHandler),
|
||||
EventHandlers: make(map[string][]FiberSSEOnEventHandler),
|
||||
}
|
||||
app.Channels[name] = newChannel
|
||||
// Add the sub-route for the channel
|
||||
(*app.Router).Get(newChannel.Base, newChannel.ServeHTTP)
|
||||
return newChannel
|
||||
}
|
||||
|
||||
// ListChannels returns a list of all the channels and prints them to the console
|
||||
func (app *FiberSSEApp) ListChannels() map[string]*FiberSSEChannel {
|
||||
fmt.Println("Listing Channels...")
|
||||
for _, channel := range app.Channels {
|
||||
channel.Print()
|
||||
}
|
||||
return app.Channels
|
||||
}
|
||||
|
||||
/*
|
||||
Create an event and send it to the channel.
|
||||
*/
|
||||
func (channel *FiberSSEChannel) SendEvent(event, data string) {
|
||||
sseEvent := &FiberSSEEvent{
|
||||
Timestamp: time.Now(),
|
||||
Event: event,
|
||||
Data: data,
|
||||
OnChannel: channel,
|
||||
}
|
||||
channel.Events <- sseEvent
|
||||
}
|
||||
|
||||
// Flush the event to the writer `w` - formats according to SSE standard
|
||||
func (e *FiberSSEEvent) Flush(w *bufio.Writer) error {
|
||||
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", e.Event, e.Data)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// Prints the channel information to the console
|
||||
func (c *FiberSSEChannel) Print() {
|
||||
fmt.Printf("==CHANNEL CREATED==\nName: %s\nRoute Endpoint: %s\n===================", c.Name, c.ParentSSEApp.Base+c.Base)
|
||||
}
|
||||
|
||||
// # Internal Method
|
||||
//
|
||||
// ServeHTTP returns a fiber.Handler for the channel.
|
||||
//
|
||||
// Use `sseApp.CreateChannel` to create a new channel.
|
||||
func (fChan *FiberSSEChannel) ServeHTTP(c *fiber.Ctx) error {
|
||||
|
||||
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
// Fire OnConnect Event Handlers
|
||||
|
||||
go fChan.FireHandlers(c, "connect")
|
||||
|
||||
for {
|
||||
event, more := <-fChan.Events
|
||||
// fmt.Fprintf(w, "event: %s\ndata: %s\n\n", string(event.Event), string(event.Data))
|
||||
// w.Flush()
|
||||
go event.FireEventHandlers(c)
|
||||
if err := event.Flush(w); err != nil {
|
||||
go fChan.FireHandlers(c, "disconnect")
|
||||
return
|
||||
}
|
||||
if !more {
|
||||
// Fire OnDisconnect Event Handlers
|
||||
go fChan.FireHandlers(c, "disconnect")
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Cleanup removes all of the channels from the app. Should be used as a defer
|
||||
func (sseApp *FiberSSEApp) Cleanup() {
|
||||
for _, channel := range sseApp.Channels {
|
||||
close(channel.Events)
|
||||
}
|
||||
fmt.Println("All Channels Closed - Cleanup Successful")
|
||||
}
|
||||
|
||||
// Fire the handlers for a given channel event (connect, disconnect)
|
||||
func (channel *FiberSSEChannel) FireHandlers(fiberCtx *fiber.Ctx, event string) {
|
||||
for _, handler := range channel.Handlers[event] {
|
||||
handler(fiberCtx, channel)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire the handlers for this event
|
||||
func (e *FiberSSEEvent) FireEventHandlers(fiberCtx *fiber.Ctx) {
|
||||
channel := e.OnChannel
|
||||
for _, handler := range channel.EventHandlers[e.Event] {
|
||||
handler(fiberCtx, channel, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the handlers to the channel for the connect method
|
||||
func (channel *FiberSSEChannel) OnConnect(handlers ...FiberSSEEventHandler) {
|
||||
channel.Handlers["connect"] = []FiberSSEEventHandler{}
|
||||
channel.Handlers["connect"] = append(channel.Handlers["connect"], handlers...)
|
||||
}
|
||||
|
||||
// Adds the handlers to the channel for the disconnect method
|
||||
func (channel *FiberSSEChannel) OnDisconnect(handlers ...FiberSSEEventHandler) {
|
||||
channel.Handlers["disconnect"] = []FiberSSEEventHandler{}
|
||||
channel.Handlers["disconnect"] = append(channel.Handlers["disconnect"], handlers...)
|
||||
}
|
||||
|
||||
// Add handlers for the any given event
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// channelOne.OnEvent("test", ...) // Fires anytime the event "test" is fired
|
||||
func (channel *FiberSSEChannel) OnEvent(eventName string, handlers ...FiberSSEOnEventHandler) {
|
||||
channel.EventHandlers[eventName] = []FiberSSEOnEventHandler{}
|
||||
channel.EventHandlers[eventName] = append(channel.EventHandlers[eventName], handlers...)
|
||||
|
||||
}
|
||||
|
||||
// Returns a channel by name
|
||||
func (app *FiberSSEApp) GetChannel(name string) *FiberSSEChannel {
|
||||
findChan := app.Channels[name]
|
||||
return findChan
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 84 KiB |
@ -47,6 +47,10 @@ html {
|
||||
--bulma-primary: #126d0f;
|
||||
}
|
||||
|
||||
.button.is-primary {
|
||||
background-color: #1ebc19;
|
||||
}
|
||||
|
||||
/* Chat input stuff */
|
||||
.chat-input-container {
|
||||
display: flex;
|
||||
|
@ -11,12 +11,11 @@
|
||||
|
||||
<link rel="stylesheet" href="/animations.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
|
||||
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
|
||||
</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>
|
||||
|
||||
{{embed}}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<div class="dropdown is-hoverable is-up is-right">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu4">
|
||||
<button class="button is-small {% if not AnyExists %} is-danger{% endif %}" aria-haspopup="true"
|
||||
aria-controls="dropdown-menu4">
|
||||
<span class="icon"><i class="fa-solid fa-key"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -7,11 +7,10 @@
|
||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-item">
|
||||
<a class="button is-small is-primary is-outlined mb-1" href="https://artificialanalysis.ai/models"
|
||||
target="_blank">
|
||||
<a class="button is-small is-primary mb-1" href="https://artificialanalysis.ai/models" target="_blank">
|
||||
Compare models
|
||||
</a>
|
||||
<a class="button is-small is-primary is-outlined mb-1" href="/signout">
|
||||
<a class="button is-small is-primary mb-1" href="/signout">
|
||||
Log out
|
||||
</a>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user