diff options
| author | adamdottv <[email protected]> | 2025-05-28 12:35:20 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-28 15:36:35 -0500 |
| commit | 15d21bf04acd6af75df97918f66df866c239b248 (patch) | |
| tree | 16ac93b0c686faaf3b21700771b082b6bc26f098 /internal/tui | |
| parent | 5e738ce7d3b66b88a407a1bcc53a5169d0f4a904 (diff) | |
| download | opencode-15d21bf04acd6af75df97918f66df866c239b248.tar.gz opencode-15d21bf04acd6af75df97918f66df866c239b248.zip | |
wip: refactoring tui
Diffstat (limited to 'internal/tui')
| -rw-r--r-- | internal/tui/components/chat/messages.go | 72 | ||||
| -rw-r--r-- | internal/tui/page/chat.go | 1 | ||||
| -rw-r--r-- | internal/tui/state/state.go | 3 | ||||
| -rw-r--r-- | internal/tui/tui.go | 101 |
4 files changed, 94 insertions, 83 deletions
diff --git a/internal/tui/components/chat/messages.go b/internal/tui/components/chat/messages.go index 613c4169a..681606b9f 100644 --- a/internal/tui/components/chat/messages.go +++ b/internal/tui/components/chat/messages.go @@ -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. diff --git a/internal/tui/page/chat.go b/internal/tui/page/chat.go index e4fb62caf..a1561b6be 100644 --- a/internal/tui/page/chat.go +++ b/internal/tui/page/chat.go @@ -201,6 +201,7 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te status.Error(err.Error()) return nil } + return tea.Batch(cmds...) } diff --git a/internal/tui/state/state.go b/internal/tui/state/state.go index 83745d6f7..d2125b930 100644 --- a/internal/tui/state/state.go +++ b/internal/tui/state/state.go @@ -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 +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index ed3a94f76..939607678 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -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, - ) - - // Extract tool names - var toolNames []string - for _, tool := range allTools { - toolNames = append(toolNames, tool.Info().Name) - } + // Get primary agent tools (which already include MCP tools) + allTools := agent.PrimaryAgentTools( + app.Permissions, + app.Sessions, + app.Messages, + app.History, + app.LSPClients, + ) - return toolNames + // Extract tool names + var toolNames []string + for _, tool := range allTools { + toolNames = append(toolNames, tool.Info().Name) + } + + 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{ |
