Working login logup and logout + placeholder welcome message

This commit is contained in:
Adrien Bouvais 2024-05-10 14:29:34 +02:00
parent 56f09d6d49
commit ed64fc01d8
11 changed files with 252 additions and 123 deletions

44
Chat.go
View File

@ -12,7 +12,15 @@ import (
) )
func ChatPageHandler(c *fiber.Ctx) error { func ChatPageHandler(c *fiber.Ctx) error {
return c.Render("chat", fiber.Map{}, "layouts/main") authCookie := c.Cookies("jade-edgedb-auth-token", "")
if authCookie != "" && !checkIfLogin() {
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie})
}
fmt.Println("Current User: ", getCurrentUser())
return c.Render("chat", fiber.Map{"IsLogin": checkIfLogin()}, "layouts/main")
} }
func LoadModelSelectionHandler(c *fiber.Ctx) error { func LoadModelSelectionHandler(c *fiber.Ctx) error {
@ -45,8 +53,6 @@ func LoadUsageKPIHandler(c *fiber.Ctx) error {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println(TotalUsage)
out, err := pongo2.Must(pongo2.FromFile("views/partials/usagePopover.html")).Execute(pongo2.Context{ out, err := pongo2.Must(pongo2.FromFile("views/partials/usagePopover.html")).Execute(pongo2.Context{
"TotalUsage": TotalUsage, "TotalUsage": TotalUsage,
}) })
@ -85,7 +91,11 @@ func DeleteMessageHandler(c *fiber.Ctx) error {
} }
func LoadChatHandler(c *fiber.Ctx) error { func LoadChatHandler(c *fiber.Ctx) error {
return c.SendString(generateChatHTML()) if checkIfLogin() {
return c.SendString(generateChatHTML())
} else {
return c.SendString(generateWelcomeChatHTML())
}
} }
type NextMessage struct { type NextMessage struct {
@ -115,7 +125,6 @@ func generateChatHTML() string {
// Reset NextMessages when a user message is encountered // Reset NextMessages when a user message is encountered
NextMessages = []NextMessage{} NextMessages = []NextMessage{}
} else { } else {
fmt.Println(i)
modelID, exist := message.ModelID.Get() modelID, exist := message.ModelID.Get()
if !exist { if !exist {
modelID = "gpt-3.5-turbo" modelID = "gpt-3.5-turbo"
@ -199,3 +208,28 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
return c.SendString(out) return c.SendString(out)
} }
func generateWelcomeChatHTML() string {
htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"
NextMessages := []NextMessage{}
nextMsg := NextMessage{
Icon: "bouvai2", // Assuming Icon is a field you want to include from Message
Content: markdownToHTML("Hi, I'm Bouvai. How can I help you today?"),
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
}
NextMessages = append(NextMessages, nextMsg)
botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0})
if err != nil {
panic(err)
}
htmlString += botOut
htmlString += "<div style='height: 10px;'></div>"
htmlString += "</div></div>"
// Render the HTML template with the messages
return htmlString
}

View File

@ -73,15 +73,8 @@ func init() {
log.Fatal(err) log.Fatal(err)
} }
// TODO Change
edgeCtx = ctx edgeCtx = ctx
var clientUUID edgedb.UUID edgeClient = client
clientUUID, err = edgedb.ParseUUID("9323365e-0b09-11ef-8f41-c3575d386283")
if err != nil {
fmt.Println("Error in edgedb.ParseUUID: in init")
log.Fatal(err)
}
edgeClient = client.WithGlobals(map[string]interface{}{"current_user_id": clientUUID})
} }
func getLastArea() edgedb.UUID { func getLastArea() edgedb.UUID {
@ -99,15 +92,21 @@ func getLastArea() edgedb.UUID {
return inserted.id return inserted.id
} }
func checkIfLogin() bool { func getCurrentUser() User {
var result User var result User
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result) err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
if err != nil { if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in checkIfLogin") fmt.Println("Error in edgedb.QuerySingle: in getCurrentUser")
fmt.Println(err) fmt.Println(err)
return false return User{}
} }
return true return result
}
func checkIfLogin() bool {
var result User
err := edgeClient.QuerySingle(edgeCtx, "SELECT global currentUser LIMIT 1;", &result)
return err == nil
} }
func insertArea() edgedb.UUID { func insertArea() edgedb.UUID {

View File

@ -1,10 +1,11 @@
using extension auth; using extension auth;
module default { module default {
global current_user_id: uuid;
global currentUser := ( global currentUser := (
assert_single((
select User select User
filter .id = global current_user_id filter .identity = global ext::auth::ClientTokenIdentity
))
); );
type User { type User {
@ -58,13 +59,13 @@ module default {
} }
type Usage { type Usage {
required model_id: str; required model_id: str;
required user: User; user: User;
input_cost: float32; input_cost: float32;
output_cost: float32; output_cost: float32;
input_token: int32; input_token: int32;
output_token: int32; output_token: int32;
required date: datetime { required date: datetime {
default := datetime_current(); default := datetime_current();
} }
} }

View File

@ -0,0 +1,11 @@
CREATE MIGRATION m1g3r5wwsplyqphor3yvb7dzp2txacgd6ed3udrpvakuoz3e2zj7ka
ONTO m1t2tddtq2grfxf4ldn6aj4yotvwiqclsrks6chobvs33d5mcd62mq
{
DROP GLOBAL default::currentUser;
CREATE GLOBAL default::current_user := (std::assert_single((SELECT
default::User
FILTER
(.identity = GLOBAL ext::auth::ClientTokenIdentity)
)));
DROP GLOBAL default::current_user_id;
};

View File

@ -0,0 +1,5 @@
CREATE MIGRATION m1c2b3o4bhfgldk3kebneqyv6oxfgmorrp62uv5rreu773ryed3azq
ONTO m1g3r5wwsplyqphor3yvb7dzp2txacgd6ed3udrpvakuoz3e2zj7ka
{
ALTER GLOBAL default::current_user RENAME TO default::currentUser;
};

View File

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

127
login.go
View File

@ -8,62 +8,106 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"time"
"github.com/edgedb/edgedb-go"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
const EDGEDB_AUTH_BASE_URL = "http://127.0.0.1:10700/db/main/ext/auth" const EDGEDB_AUTH_BASE_URL = "http://127.0.0.1:10700/db/main/ext/auth"
func generatePKCE() (string, string) { func generatePKCE() (string, string) {
fmt.Println("Generating PKCE") verifier_source := make([]byte, 32)
verifier := make([]byte, 32) _, err := rand.Read(verifier_source)
_, err := rand.Read(verifier)
if err != nil { if err != nil {
panic(err) panic(err)
} }
challenge := sha256.Sum256(verifier) verifier := base64.RawURLEncoding.EncodeToString(verifier_source)
challenge := sha256.Sum256([]byte(verifier))
var URLEncoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") return verifier, base64.RawURLEncoding.EncodeToString(challenge[:])
var RawURLEncoding = URLEncoding.WithPadding(base64.NoPadding)
encodedVerifier := RawURLEncoding.EncodeToString(verifier)
encodedChallenge := RawURLEncoding.EncodeToString(challenge[:])
fmt.Println("verifier: " + encodedVerifier)
fmt.Println("challenge: " + encodedChallenge)
return encodedVerifier, encodedChallenge
} }
func handleUiSignIn(c *fiber.Ctx) error { func handleUiSignIn(c *fiber.Ctx) error {
verifier, challenge := generatePKCE() verifier, challenge := generatePKCE()
fmt.Println("handleUiSignIn verifier: " + verifier) c.Cookie(&fiber.Cookie{
fmt.Println("handleUiSignIn challenge: " + challenge) Name: "jade-edgedb-pkce-verifier",
Value: verifier,
cookie := new(fiber.Cookie) HTTPOnly: true,
cookie.Name = "jade-edgedb-pkce-verifier" Path: "/",
cookie.Value = verifier Secure: true,
cookie.Expires = time.Now().Add(10 * time.Minute) SameSite: "Strict",
c.Cookie(cookie) })
return c.Redirect(fmt.Sprintf("%s/ui/signup?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge), fiber.StatusTemporaryRedirect) return c.Redirect(fmt.Sprintf("%s/ui/signup?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge), fiber.StatusTemporaryRedirect)
} }
func handleUiSignUp(c *fiber.Ctx) error { func handleCallbackSignup(c *fiber.Ctx) error {
verifier, challenge := generatePKCE() 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))
}
fmt.Println("handleUiSignUp verifier: " + verifier) verifier := c.Cookies("jade-edgedb-pkce-verifier")
fmt.Println("handleUiSignUp challenge: " + challenge) 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?")
}
cookie := new(fiber.Cookie) codeExchangeURL := fmt.Sprintf("%s/token?code=%s&verifier=%s", EDGEDB_AUTH_BASE_URL, code, verifier)
cookie.Name = "jade-edgedb-pkce-verifier" resp, err := http.Get(codeExchangeURL)
cookie.Value = verifier if err != nil {
cookie.Expires = time.Now().Add(10 * time.Minute) return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", err.Error()))
c.Cookie(cookie) }
defer resp.Body.Close()
return c.Redirect(fmt.Sprintf("%s/ui/signup?challenge=%s", EDGEDB_AUTH_BASE_URL, challenge), fiber.StatusTemporaryRedirect) if resp.StatusCode != fiber.StatusOK {
body, _ := io.ReadAll(resp.Body)
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", string(body)))
}
var tokenResponse struct {
AuthToken string `json:"auth_token"`
IdentityID string `json:"identity_id"`
}
err = json.NewDecoder(resp.Body).Decode(&tokenResponse)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error decoding auth server response: %s", err.Error()))
}
c.Cookie(&fiber.Cookie{
Name: "jade-edgedb-auth-token",
Value: tokenResponse.AuthToken,
HTTPOnly: true,
Path: "/",
Secure: true,
SameSite: "Strict",
})
// Create a new User and attach the identity
var identityUUID edgedb.UUID
identityUUID, err = edgedb.ParseUUID(tokenResponse.IdentityID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("Error in edgedb.ParseUUID: in handleCallbackSignup: %s", err.Error()))
}
err = edgeClient.Execute(edgeCtx, `
INSERT User {
setting := (
INSERT Setting {
default_model := "gpt-3.5-turbo"
}
),
identity := (SELECT ext::auth::Identity FILTER .id = <uuid>$0)
}
`, identityUUID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("Error in edgedb.QuerySingle: in handleCallbackSignup: %s", err.Error()))
}
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": tokenResponse.AuthToken})
return c.Redirect("/", fiber.StatusTemporaryRedirect)
} }
func handleCallback(c *fiber.Ctx) error { func handleCallback(c *fiber.Ctx) error {
@ -78,11 +122,7 @@ func handleCallback(c *fiber.Ctx) error {
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?") 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?")
} }
fmt.Println("handleCallback code: " + code)
fmt.Println("handleCallback verifier: " + verifier)
codeExchangeURL := fmt.Sprintf("%s/token?code=%s&verifier=%s", EDGEDB_AUTH_BASE_URL, code, verifier) codeExchangeURL := fmt.Sprintf("%s/token?code=%s&verifier=%s", EDGEDB_AUTH_BASE_URL, code, verifier)
fmt.Println("codeExchangeURL: " + codeExchangeURL)
resp, err := http.Get(codeExchangeURL) resp, err := http.Get(codeExchangeURL)
if err != nil { if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", err.Error())) return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("Error from the auth server: %s", err.Error()))
@ -103,7 +143,7 @@ func handleCallback(c *fiber.Ctx) error {
} }
c.Cookie(&fiber.Cookie{ c.Cookie(&fiber.Cookie{
Name: "edgedb-auth-token", Name: "jade-edgedb-auth-token",
Value: tokenResponse.AuthToken, Value: tokenResponse.AuthToken,
HTTPOnly: true, HTTPOnly: true,
Path: "/", Path: "/",
@ -111,8 +151,13 @@ func handleCallback(c *fiber.Ctx) error {
SameSite: "Strict", SameSite: "Strict",
}) })
fmt.Println("Login successful") edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": tokenResponse.AuthToken})
fmt.Println("edgedb-auth-token: " + tokenResponse.AuthToken)
return c.SendStatus(fiber.StatusNoContent) return c.Redirect("/", fiber.StatusTemporaryRedirect)
}
func handleSignOut(c *fiber.Ctx) error {
c.ClearCookie("jade-edgedb-auth-token")
edgeClient = edgeClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": ""})
return c.Redirect("/", fiber.StatusTemporaryRedirect)
} }

34
main.go
View File

@ -1,8 +1,6 @@
package main package main
import ( import (
"fmt"
"github.com/flosch/pongo2" "github.com/flosch/pongo2"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
@ -32,24 +30,26 @@ func main() {
// Add static files // Add static files
app.Static("/", "./static") app.Static("/", "./static")
// Add routes // Main routes
app.Get("/", ChatPageHandler) // Complete chat page app.Get("/", ChatPageHandler)
app.Post("/requestMultipleMessages", RequestMultipleMessages) // Request multiple messages app.Get("/loadChat", LoadChatHandler)
app.Get("/loadChat", LoadChatHandler) // Load chat
app.Post("/deleteMessage", DeleteMessageHandler) // Delete message // Chat routes
app.Get("/loadModelSelection", LoadModelSelectionHandler) // Load model selection app.Post("/requestMultipleMessages", RequestMultipleMessages)
app.Get("/loadUsageKPI", LoadUsageKPIHandler) // Load usage KPI app.Post("/deleteMessage", DeleteMessageHandler)
app.Get("/generateMultipleMessages", GenerateMultipleMessages) // Generate multiple messages app.Get("/generateMultipleMessages", GenerateMultipleMessages)
app.Get("/messageContent", GetMessageContentHandler) app.Get("/messageContent", GetMessageContentHandler)
app.Get("/signin", handleUiSignIn) // Popovers
app.Get("/signup", handleUiSignUp) app.Get("/loadModelSelection", LoadModelSelectionHandler)
app.Get("/callback", handleCallback) app.Get("/loadUsageKPI", LoadUsageKPIHandler)
// Authentication
app.Get("/signin", handleUiSignIn)
app.Get("/signout", handleSignOut)
app.Get("/callback", handleCallback)
app.Get("/callbackSignup", handleCallbackSignup)
app.Get("test", func(c *fiber.Ctx) error {
fmt.Println("test")
return c.SendString("")
})
// Start server // Start server
app.Listen(":8080") app.Listen(":8080")
} }

View File

@ -1,23 +1,10 @@
.my-indicator { body,
display: none; html {
} height: 100%;
margin: 0;
.htmx-request .my-indicator {
display: inline;
}
.htmx-request.my-indicator {
display: inline;
}
svg text {
font-family: 'Russo One', sans-serif;
text-transform: uppercase;
fill: #000;
stroke: #000;
font-size: 240px;
} }
/* Stuff for message boxes */
.message-content { .message-content {
background-color: #303030; background-color: #303030;
border-radius: 5px; border-radius: 5px;
@ -28,6 +15,7 @@ svg text {
overflow-x: auto; overflow-x: auto;
white-space: pre; white-space: pre;
max-width: 662px; max-width: 662px;
position: relative;
} }
#chat-messages .message-content pre code { #chat-messages .message-content pre code {
@ -36,17 +24,23 @@ svg text {
white-space: inherit; white-space: inherit;
} }
.copy-button {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
border: none;
border-radius: 5px;
padding: 5px 10px;
}
.content { .content {
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
color: #ffffff; color: #ffffff;
} }
.content pre { /* Style for the overall chat container */
font-family: monospace;
background-color: #363636 !important;
border-radius: 3px;
}
#chat-messages { #chat-messages {
max-width: 780px; max-width: 780px;
@ -55,23 +49,13 @@ svg text {
margin-bottom: 180px; margin-bottom: 180px;
} }
#chat-input-form { /* Primary color */
max-width: 900px;
margin: auto;
width: 98%;
}
:root { :root {
--bulma-body-background-color: #202020; --bulma-body-background-color: #202020;
--bulma-primary: #126d0f; --bulma-primary: #126d0f;
} }
body, /* Chat input stuff */
html {
height: 100%;
margin: 0;
}
.chat-input-container { .chat-input-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -106,4 +90,26 @@ html {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
}
/* Indicator */
.my-indicator {
display: none;
}
.htmx-request .my-indicator {
display: inline;
}
.htmx-request.my-indicator {
display: inline;
}
/* Logo */
svg text {
font-family: 'Russo One', sans-serif;
text-transform: uppercase;
fill: #000;
stroke: #000;
font-size: 240px;
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"regexp"
"strings" "strings"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
@ -19,7 +20,22 @@ func markdownToHTML(markdownText string) string {
panic(err) // Handle the error appropriately panic(err) // Handle the error appropriately
} }
return buf.String() return addCopyButtonsToCode(buf.String())
}
func addCopyButtonsToCode(htmlContent string) string {
buttonHTML := `<button class="copy-button" onclick="copyCode(this)"><i class="fa-solid fa-copy"></i></button>`
// Regular expression pattern to match <pre> elements and insert the button right before <code>
pattern := `(<pre[^>]*>)`
// Compile the regular expression
re := regexp.MustCompile(pattern)
// Replace each matched <pre> element with the updated HTML
updatedHTML := re.ReplaceAllString(htmlContent, "$1"+buttonHTML)
return updatedHTML
} }
func model2Icon(model string) string { func model2Icon(model string) string {

View File

@ -12,12 +12,15 @@
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item"> <div class="navbar-item">
<div class="buttons"> <div class="buttons">
{% if IsLogin %}
<a class="button is-light is-small" href="/signout">
Log out
</a>
{% else %}
<a class="button is-light is-small" href="/signin"> <a class="button is-light is-small" href="/signin">
Log in Log in
</a> </a>
<a class="button is-primary" href="/signup"> {% endif %}
<strong>Sign up</strong>
</a>
</div> </div>
</div> </div>
</div> </div>