Skip to content

Commit dbe86c8

Browse files
committed
Persist chat entries and load history in TUI
1 parent 0321ce3 commit dbe86c8

File tree

2 files changed

+91
-26
lines changed

2 files changed

+91
-26
lines changed

chat/main.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"flag"
66
"fmt"
77
"log"
8+
"log/slog"
89
"net"
910
"os"
1011
"strings"
@@ -95,14 +96,16 @@ func serveHandler(t *kamune.Transport) error {
9596
return nil
9697
}
9798
p.Send(NewMessage(metadata.Timestamp(), b.GetValue()))
98-
go func() {
99-
t.Store().AddChatEntry(
100-
t.SessionID(),
101-
b.GetValue(),
102-
metadata.Timestamp(),
103-
kamune.SenderPeer,
104-
)
105-
}()
99+
if err := t.Store().AddChatEntry(
100+
t.SessionID(),
101+
b.GetValue(),
102+
metadata.Timestamp(),
103+
kamune.SenderPeer,
104+
); err != nil {
105+
slog.Error("failed to persist received chat entry",
106+
slog.String("session_id", t.SessionID()),
107+
slog.Any("error", err))
108+
}
106109
}
107110
}
108111

@@ -185,14 +188,16 @@ func client(addr string) {
185188
return
186189
}
187190
p.Send(NewMessage(metadata.Timestamp(), b.GetValue()))
188-
go func() {
189-
t.Store().AddChatEntry(
190-
t.SessionID(),
191-
b.GetValue(),
192-
metadata.Timestamp(),
193-
kamune.SenderPeer,
194-
)
195-
}()
191+
if err := t.Store().AddChatEntry(
192+
t.SessionID(),
193+
b.GetValue(),
194+
metadata.Timestamp(),
195+
kamune.SenderPeer,
196+
); err != nil {
197+
slog.Error("failed to persist received chat entry",
198+
slog.String("session_id", t.SessionID()),
199+
slog.Any("error", err))
200+
}
196201
}
197202
}
198203

chat/tea.go

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"fmt"
5+
"log/slog"
56
"strings"
67
"time"
78

@@ -19,6 +20,12 @@ type (
1920
errMsg error
2021
)
2122

23+
// historyLoaded is sent once the background goroutine finishes reading prior
24+
// chat entries from the database.
25+
type historyLoaded struct {
26+
messages []string
27+
}
28+
2229
type model struct {
2330
viewport viewport.Model
2431
messages []string
@@ -53,7 +60,7 @@ func initialModel(t *kamune.Transport) model {
5360
ta.ShowLineNumbers = false
5461

5562
vp := viewport.New(30, 5)
56-
vp.SetContent(fmt.Sprintf(`Session ID is %s. Happy Chatting!`, t.SessionID()))
63+
vp.SetContent(fmt.Sprintf(`Session ID is %s. Loading history…`, t.SessionID()))
5764
vp.MouseWheelEnabled = true
5865
vp.Style = lipgloss.NewStyle().
5966
Border(lipgloss.RoundedBorder()).
@@ -74,8 +81,40 @@ func initialModel(t *kamune.Transport) model {
7481
}
7582
}
7683

84+
// loadHistory returns a tea.Cmd that asynchronously reads prior chat entries
85+
// from the database and delivers them as a historyLoaded message.
86+
func loadHistory(t *kamune.Transport, userPrefix, userText, peerPrefix, peerText lipgloss.Style) tea.Cmd {
87+
return func() tea.Msg {
88+
entries, err := t.Store().GetChatHistory(t.SessionID())
89+
if err != nil {
90+
slog.Warn("failed to load chat history",
91+
slog.String("session_id", t.SessionID()),
92+
slog.Any("error", err))
93+
return historyLoaded{}
94+
}
95+
96+
var msgs []string
97+
for _, ent := range entries {
98+
sender := "You"
99+
prefixStyle := userPrefix
100+
textStyle := userText
101+
if ent.Sender != kamune.SenderLocal {
102+
sender = "Peer"
103+
prefixStyle = peerPrefix
104+
textStyle = peerText
105+
}
106+
prefix := fmt.Sprintf("[%s] %s: ", ent.Timestamp.Format(time.DateTime), sender)
107+
msgs = append(msgs, prefixStyle.Render(prefix)+textStyle.Render(string(ent.Data)))
108+
}
109+
return historyLoaded{messages: msgs}
110+
}
111+
}
112+
77113
func (m model) Init() tea.Cmd {
78-
return textarea.Blink
114+
return tea.Batch(
115+
textarea.Blink,
116+
loadHistory(m.transport, m.userPrefix, m.userText, m.peerPrefix, m.peerText),
117+
)
79118
}
80119

81120
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -88,6 +127,25 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
88127
m.viewport, vpCmd = m.viewport.Update(msg)
89128

90129
switch msg := msg.(type) {
130+
case historyLoaded:
131+
header := fmt.Sprintf("Session ID is %s. Happy Chatting!", m.transport.SessionID())
132+
if len(msg.messages) > 0 {
133+
header = fmt.Sprintf("Session ID is %s. Restored %d message(s). Happy Chatting!",
134+
m.transport.SessionID(), len(msg.messages))
135+
// Prepend historical messages before any that arrived while loading.
136+
m.messages = append(msg.messages, m.messages...)
137+
}
138+
content := header
139+
if len(m.messages) > 0 {
140+
content = header + "\n" + strings.Join(m.messages, "\n")
141+
}
142+
m.viewport.SetContent(lipgloss.
143+
NewStyle().
144+
Width(m.viewport.Width).
145+
Render(content),
146+
)
147+
m.viewport.GotoBottom()
148+
91149
case tea.WindowSizeMsg:
92150
m.viewport.Width = msg.Width
93151
m.textarea.SetWidth(msg.Width)
@@ -114,14 +172,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
114172
m.err = err
115173
return m, tiCmd
116174
}
117-
go func() {
118-
m.transport.Store().AddChatEntry(
119-
m.transport.SessionID(),
120-
[]byte(text),
121-
metadata.Timestamp(),
122-
kamune.SenderLocal,
123-
)
124-
}()
175+
if err := m.transport.Store().AddChatEntry(
176+
m.transport.SessionID(),
177+
[]byte(text),
178+
metadata.Timestamp(),
179+
kamune.SenderLocal,
180+
); err != nil {
181+
slog.Error("failed to persist sent chat entry",
182+
slog.String("session_id", m.transport.SessionID()),
183+
slog.Any("error", err))
184+
}
125185
prefix := fmt.Sprintf(
126186
"[%s] You: ",
127187
metadata.Timestamp().Format(time.DateTime),

0 commit comments

Comments
 (0)