wip: refactoring tui

This commit is contained in:
adamdottv
2025-05-28 12:35:20 -05:00
parent 5e738ce7d3
commit 15d21bf04a
10 changed files with 135 additions and 271 deletions

View File

@@ -2,9 +2,9 @@ package chat
import (
"context"
"encoding/json"
"fmt"
"math"
"strings"
"time"
"github.com/charmbracelet/bubbles/key"
@@ -156,55 +156,6 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
}
case app.StorageWriteMsg:
// Handle storage write events from the TypeScript backend
keyParts := strings.Split(msg.Key, "/")
if len(keyParts) >= 4 && keyParts[0] == "session" && keyParts[1] == "message" {
sessionID := keyParts[2]
if sessionID == m.app.CurrentSession.ID {
// Convert storage message to internal format
convertedMsg, err := app.ConvertStorageMessage(msg.Content, sessionID)
if err != nil {
status.Error("Failed to convert message: " + err.Error())
return m, nil
}
// Check if message exists
messageExists := false
messageIndex := -1
for i, v := range m.messages {
if v.ID == convertedMsg.ID {
messageExists = true
messageIndex = i
break
}
}
needsRerender := false
if messageExists {
// Update existing message
m.messages[messageIndex] = *convertedMsg
delete(m.cachedContent, convertedMsg.ID)
needsRerender = true
} else {
// Add new message
if len(m.messages) > 0 {
lastMsgID := m.messages[len(m.messages)-1].ID
delete(m.cachedContent, lastMsgID)
}
m.messages = append(m.messages, *convertedMsg)
delete(m.cachedContent, m.currentMsgID)
m.currentMsgID = convertedMsg.ID
needsRerender = true
}
if needsRerender {
m.renderView()
m.viewport.GotoBottom()
}
}
}
}
spinner, cmd := m.spinner.Update(msg)
@@ -293,20 +244,33 @@ func (m *messagesCmp) renderView() {
)
}
temp, _ := json.MarshalIndent(m.app.State, "", " ")
m.viewport.SetContent(
baseStyle.
Width(m.width).
Render(
lipgloss.JoinVertical(
lipgloss.Top,
messages...,
),
string(temp),
// lipgloss.JoinVertical(
// lipgloss.Top,
// messages...,
// ),
),
)
}
func (m *messagesCmp) View() string {
baseStyle := styles.BaseStyle()
return baseStyle.
Width(m.width).
Render(
lipgloss.JoinVertical(
lipgloss.Top,
m.viewport.View(),
m.working(),
m.help(),
),
)
if m.rendering {
return baseStyle.

View File

@@ -201,6 +201,7 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te
status.Error(err.Error())
return nil
}
return tea.Batch(cmds...)
}

View File

@@ -5,3 +5,6 @@ import "github.com/sst/opencode/internal/session"
type SessionSelectedMsg = *session.Session
type SessionClearedMsg struct{}
type CompactSessionMsg struct{}
type StateUpdatedMsg struct {
State map[string]any
}

View File

@@ -2,6 +2,7 @@ package tui
import (
"context"
"encoding/json"
// "fmt"
"log/slog"
"strings"
@@ -13,6 +14,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/config"
// "github.com/sst/opencode/internal/llm/agent"
"github.com/sst/opencode/internal/logging"
"github.com/sst/opencode/internal/message"
@@ -251,6 +253,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case pubsub.Event[permission.PermissionRequest]:
a.showPermissions = true
return a, a.permissions.SetPermissions(msg.Payload)
case dialog.PermissionResponseMsg:
// TODO: Permissions service not implemented in API yet
// var cmd tea.Cmd
@@ -282,25 +285,44 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.CurrentSession = &msg.Payload
}
}
// Handle SSE events from the TypeScript backend
case *client.EventStorageWrite:
// Process storage write events
processedMsg := app.ProcessSSEEvent(msg)
if storageMsg, ok := processedMsg.(app.StorageWriteMsg); ok {
// Forward to the appropriate page/component based on key
keyParts := strings.Split(storageMsg.Key, "/")
if len(keyParts) >= 3 && keyParts[0] == "session" {
if keyParts[1] == "message" {
// This is a message update, forward to the chat page
return a.updateAllPages(storageMsg)
} else if keyParts[1] == "info" {
// This is a session info update
return a.updateAllPages(storageMsg)
slog.Debug("Received SSE event", "key", msg.Key, "content", msg.Content)
// Create a deep copy of the state to avoid mutation issues
newState := deepCopyState(a.app.State)
// Split the key and traverse/create the nested structure
splits := strings.Split(msg.Key, "/")
current := newState
for i, part := range splits {
if i == len(splits)-1 {
// Last part - set the value
current[part] = msg.Content
} else {
// Intermediate parts - ensure map exists
if _, exists := current[part]; !exists {
current[part] = make(map[string]any)
}
// Navigate to the next level
nextLevel, ok := current[part].(map[string]any)
if !ok {
// If it's not a map, replace it with a new map
current[part] = make(map[string]any)
nextLevel = current[part].(map[string]any)
}
current = nextLevel
}
}
return a, nil
// Update the app state
a.app.State = newState
// Trigger UI update by updating all pages with the new state
return a.updateAllPages(state.StateUpdatedMsg{State: newState})
case dialog.CloseQuitMsg:
a.showQuit = false
@@ -733,22 +755,22 @@ func getAvailableToolNames(app *app.App) []string {
// TODO: Tools not implemented in API yet
return []string{"Tools not available in API mode"}
/*
// Get primary agent tools (which already include MCP tools)
allTools := agent.PrimaryAgentTools(
app.Permissions,
app.Sessions,
app.Messages,
app.History,
app.LSPClients,
)
// Get primary agent tools (which already include MCP tools)
allTools := agent.PrimaryAgentTools(
app.Permissions,
app.Sessions,
app.Messages,
app.History,
app.LSPClients,
)
// Extract tool names
var toolNames []string
for _, tool := range allTools {
toolNames = append(toolNames, tool.Info().Name)
}
// Extract tool names
var toolNames []string
for _, tool := range allTools {
toolNames = append(toolNames, tool.Info().Name)
}
return toolNames
return toolNames
*/
}
@@ -976,6 +998,27 @@ func (a appModel) View() string {
return appView
}
// deepCopyState creates a deep copy of a map[string]any
func deepCopyState(src map[string]any) map[string]any {
if src == nil {
return nil
}
dst := make(map[string]any, len(src))
for k, v := range src {
switch val := v.(type) {
case map[string]any:
// Recursively copy nested maps
dst[k] = deepCopyState(val)
default:
// For other types, just copy the value
// Note: This is still a shallow copy for slices/arrays
dst[k] = v
}
}
return dst
}
func New(app *app.App) tea.Model {
startPage := page.ChatPage
model := &appModel{