From 5e738ce7d3b66b88a407a1bcc53a5169d0f4a904 Mon Sep 17 00:00:00 2001 From: adamdottv <2363879+adamdottv@users.noreply.github.com> Date: Wed, 28 May 2025 10:12:08 -0500 Subject: wip: refactoring tui --- internal/tui/components/chat/messages.go | 50 +++++++++++++++++++++++++ internal/tui/components/chat/sidebar.go | 61 +++++++++++++++++-------------- internal/tui/components/logs/table.go | 13 +++---- internal/tui/tui.go | 63 +++++++++++++++++++++++--------- 4 files changed, 134 insertions(+), 53 deletions(-) (limited to 'internal/tui') diff --git a/internal/tui/components/chat/messages.go b/internal/tui/components/chat/messages.go index d6f252aad..613c4169a 100644 --- a/internal/tui/components/chat/messages.go +++ b/internal/tui/components/chat/messages.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "strings" "time" "github.com/charmbracelet/bubbles/key" @@ -155,6 +156,55 @@ 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) diff --git a/internal/tui/components/chat/sidebar.go b/internal/tui/components/chat/sidebar.go index 973b03ef1..c2b392c24 100644 --- a/internal/tui/components/chat/sidebar.go +++ b/internal/tui/components/chat/sidebar.go @@ -10,7 +10,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/config" - "github.com/sst/opencode/internal/diff" + // "github.com/sst/opencode/internal/diff" "github.com/sst/opencode/internal/history" "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/tui/state" @@ -28,39 +28,28 @@ type sidebarCmp struct { } func (m *sidebarCmp) Init() tea.Cmd { - if m.app.History != nil { - ctx := context.Background() - // Subscribe to file events - filesCh := m.app.History.Subscribe(ctx) - - // Initialize the modified files map - m.modFiles = make(map[string]struct { - additions int - removals int - }) - - // Load initial files and calculate diffs - m.loadModifiedFiles(ctx) - - // Return a command that will send file events to the Update method - return func() tea.Msg { - return <-filesCh - } - } + // TODO: History service not implemented in API yet + // Initialize the modified files map + m.modFiles = make(map[string]struct { + additions int + removals int + }) return nil } func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { + switch msg.(type) { case state.SessionSelectedMsg: - ctx := context.Background() - m.loadModifiedFiles(ctx) + // TODO: History service not implemented in API yet + // ctx := context.Background() + // m.loadModifiedFiles(ctx) case pubsub.Event[history.File]: - if msg.Payload.SessionID == m.app.CurrentSession.ID { - // Process the individual file change instead of reloading all files - ctx := context.Background() - m.processFileChanges(ctx, msg.Payload) - } + // TODO: History service not implemented in API yet + // if msg.Payload.SessionID == m.app.CurrentSession.ID { + // // Process the individual file change instead of reloading all files + // ctx := context.Background() + // m.processFileChanges(ctx, msg.Payload) + // } } return m, nil } @@ -224,6 +213,9 @@ func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { return } + // TODO: History service not implemented in API yet + return + /* // Get all latest files for this session latestFiles, err := m.app.History.ListLatestSessionFiles(ctx, m.app.CurrentSession.ID) if err != nil { @@ -235,6 +227,7 @@ func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { if err != nil { return } + */ // Clear the existing map to rebuild it m.modFiles = make(map[string]struct { @@ -242,6 +235,7 @@ func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { removals int }) + /* // Process each latest file for _, file := range latestFiles { // Skip if this is the initial version (no changes to show) @@ -286,9 +280,13 @@ func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { } } } + */ } func (m *sidebarCmp) processFileChanges(ctx context.Context, file history.File) { + // TODO: History service not implemented in API yet + return + /* // Skip if this is the initial version (no changes to show) if file.Version == history.InitialVersion { return @@ -327,16 +325,22 @@ func (m *sidebarCmp) processFileChanges(ctx context.Context, file history.File) displayPath := getDisplayPath(file.Path) delete(m.modFiles, displayPath) } + */ } // Helper function to find the initial version of a file func (m *sidebarCmp) findInitialVersion(ctx context.Context, path string) (history.File, error) { + // TODO: History service not implemented in API yet + return history.File{}, fmt.Errorf("history service not implemented") + /* // Get all versions of this file for the session fileVersions, err := m.app.History.ListBySession(ctx, m.app.CurrentSession.ID) if err != nil { return history.File{}, err } + */ + /* // Find the initial version for _, v := range fileVersions { if v.Path == path && v.Version == history.InitialVersion { @@ -345,6 +349,7 @@ func (m *sidebarCmp) findInitialVersion(ctx context.Context, path string) (histo } return history.File{}, fmt.Errorf("initial version not found") + */ } // Helper function to get the display path for a file diff --git a/internal/tui/components/logs/table.go b/internal/tui/components/logs/table.go index e85009205..1fc1daa38 100644 --- a/internal/tui/components/logs/table.go +++ b/internal/tui/components/logs/table.go @@ -1,7 +1,8 @@ package logs import ( - "context" + // "context" + "fmt" "log/slog" "github.com/charmbracelet/bubbles/key" @@ -41,18 +42,16 @@ func (i *tableCmp) Init() tea.Cmd { func (i *tableCmp) fetchLogs() tea.Cmd { return func() tea.Msg { - ctx := context.Background() + // ctx := context.Background() var logs []logging.Log var err error // Limit the number of logs to improve performance const logLimit = 100 - if i.app.CurrentSession.ID == "" { - logs, err = i.app.Logs.ListAll(ctx, logLimit) - } else { - logs, err = i.app.Logs.ListBySession(ctx, i.app.CurrentSession.ID) - } + // TODO: Logs service not implemented in API yet + logs = []logging.Log{} + err = fmt.Errorf("logs service not implemented") if err != nil { slog.Error("Failed to fetch logs", "error", err) diff --git a/internal/tui/tui.go b/internal/tui/tui.go index ebed1182a..ed3a94f76 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -2,7 +2,7 @@ package tui import ( "context" - "fmt" + // "fmt" "log/slog" "strings" @@ -13,7 +13,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/llm/agent" "github.com/sst/opencode/internal/logging" "github.com/sst/opencode/internal/message" "github.com/sst/opencode/internal/permission" @@ -28,6 +28,7 @@ import ( "github.com/sst/opencode/internal/tui/page" "github.com/sst/opencode/internal/tui/state" "github.com/sst/opencode/internal/tui/util" + "github.com/sst/opencode/pkg/client" ) type keyMap struct { @@ -251,17 +252,18 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.showPermissions = true return a, a.permissions.SetPermissions(msg.Payload) case dialog.PermissionResponseMsg: - var cmd tea.Cmd - switch msg.Action { - case dialog.PermissionAllow: - a.app.Permissions.Grant(context.Background(), msg.Permission) - case dialog.PermissionAllowForSession: - a.app.Permissions.GrantPersistant(context.Background(), msg.Permission) - case dialog.PermissionDeny: - a.app.Permissions.Deny(context.Background(), msg.Permission) - } + // TODO: Permissions service not implemented in API yet + // var cmd tea.Cmd + // switch msg.Action { + // case dialog.PermissionAllow: + // a.app.Permissions.Grant(context.Background(), msg.Permission) + // case dialog.PermissionAllowForSession: + // a.app.Permissions.GrantPersistant(context.Background(), msg.Permission) + // case dialog.PermissionDeny: + // a.app.Permissions.Deny(context.Background(), msg.Permission) + // } a.showPermissions = false - return a, cmd + return a, nil case page.PageChangeMsg: return a, a.moveToPage(msg.ID) @@ -280,6 +282,25 @@ 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) + } + } + } + return a, nil case dialog.CloseQuitMsg: a.showQuit = false @@ -321,13 +342,15 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case dialog.ModelSelectedMsg: a.showModelDialog = false - model, err := a.app.PrimaryAgent.Update(config.AgentPrimary, msg.Model.ID) - if err != nil { - status.Error(err.Error()) - return a, nil - } + // TODO: Agent model update not implemented in API yet + // model, err := a.app.PrimaryAgent.Update(config.AgentPrimary, msg.Model.ID) + // if err != nil { + // status.Error(err.Error()) + // return a, nil + // } - status.Info(fmt.Sprintf("Model changed to %s", model.Name)) + // status.Info(fmt.Sprintf("Model changed to %s", model.Name)) + status.Info("Model selection not implemented in API yet") return a, nil case dialog.ShowInitDialogMsg: @@ -707,6 +730,9 @@ func (a *appModel) RegisterCommand(cmd dialog.Command) { // getAvailableToolNames returns a list of all available tool names 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, @@ -723,6 +749,7 @@ func getAvailableToolNames(app *app.App) []string { } return toolNames + */ } func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd { -- cgit v1.2.3