Hello everyone,
in part 1 i made simple server side clock https://dev.to/blinkinglight/golang-data-star-1o53/
and now decided to write more complex things using https://nats.io and https://data-star.dev -
Chat bot which returns what you wrote to it:
some golang code:
package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/blinkinglight/chat-data-star/web/views/chatview"
"github.com/delaneyj/datastar"
"github.com/delaneyj/toolbelt"
"github.com/delaneyj/toolbelt/embeddednats"
"github.com/go-chi/chi/v5"
"github.com/gorilla/sessions"
"github.com/nats-io/nats.go"
)
func SetupChat(router chi.Router, session sessions.Store, ns *embeddednats.Server) error {
type Message struct {
Message string `json:"message"`
Sender string `json:"sender"`
}
nc, err := ns.Client()
if err != nil {
return err
}
nc.Subscribe("chat.>", func(msg *nats.Msg) {
var message = Message{}
err := json.Unmarshal(msg.Data, &message)
if err != nil {
log.Printf("%v", err)
return
}
if message.Sender != "bot" {
// do something with user message and send back response
nc.Publish("chat."+message.Sender, []byte(`{"message":"hello, i am bot. You send me: `+message.Message+`", "sender":"bot"}`))
}
})
_ = nc
chatIndex := func(w http.ResponseWriter, r *http.Request) {
_, err := upsertSessionID(session, r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
chatview.Index().Render(r.Context(), w)
}
chatMessage := func(w http.ResponseWriter, r *http.Request) {
id, err := upsertSessionID(session, r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var state = Message{}
err = datastar.BodyUnmarshal(r, &state)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
state.Sender = id
b, err := json.Marshal(state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
nc.Publish("chat."+id, b)
sse := datastar.NewSSE(w, r)
_ = sse
}
chatMessages := func(w http.ResponseWriter, r *http.Request) {
id, err := upsertSessionID(session, r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var ch = make(chan *nats.Msg)
sub, err := nc.Subscribe("chat."+id, func(msg *nats.Msg) {
ch <- msg
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer close(ch)
defer sub.Unsubscribe()
sse := datastar.NewSSE(w, r)
for {
select {
case <-r.Context().Done():
return
case msg := <-ch:
var message = Message{}
err := json.Unmarshal(msg.Data, &message)
if err != nil {
datastar.Error(sse, err)
return
}
if message.Sender == "bot" {
datastar.RenderFragmentTempl(sse, chatview.Bot(message.Message), datastar.WithMergeAppend(), datastar.WithQuerySelector("#chatbox"))
} else {
datastar.RenderFragmentTempl(sse, chatview.Me(message.Message), datastar.WithMergeAppend(), datastar.WithQuerySelector("#chatbox"))
}
}
}
}
router.Get("/chat", chatIndex)
router.Post("/chat", chatMessage)
router.Get("/messages", chatMessages)
return nil
}
func upsertSessionID(store sessions.Store, r *http.Request, w http.ResponseWriter) (string, error) {
sess, err := store.Get(r, "chatters")
if err != nil {
return "", fmt.Errorf("failed to get session: %w", err)
}
id, ok := sess.Values["id"].(string)
if !ok {
id = toolbelt.NextEncodedID()
sess.Values["id"] = id
if err := sess.Save(r, w); err != nil {
return "", fmt.Errorf("failed to save session: %w", err)
}
}
return id, nil
}
and template
package chatview
import "github.com/blinkinglight/chat-data-star/web/views/layoutview"
templ Index() {
@layoutview.Main() {
<!-- component -->
<div class="fixed bottom-0 right-0 mb-4 mr-4">
<button id="open-chat" class="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition duration-300 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
Chat with Admin Bot
</button>
</div>
<div id="chat-container" class="hidden fixed bottom-16 right-4 w-96">
<div class="bg-white shadow-md rounded-lg max-w-lg w-full">
<div class="p-4 border-b bg-blue-500 text-white rounded-t-lg flex justify-between items-center">
<p class="text-lg font-semibold">Admin Bot</p>
<button id="close-chat" class="text-gray-300 hover:text-gray-400 focus:outline-none focus:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="chatbox" class="p-4 h-80 overflow-y-auto" data-on-load="$$get('/messages')" data-store="{ message: '' }">
<!-- Chat messages will be displayed here -->
</div>
<div class="p-4 border-t flex">
<input data-model="message" id="user-input" type="text" placeholder="Type a message" class="w-full px-3 py-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"/>
<button data-on-keydown.window.key_enter="$$post('/chat'); $message=''" data-on-click="$$post('/chat'); $message=''" id="send-button" class="bg-blue-500 text-white px-4 py-2 rounded-r-md hover:bg-blue-600 transition duration-300">Send</button>
</div>
</div>
</div>
<script>
const chatbox = document.getElementById("chatbox");
const chatContainer = document.getElementById("chat-container");
const userInput = document.getElementById("user-input");
const sendButton = document.getElementById("send-button");
const openChatButton = document.getElementById("open-chat");
const closeChatButton = document.getElementById("close-chat");
let isChatboxOpen = true; // Set the initial state to open
function toggleChatbox() {
chatContainer.classList.toggle("hidden");
isChatboxOpen = !isChatboxOpen; // Toggle the state
}
openChatButton.addEventListener("click", toggleChatbox);
closeChatButton.addEventListener("click", toggleChatbox);
toggleChatbox();
</script>
}
}
templ Me(message string) {
<div class="mb-2 text-right">
<p class="bg-blue-500 text-white rounded-lg py-2 px-4 inline-block">{ message }</p>
</div>
}
templ Bot(message string) {
<div class="mb-2">
<p class="bg-gray-200 text-gray-700 rounded-lg py-2 px-4 inline-block">{ message }</p>
</div>
}
you can find working example at https://github.com/blinkinglight/chat-data-star
Top comments (0)