summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-05 13:10:15 -0500
committeradamdottv <[email protected]>2025-06-05 13:10:20 -0500
commit167aea6aaf445eed0598c41ae3390abb5e475f44 (patch)
tree240843adfe45da3a791d34c1148b254458d245fc
parent142056e9afa6913a44e65bf109eac0c857b49b02 (diff)
downloadopencode-167aea6aaf445eed0598c41ae3390abb5e475f44.tar.gz
opencode-167aea6aaf445eed0598c41ae3390abb5e475f44.zip
wip: refactoring tui
-rw-r--r--packages/tui/internal/components/chat/cache.go88
-rw-r--r--packages/tui/internal/components/chat/messages.go26
2 files changed, 112 insertions, 2 deletions
diff --git a/packages/tui/internal/components/chat/cache.go b/packages/tui/internal/components/chat/cache.go
new file mode 100644
index 000000000..5219e7092
--- /dev/null
+++ b/packages/tui/internal/components/chat/cache.go
@@ -0,0 +1,88 @@
+package chat
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "sync"
+
+ "github.com/sst/opencode/pkg/client"
+)
+
+// MessageCache caches rendered messages to avoid re-rendering
+type MessageCache struct {
+ mu sync.RWMutex
+ cache map[string]string
+}
+
+// NewMessageCache creates a new message cache
+func NewMessageCache() *MessageCache {
+ return &MessageCache{
+ cache: make(map[string]string),
+ }
+}
+
+// generateKey creates a unique key for a message based on its content and rendering parameters
+func (c *MessageCache) generateKey(msg client.MessageInfo, width int, showToolMessages bool, appInfo client.AppInfo) string {
+ // Create a hash of the message content and rendering parameters
+ h := sha256.New()
+
+ // Include message ID and role
+ h.Write(fmt.Appendf(nil, "%s:%s", msg.Id, msg.Role))
+
+ // Include timestamp
+ h.Write(fmt.Appendf(nil, ":%f", msg.Metadata.Time.Created))
+
+ // Include width and showToolMessages flag
+ h.Write(fmt.Appendf(nil, ":%d:%t", width, showToolMessages))
+
+ // Include app path for relative path calculations
+ h.Write([]byte(appInfo.Path.Root))
+
+ // Include message parts
+ for _, part := range msg.Parts {
+ h.Write(fmt.Appendf(nil, ":%v", part))
+ }
+
+ // Include tool metadata if present
+ for toolID, metadata := range msg.Metadata.Tool {
+ h.Write(fmt.Appendf(nil, ":%s:%v", toolID, metadata))
+ }
+
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+// Get retrieves a cached rendered message
+func (c *MessageCache) Get(msg client.MessageInfo, width int, showToolMessages bool, appInfo client.AppInfo) (string, bool) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ key := c.generateKey(msg, width, showToolMessages, appInfo)
+ content, exists := c.cache[key]
+ return content, exists
+}
+
+// Set stores a rendered message in the cache
+func (c *MessageCache) Set(msg client.MessageInfo, width int, showToolMessages bool, appInfo client.AppInfo, content string) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ key := c.generateKey(msg, width, showToolMessages, appInfo)
+ c.cache[key] = content
+}
+
+// Clear removes all entries from the cache
+func (c *MessageCache) Clear() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cache = make(map[string]string)
+}
+
+// Size returns the number of cached entries
+func (c *MessageCache) Size() int {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return len(c.cache)
+}
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 202f7adb6..f39884e7a 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -25,6 +25,7 @@ type messagesCmp struct {
rendering bool
attachments viewport.Model
showToolMessages bool
+ cache *MessageCache
}
type renderFinishedMsg struct{}
type ToggleToolMessagesMsg struct{}
@@ -63,6 +64,7 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case dialog.ThemeChangedMsg:
+ m.cache.Clear()
m.renderView()
return m, nil
case ToggleToolMessagesMsg:
@@ -70,9 +72,13 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.renderView()
return m, nil
case state.SessionSelectedMsg:
+ // Clear cache when switching sessions
+ m.cache.Clear()
cmd := m.Reload()
return m, cmd
case state.SessionClearedMsg:
+ // Clear cache when session is cleared
+ m.cache.Clear()
cmd := m.Reload()
return m, cmd
case tea.KeyMsg:
@@ -103,12 +109,23 @@ func (m *messagesCmp) renderView() {
messages := make([]string, 0)
for _, msg := range m.app.Messages {
+ var content string
+ var cached bool
+
switch msg.Role {
case client.User:
- content := renderUserMessage(m.app.Info.User, msg, m.width)
+ content, cached = m.cache.Get(msg, m.width, m.showToolMessages, *m.app.Info)
+ if !cached {
+ content = renderUserMessage(m.app.Info.User, msg, m.width)
+ m.cache.Set(msg, m.width, m.showToolMessages, *m.app.Info, content)
+ }
messages = append(messages, content+"\n")
case client.Assistant:
- content := renderAssistantMessage(msg, m.width, m.showToolMessages, *m.app.Info)
+ content, cached = m.cache.Get(msg, m.width, m.showToolMessages, *m.app.Info)
+ if !cached {
+ content = renderAssistantMessage(msg, m.width, m.showToolMessages, *m.app.Info)
+ m.cache.Set(msg, m.width, m.showToolMessages, *m.app.Info, content)
+ }
messages = append(messages, content+"\n")
}
}
@@ -287,6 +304,10 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
if m.width == width && m.height == height {
return nil
}
+ // Clear cache on resize since width affects rendering
+ if m.width != width {
+ m.cache.Clear()
+ }
m.width = width
m.height = height
m.viewport.Width = width
@@ -338,5 +359,6 @@ func NewMessagesCmp(app *app.App) tea.Model {
spinner: s,
attachments: attachments,
showToolMessages: true,
+ cache: NewMessageCache(),
}
}