diff options
| author | adamdottv <[email protected]> | 2025-05-29 09:42:56 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-29 14:04:48 -0500 |
| commit | 6759674c0f3205155a0737b929a3fd0346a752c4 (patch) | |
| tree | 7f90fcf3283985022536a966b09a18547873464c /internal/tui | |
| parent | a9799136fe68c6ebac8b60ad963521ba159d9ae9 (diff) | |
| download | opencode-6759674c0f3205155a0737b929a3fd0346a752c4.tar.gz opencode-6759674c0f3205155a0737b929a3fd0346a752c4.zip | |
wip: refactoring tui
Diffstat (limited to 'internal/tui')
| -rw-r--r-- | internal/tui/app/app.go | 94 | ||||
| -rw-r--r-- | internal/tui/components/chat/editor.go | 4 | ||||
| -rw-r--r-- | internal/tui/components/chat/message.go | 148 | ||||
| -rw-r--r-- | internal/tui/components/chat/messages.go | 271 | ||||
| -rw-r--r-- | internal/tui/components/chat/sidebar.go | 4 | ||||
| -rw-r--r-- | internal/tui/components/core/status.go | 49 | ||||
| -rw-r--r-- | internal/tui/page/chat.go | 14 | ||||
| -rw-r--r-- | internal/tui/tui.go | 97 |
8 files changed, 346 insertions, 335 deletions
diff --git a/internal/tui/app/app.go b/internal/tui/app/app.go index 10ff74914..e573352f3 100644 --- a/internal/tui/app/app.go +++ b/internal/tui/app/app.go @@ -3,7 +3,6 @@ package app import ( "context" "fmt" - "maps" "sync" "time" @@ -12,7 +11,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/fileutil" - "github.com/sst/opencode/internal/lsp" "github.com/sst/opencode/internal/message" "github.com/sst/opencode/internal/session" "github.com/sst/opencode/internal/status" @@ -23,23 +21,21 @@ import ( ) type App struct { - State map[string]any - - CurrentSession *session.Session - Logs any // TODO: Define LogService interface when needed - Sessions SessionService - Messages MessageService - History any // TODO: Define HistoryService interface when needed - Permissions any // TODO: Define PermissionService interface when needed - Status status.Service - Client *client.ClientWithResponses - Events *client.Client - - PrimaryAgent AgentService - - LSPClients map[string]*lsp.Client - - clientsMutex sync.RWMutex + Client *client.ClientWithResponses + Events *client.Client + State map[string]any + Session *client.SessionInfo + Messages []client.SessionMessage + + CurrentSessionOLD *session.Session + SessionsOLD SessionService + MessagesOLD MessageService + LogsOLD any // TODO: Define LogService interface when needed + HistoryOLD any // TODO: Define HistoryService interface when needed + PermissionsOLD any // TODO: Define PermissionService interface when needed + Status status.Service + + PrimaryAgentOLD AgentService watcherCancelFuncs []context.CancelFunc cancelFuncsMutex sync.Mutex @@ -80,20 +76,20 @@ func New(ctx context.Context) (*App, error) { agentBridge := NewAgentServiceBridge(httpClient) app := &App{ - State: make(map[string]any), - Client: httpClient, - Events: eventClient, - CurrentSession: &session.Session{}, - Sessions: sessionBridge, - Messages: messageBridge, - PrimaryAgent: agentBridge, - Status: status.GetService(), - LSPClients: make(map[string]*lsp.Client), + State: make(map[string]any), + Client: httpClient, + Events: eventClient, + Session: &client.SessionInfo{}, + CurrentSessionOLD: &session.Session{}, + SessionsOLD: sessionBridge, + MessagesOLD: messageBridge, + PrimaryAgentOLD: agentBridge, + Status: status.GetService(), // TODO: These services need API endpoints: - Logs: nil, // logging.GetService(), - History: nil, // history.GetService(), - Permissions: nil, // permission.GetService(), + LogsOLD: nil, // logging.GetService(), + HistoryOLD: nil, // history.GetService(), + PermissionsOLD: nil, // permission.GetService(), } // Initialize theme based on configuration @@ -105,30 +101,28 @@ func New(ctx context.Context) (*App, error) { // Create creates a new session func (a *App) SendChatMessage(ctx context.Context, text string, attachments []message.Attachment) tea.Cmd { var cmds []tea.Cmd - if a.CurrentSession.ID == "" { + if a.Session.Id == "" { resp, err := a.Client.PostSessionCreateWithResponse(ctx) if err != nil { - // return session.Session{}, err + status.Error(err.Error()) + return nil } if resp.StatusCode() != 200 { - // return session.Session{}, fmt.Errorf("failed to create session: %d", resp.StatusCode()) + status.Error(fmt.Sprintf("failed to create session: %d", resp.StatusCode())) + return nil } + info := resp.JSON200 + a.Session = info - // Convert to old session type + // Convert to old session type for backwards compatibility newSession := session.Session{ ID: info.Id, Title: info.Title, CreatedAt: time.Now(), // API doesn't provide this yet UpdatedAt: time.Now(), // API doesn't provide this yet } - - if err != nil { - status.Error(err.Error()) - return nil - } - - a.CurrentSession = &newSession + a.CurrentSessionOLD = &newSession cmds = append(cmds, util.CmdHandler(state.SessionSelectedMsg(&newSession))) } @@ -147,7 +141,7 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []me parts := []client.SessionMessagePart{part} go a.Client.PostSessionChatWithResponse(ctx, client.PostSessionChatJSONRequestBody{ - SessionID: a.CurrentSession.ID, + SessionID: a.Session.Id, Parts: parts, ProviderID: "anthropic", ModelID: "claude-sonnet-4-20250514", @@ -234,18 +228,4 @@ func (app *App) Shutdown() { } app.cancelFuncsMutex.Unlock() app.watcherWG.Wait() - - // Perform additional cleanup for LSP clients - app.clientsMutex.RLock() - clients := make(map[string]*lsp.Client, len(app.LSPClients)) - maps.Copy(clients, app.LSPClients) - app.clientsMutex.RUnlock() - - for name, client := range clients { - shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - if err := client.Shutdown(shutdownCtx); err != nil { - slog.Error("Failed to shutdown LSP client", "name", name, "error", err) - } - cancel() - } } diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go index 3b82c96d3..17fb82c20 100644 --- a/internal/tui/components/chat/editor.go +++ b/internal/tui/components/chat/editor.go @@ -143,7 +143,7 @@ func (m *editorCmp) Init() tea.Cmd { } func (m *editorCmp) send() tea.Cmd { - if m.app.PrimaryAgent.IsSessionBusy(m.app.CurrentSession.ID) { + if m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID) { status.Warn("Agent is working, please wait...") return nil } @@ -217,7 +217,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } if key.Matches(msg, editorMaps.OpenEditor) { - if m.app.PrimaryAgent.IsSessionBusy(m.app.CurrentSession.ID) { + if m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID) { status.Warn("Agent is working, please wait...") return m, nil } diff --git a/internal/tui/components/chat/message.go b/internal/tui/components/chat/message.go index f887337ae..c23644638 100644 --- a/internal/tui/components/chat/message.go +++ b/internal/tui/components/chat/message.go @@ -1,7 +1,6 @@ package chat import ( - "context" "encoding/json" "fmt" "path/filepath" @@ -12,12 +11,12 @@ import ( "github.com/charmbracelet/x/ansi" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/diff" - "github.com/sst/opencode/internal/llm/agent" "github.com/sst/opencode/internal/llm/models" "github.com/sst/opencode/internal/llm/tools" "github.com/sst/opencode/internal/message" "github.com/sst/opencode/internal/tui/styles" "github.com/sst/opencode/internal/tui/theme" + "github.com/sst/opencode/pkg/client" ) type uiMessageType int @@ -33,8 +32,6 @@ const ( type uiMessage struct { ID string messageType uiMessageType - position int - height int content string } @@ -48,7 +45,7 @@ func renderMessage(msg string, isUser bool, isFocused bool, width int, info ...s t := theme.CurrentTheme() style := styles.BaseStyle(). - Width(width - 1). + // Width(width - 1). BorderLeft(true). Foreground(t.TextMuted()). BorderForeground(t.Primary()). @@ -79,28 +76,29 @@ func renderMessage(msg string, isUser bool, isFocused bool, width int, info ...s return rendered } -func renderUserMessage(msg message.Message, isFocused bool, width int, position int) uiMessage { - var styledAttachments []string +func renderUserMessage(msg client.SessionMessage, isFocused bool, width int, position int) uiMessage { + // var styledAttachments []string t := theme.CurrentTheme() baseStyle := styles.BaseStyle() - attachmentStyles := baseStyle. - MarginLeft(1). - Background(t.TextMuted()). - Foreground(t.Text()) - for _, attachment := range msg.BinaryContent() { - file := filepath.Base(attachment.Path) - var filename string - if len(file) > 10 { - filename = fmt.Sprintf(" %s %s...", styles.DocumentIcon, file[0:7]) - } else { - filename = fmt.Sprintf(" %s %s", styles.DocumentIcon, file) - } - styledAttachments = append(styledAttachments, attachmentStyles.Render(filename)) - } + // attachmentStyles := baseStyle. + // MarginLeft(1). + // Background(t.TextMuted()). + // Foreground(t.Text()) + // for _, attachment := range msg.BinaryContent() { + // file := filepath.Base(attachment.Path) + // var filename string + // if len(file) > 10 { + // filename = fmt.Sprintf(" %s %s...", styles.DocumentIcon, file[0:7]) + // } else { + // filename = fmt.Sprintf(" %s %s", styles.DocumentIcon, file) + // } + // styledAttachments = append(styledAttachments, attachmentStyles.Render(filename)) + // } - // Add timestamp info info := []string{} - timestamp := msg.CreatedAt.Local().Format("02 Jan 2006 03:04 PM") + + // Add timestamp info + timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM") username, _ := config.GetUsername() info = append(info, baseStyle. Width(width-1). @@ -109,17 +107,27 @@ func renderUserMessage(msg message.Message, isFocused bool, width int, position ) content := "" - if len(styledAttachments) > 0 { - attachmentContent := baseStyle.Width(width).Render(lipgloss.JoinHorizontal(lipgloss.Left, styledAttachments...)) - content = renderMessage(msg.Content().String(), true, isFocused, width, append(info, attachmentContent)...) - } else { - content = renderMessage(msg.Content().String(), true, isFocused, width, info...) + // if len(styledAttachments) > 0 { + // attachmentContent := baseStyle.Width(width).Render(lipgloss.JoinHorizontal(lipgloss.Left, styledAttachments...)) + // content = renderMessage(msg.Content().String(), true, isFocused, width, append(info, attachmentContent)...) + // } else { + for _, p := range msg.Parts { + part, err := p.ValueByDiscriminator() + if err != nil { + continue //TODO: handle error? + } + + switch part.(type) { + case client.SessionMessagePartText: + textPart := part.(client.SessionMessagePartText) + content = renderMessage(textPart.Text, true, isFocused, width, info...) + } } + // content = renderMessage(msg.Parts, true, isFocused, width, info...) + userMsg := uiMessage{ - ID: msg.ID, + ID: msg.Id, messageType: userMessageType, - position: position, - height: lipgloss.Height(content), content: content, } return userMsg @@ -193,11 +201,11 @@ func renderAssistantMessage( messages = append(messages, uiMessage{ ID: msg.ID, messageType: assistantMessageType, - position: position, - height: lipgloss.Height(content), - content: content, + // position: position, + // height: lipgloss.Height(content), + content: content, }) - position += messages[0].height + // position += messages[0].height position++ // for the space } else if thinking && thinkingContent != "" { // Render the thinking content with timestamp @@ -205,9 +213,9 @@ func renderAssistantMessage( messages = append(messages, uiMessage{ ID: msg.ID, messageType: assistantMessageType, - position: position, - height: lipgloss.Height(content), - content: content, + // position: position, + // height: lipgloss.Height(content), + content: content, }) position += lipgloss.Height(content) position++ // for the space @@ -226,7 +234,7 @@ func renderAssistantMessage( i+1, ) messages = append(messages, toolCallContent) - position += toolCallContent.height + // position += toolCallContent.height position++ // for the space } } @@ -246,8 +254,8 @@ func findToolResponse(toolCallID string, futureMessages []message.Message) *mess func toolName(name string) string { switch name { - case agent.AgentToolName: - return "Task" + // case agent.AgentToolName: + // return "Task" case tools.BashToolName: return "Bash" case tools.EditToolName: @@ -274,8 +282,8 @@ func toolName(name string) string { func getToolAction(name string) string { switch name { - case agent.AgentToolName: - return "Preparing prompt..." + // case agent.AgentToolName: + // return "Preparing prompt..." case tools.BashToolName: return "Building command..." case tools.EditToolName: @@ -363,11 +371,11 @@ func removeWorkingDirPrefix(path string) string { func renderToolParams(paramWidth int, toolCall message.ToolCall) string { params := "" switch toolCall.Name { - case agent.AgentToolName: - var params agent.AgentParams - json.Unmarshal([]byte(toolCall.Input), ¶ms) - prompt := strings.ReplaceAll(params.Prompt, "\n", " ") - return renderParams(paramWidth, prompt) + // case agent.AgentToolName: + // var params agent.AgentParams + // json.Unmarshal([]byte(toolCall.Input), ¶ms) + // prompt := strings.ReplaceAll(params.Prompt, "\n", " ") + // return renderParams(paramWidth, prompt) case tools.BashToolName: var params tools.BashParams json.Unmarshal([]byte(toolCall.Input), ¶ms) @@ -481,11 +489,11 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult, resultContent := truncateHeight(response.Content, maxResultHeight) switch toolCall.Name { - case agent.AgentToolName: - return styles.ForceReplaceBackgroundWithLipgloss( - toMarkdown(resultContent, false, width), - t.Background(), - ) + // case agent.AgentToolName: + // return styles.ForceReplaceBackgroundWithLipgloss( + // toMarkdown(resultContent, false, width), + // t.Background(), + // ) case tools.BashToolName: resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent) return styles.ForceReplaceBackgroundWithLipgloss( @@ -628,9 +636,9 @@ func renderToolMessage( content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText)) toolMsg := uiMessage{ messageType: toolMessageType, - position: position, - height: lipgloss.Height(content), - content: content, + // position: position, + // height: lipgloss.Height(content), + content: content, } return toolMsg } @@ -667,17 +675,17 @@ func renderToolMessage( parts = append(parts, lipgloss.JoinHorizontal(lipgloss.Left, prefix, toolNameText, formattedParams)) } - if toolCall.Name == agent.AgentToolName { - taskMessages, _ := messagesService.List(context.Background(), toolCall.ID) - toolCalls := []message.ToolCall{} - for _, v := range taskMessages { - toolCalls = append(toolCalls, v.ToolCalls()...) - } - for _, call := range toolCalls { - rendered := renderToolMessage(call, []message.Message{}, messagesService, focusedUIMessageId, true, width, 0) - parts = append(parts, rendered.content) - } - } + // if toolCall.Name == agent.AgentToolName { + // taskMessages, _ := messagesService.List(context.Background(), toolCall.ID) + // toolCalls := []message.ToolCall{} + // for _, v := range taskMessages { + // toolCalls = append(toolCalls, v.ToolCalls()...) + // } + // for _, call := range toolCalls { + // rendered := renderToolMessage(call, []message.Message{}, messagesService, focusedUIMessageId, true, width, 0) + // parts = append(parts, rendered.content) + // } + // } if responseContent != "" && !nested { parts = append(parts, responseContent) } @@ -696,9 +704,9 @@ func renderToolMessage( } toolMsg := uiMessage{ messageType: toolMessageType, - position: position, - height: lipgloss.Height(content), - content: content, + // position: position, + // height: lipgloss.Height(content), + content: content, } return toolMsg } diff --git a/internal/tui/components/chat/messages.go b/internal/tui/components/chat/messages.go index ce02e2e07..652f831d3 100644 --- a/internal/tui/components/chat/messages.go +++ b/internal/tui/components/chat/messages.go @@ -1,8 +1,6 @@ package chat import ( - "context" - "encoding/json" "fmt" "math" "time" @@ -13,14 +11,13 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/sst/opencode/internal/message" - "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/session" - "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/tui/app" "github.com/sst/opencode/internal/tui/components/dialog" "github.com/sst/opencode/internal/tui/state" "github.com/sst/opencode/internal/tui/styles" "github.com/sst/opencode/internal/tui/theme" + "github.com/sst/opencode/pkg/client" ) type cacheItem struct { @@ -32,7 +29,6 @@ type messagesCmp struct { app *app.App width, height int viewport viewport.Model - messages []message.Message uiMessages []uiMessage currentMsgID string cachedContent map[string]cacheItem @@ -75,6 +71,8 @@ func (m *messagesCmp) Init() tea.Cmd { } func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + m.renderView() + var cmds []tea.Cmd switch msg := msg.(type) { case dialog.ThemeChangedMsg: @@ -90,7 +88,7 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmd := m.Reload(msg) return m, cmd case state.SessionClearedMsg: - m.messages = make([]message.Message, 0) + // m.messages = make([]message.Message, 0) m.currentMsgID = "" m.rendering = false return m, nil @@ -104,62 +102,63 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case renderFinishedMsg: m.rendering = false m.viewport.GotoBottom() + case state.StateUpdatedMsg: m.renderView() m.viewport.GotoBottom() - case pubsub.Event[message.Message]: - needsRerender := false - if msg.Type == message.EventMessageCreated { - if msg.Payload.SessionID == m.app.CurrentSession.ID { - messageExists := false - for _, v := range m.messages { - if v.ID == msg.Payload.ID { - messageExists = true - break - } - } - - if !messageExists { - if len(m.messages) > 0 { - lastMsgID := m.messages[len(m.messages)-1].ID - delete(m.cachedContent, lastMsgID) - } - - m.messages = append(m.messages, msg.Payload) - delete(m.cachedContent, m.currentMsgID) - m.currentMsgID = msg.Payload.ID - needsRerender = true - } - } - // There are tool calls from the child task - for _, v := range m.messages { - for _, c := range v.ToolCalls() { - if c.ID == msg.Payload.SessionID { - delete(m.cachedContent, v.ID) - needsRerender = true - } - } - } - } else if msg.Type == message.EventMessageUpdated && msg.Payload.SessionID == m.app.CurrentSession.ID { - for i, v := range m.messages { - if v.ID == msg.Payload.ID { - m.messages[i] = msg.Payload - delete(m.cachedContent, msg.Payload.ID) - needsRerender = true - break - } - } - } - if needsRerender { - m.renderView() - if len(m.messages) > 0 { - if (msg.Type == message.EventMessageCreated) || - (msg.Type == message.EventMessageUpdated && msg.Payload.ID == m.messages[len(m.messages)-1].ID) { - m.viewport.GotoBottom() - } - } - } + // case pubsub.Event[message.Message]: + // needsRerender := false + // if msg.Type == message.EventMessageCreated { + // if msg.Payload.SessionID == m.app.CurrentSessionOLD.ID { + // messageExists := false + // for _, v := range m.messages { + // if v.ID == msg.Payload.ID { + // messageExists = true + // break + // } + // } + // + // if !messageExists { + // if len(m.messages) > 0 { + // lastMsgID := m.messages[len(m.messages)-1].ID + // delete(m.cachedContent, lastMsgID) + // } + // + // m.messages = append(m.messages, msg.Payload) + // delete(m.cachedContent, m.currentMsgID) + // m.currentMsgID = msg.Payload.ID + // needsRerender = true + // } + // } + // // There are tool calls from the child task + // for _, v := range m.messages { + // for _, c := range v.ToolCalls() { + // if c.ID == msg.Payload.SessionID { + // delete(m.cachedContent, v.ID) + // needsRerender = true + // } + // } + // } + // } else if msg.Type == message.EventMessageUpdated && msg.Payload.SessionID == m.app.CurrentSessionOLD.ID { + // for i, v := range m.messages { + // if v.ID == msg.Payload.ID { + // m.messages[i] = msg.Payload + // delete(m.cachedContent, msg.Payload.ID) + // needsRerender = true + // break + // } + // } + // } + // if needsRerender { + // m.renderView() + // if len(m.messages) > 0 { + // if (msg.Type == message.EventMessageCreated) || + // (msg.Type == message.EventMessageUpdated && msg.Payload.ID == m.messages[len(m.messages)-1].ID) { + // m.viewport.GotoBottom() + // } + // } + // } } spinner, cmd := m.spinner.Update(msg) @@ -169,7 +168,7 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m *messagesCmp) IsAgentWorking() bool { - return m.app.PrimaryAgent.IsSessionBusy(m.app.CurrentSession.ID) + return m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID) } func formatTimeDifference(unixTime1, unixTime2 int64) string { @@ -192,48 +191,48 @@ func (m *messagesCmp) renderView() { if m.width == 0 { return } - for inx, msg := range m.messages { + for _, msg := range m.app.Messages { switch msg.Role { - case message.User: - if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width { + case client.User: + if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width { m.uiMessages = append(m.uiMessages, cache.content...) continue } userMsg := renderUserMessage( msg, - msg.ID == m.currentMsgID, + msg.Id == m.currentMsgID, m.width, pos, ) m.uiMessages = append(m.uiMessages, userMsg) - m.cachedContent[msg.ID] = cacheItem{ + m.cachedContent[msg.Id] = cacheItem{ width: m.width, content: []uiMessage{userMsg}, } - pos += userMsg.height + 1 // + 1 for spacing - case message.Assistant: - if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width { + // pos += userMsg.height + 1 // + 1 for spacing + case client.Assistant: + if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width { m.uiMessages = append(m.uiMessages, cache.content...) continue } - assistantMessages := renderAssistantMessage( - msg, - inx, - m.messages, - m.app.Messages, - m.currentMsgID, - m.width, - pos, - m.showToolMessages, - ) - for _, msg := range assistantMessages { - m.uiMessages = append(m.uiMessages, msg) - pos += msg.height + 1 // + 1 for spacing - } - m.cachedContent[msg.ID] = cacheItem{ - width: m.width, - content: assistantMessages, - } + // assistantMessages := renderAssistantMessage( + // msg, + // inx, + // m.app.Messages, + // m.app.MessagesOLD, + // m.currentMsgID, + // m.width, + // pos, + // m.showToolMessages, + // ) + // for _, msg := range assistantMessages { + // m.uiMessages = append(m.uiMessages, msg) + // // pos += msg.height + 1 // + 1 for spacing + // } + // m.cachedContent[msg.Id] = cacheItem{ + // width: m.width, + // content: assistantMessages, + // } } } @@ -248,33 +247,23 @@ func (m *messagesCmp) renderView() { ) } - temp, _ := json.MarshalIndent(m.app.State, "", " ") + // temp, _ := json.MarshalIndent(m.app.State, "", " ") m.viewport.SetContent( baseStyle. Width(m.width). Render( - string(temp), - // 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. @@ -283,12 +272,12 @@ func (m *messagesCmp) View() string { lipgloss.JoinVertical( lipgloss.Top, "Loading...", - m.working(), + // m.working(), m.help(), ), ) } - if len(m.messages) == 0 { + if len(m.app.Messages) == 0 { content := baseStyle. Width(m.width). Height(m.height - 1). @@ -314,7 +303,7 @@ func (m *messagesCmp) View() string { lipgloss.JoinVertical( lipgloss.Top, m.viewport.View(), - m.working(), + // m.working(), m.help(), ), ) @@ -356,31 +345,31 @@ func hasUnfinishedToolCalls(messages []message.Message) bool { return false } -func (m *messagesCmp) working() string { - text := "" - if m.IsAgentWorking() && len(m.messages) > 0 { - t := theme.CurrentTheme() - baseStyle := styles.BaseStyle() - - task := "Thinking..." - lastMessage := m.messages[len(m.messages)-1] - if hasToolsWithoutResponse(m.messages) { - task = "Waiting for tool response..." - } else if hasUnfinishedToolCalls(m.messages) { - task = "Building tool call..." - } else if !lastMessage.IsFinished() { - task = "Generating..." - } - if task != "" { - text += baseStyle. - Width(m.width). - Foreground(t.Primary()). - Bold(true). - Render(fmt.Sprintf("%s %s ", m.spinner.View(), task)) - } - } - return text -} +// func (m *messagesCmp) working() string { +// text := "" +// if m.IsAgentWorking() && len(m.app.Messages) > 0 { +// t := theme.CurrentTheme() +// baseStyle := styles.BaseStyle() +// +// task := "Thinking..." +// lastMessage := m.app.Messages[len(m.app.Messages)-1] +// if hasToolsWithoutResponse(m.app.Messages) { +// task = "Waiting for tool response..." +// } else if hasUnfinishedToolCalls(m.app.Messages) { +// task = "Building tool call..." +// } else if !lastMessage.IsFinished() { +// task = "Generating..." +// } +// if task != "" { +// text += baseStyle. +// Width(m.width). +// Foreground(t.Primary()). +// Bold(true). +// Render(fmt.Sprintf("%s %s ", m.spinner.View(), task)) +// } +// } +// return text +// } func (m *messagesCmp) help() string { t := theme.CurrentTheme() @@ -388,7 +377,7 @@ func (m *messagesCmp) help() string { text := "" - if m.app.PrimaryAgent.IsBusy() { + if m.app.PrimaryAgentOLD.IsBusy() { text += lipgloss.JoinHorizontal( lipgloss.Left, baseStyle.Foreground(t.TextMuted()).Bold(true).Render("press "), @@ -429,8 +418,8 @@ func (m *messagesCmp) initialScreen() string { } func (m *messagesCmp) rerender() { - for _, msg := range m.messages { - delete(m.cachedContent, msg.ID) + for _, msg := range m.app.Messages { + delete(m.cachedContent, msg.Id) } m.renderView() } @@ -454,14 +443,16 @@ func (m *messagesCmp) GetSize() (int, int) { } func (m *messagesCmp) Reload(session *session.Session) tea.Cmd { - messages, err := m.app.Messages.List(context.Background(), session.ID) - if err != nil { - status.Error(err.Error()) - return nil - } - m.messages = messages - if len(m.messages) > 0 { - m.currentMsgID = m.messages[len(m.messages)-1].ID + // messages := m.app.Messages + // messages, err := m.app.MessagesOLD.List(context.Background(), session.ID) + // if err != nil { + // status.Error(err.Error()) + // return nil + // } + // m.messages = messages + + if len(m.app.Messages) > 0 { + m.currentMsgID = m.app.Messages[len(m.app.Messages)-1].Id } delete(m.cachedContent, m.currentMsgID) m.rendering = true diff --git a/internal/tui/components/chat/sidebar.go b/internal/tui/components/chat/sidebar.go index 6676f9b09..5645d44f2 100644 --- a/internal/tui/components/chat/sidebar.go +++ b/internal/tui/components/chat/sidebar.go @@ -86,7 +86,7 @@ func (m *sidebarCmp) sessionSection() string { sessionValue := baseStyle. Foreground(t.Text()). - Render(fmt.Sprintf(": %s", m.app.CurrentSession.Title)) + Render(fmt.Sprintf(": %s", m.app.CurrentSessionOLD.Title)) return sessionKey + sessionValue } @@ -209,7 +209,7 @@ func NewSidebarCmp(app *app.App) tea.Model { } func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { - if m.app.CurrentSession.ID == "" { + if m.app.CurrentSessionOLD.ID == "" { return } diff --git a/internal/tui/components/core/status.go b/internal/tui/components/core/status.go index 681703c36..74e254eb9 100644 --- a/internal/tui/components/core/status.go +++ b/internal/tui/components/core/status.go @@ -9,7 +9,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/llm/models" - "github.com/sst/opencode/internal/lsp" "github.com/sst/opencode/internal/lsp/protocol" "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/status" @@ -155,8 +154,8 @@ func (m statusCmp) View() string { // Initialize the help widget status := getHelpWidget("") - if m.app.CurrentSession.ID != "" { - tokens := formatTokensAndCost(m.app.CurrentSession.PromptTokens+m.app.CurrentSession.CompletionTokens, model.ContextWindow, m.app.CurrentSession.Cost) + if m.app.CurrentSessionOLD.ID != "" { + tokens := formatTokensAndCost(m.app.CurrentSessionOLD.PromptTokens+m.app.CurrentSessionOLD.CompletionTokens, model.ContextWindow, m.app.CurrentSessionOLD.Cost) tokensStyle := styles.Padded(). Background(t.Text()). Foreground(t.BackgroundSecondary()). @@ -245,12 +244,12 @@ func (m *statusCmp) projectDiagnostics() string { // Check if any LSP server is still initializing initializing := false - for _, client := range m.app.LSPClients { - if client.GetServerState() == lsp.StateStarting { - initializing = true - break - } - } + // for _, client := range m.app.LSPClients { + // if client.GetServerState() == lsp.StateStarting { + // initializing = true + // break + // } + // } // If any server is initializing, show that status if initializing { @@ -263,22 +262,22 @@ func (m *statusCmp) projectDiagnostics() string { warnDiagnostics := []protocol.Diagnostic{} hintDiagnostics := []protocol.Diagnostic{} infoDiagnostics := []protocol.Diagnostic{} - for _, client := range m.app.LSPClients { - for _, d := range client.GetDiagnostics() { - for _, diag := range d { - switch diag.Severity { - case protocol.SeverityError: - errorDiagnostics = append(errorDiagnostics, diag) - case protocol.SeverityWarning: - warnDiagnostics = append(warnDiagnostics, diag) - case protocol.SeverityHint: - hintDiagnostics = append(hintDiagnostics, diag) - case protocol.SeverityInformation: - infoDiagnostics = append(infoDiagnostics, diag) - } - } - } - } + // for _, client := range m.app.LSPClients { + // for _, d := range client.GetDiagnostics() { + // for _, diag := range d { + // switch diag.Severity { + // case protocol.SeverityError: + // errorDiagnostics = append(errorDiagnostics, diag) + // case protocol.SeverityWarning: + // warnDiagnostics = append(warnDiagnostics, diag) + // case protocol.SeverityHint: + // hintDiagnostics = append(hintDiagnostics, diag) + // case protocol.SeverityInformation: + // infoDiagnostics = append(infoDiagnostics, diag) + // } + // } + // } + // } if len(errorDiagnostics) == 0 && len(warnDiagnostics) == 0 && diff --git a/internal/tui/page/chat.go b/internal/tui/page/chat.go index 96847fee7..d0f974ce4 100644 --- a/internal/tui/page/chat.go +++ b/internal/tui/page/chat.go @@ -78,7 +78,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case dialog.CommandRunCustomMsg: // Check if the agent is busy before executing custom commands - if p.app.PrimaryAgent.IsBusy() { + if p.app.PrimaryAgentOLD.IsBusy() { status.Warn("Agent is busy, please wait before executing a command...") return p, nil } @@ -105,20 +105,20 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmd := p.setSidebar() cmds = append(cmds, cmd) case state.CompactSessionMsg: - if p.app.CurrentSession.ID == "" { + if p.app.CurrentSessionOLD.ID == "" { status.Warn("No active session to compact.") return p, nil } // Run compaction in background go func(sessionID string) { - err := p.app.PrimaryAgent.CompactSession(context.Background(), sessionID, false) + err := p.app.PrimaryAgentOLD.CompactSession(context.Background(), sessionID, false) if err != nil { status.Error(fmt.Sprintf("Compaction failed: %v", err)) } else { status.Info("Conversation compacted successfully.") } - }(p.app.CurrentSession.ID) + }(p.app.CurrentSessionOLD.ID) return p, nil case dialog.CompletionDialogCloseMsg: @@ -131,16 +131,16 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.app.SetCompletionDialogOpen(true) // Continue sending keys to layout->chat case key.Matches(msg, keyMap.NewSession): - p.app.CurrentSession = &session.Session{} + p.app.CurrentSessionOLD = &session.Session{} return p, tea.Batch( p.clearSidebar(), util.CmdHandler(state.SessionClearedMsg{}), ) case key.Matches(msg, keyMap.Cancel): - if p.app.CurrentSession.ID != "" { + if p.app.CurrentSessionOLD.ID != "" { // Cancel the current session's generation process // This allows users to interrupt long-running operations - p.app.PrimaryAgent.Cancel(p.app.CurrentSession.ID) + p.app.PrimaryAgentOLD.Cancel(p.app.CurrentSessionOLD.ID) return p, nil } case key.Matches(msg, keyMap.ToggleTools): diff --git a/internal/tui/tui.go b/internal/tui/tui.go index dbb8460ed..b0950c2b3 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -2,6 +2,7 @@ package tui import ( "context" + "encoding/json" "log/slog" "strings" @@ -178,7 +179,7 @@ func (a appModel) Init() tea.Cmd { func (a appModel) updateAllPages(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd - for id, _ := range a.pages { + for id := range a.pages { a.pages[id], cmd = a.pages[id].Update(msg) cmds = append(cmds, cmd) } @@ -256,26 +257,76 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, a.moveToPage(msg.ID) case state.SessionSelectedMsg: - a.app.CurrentSession = msg + a.app.CurrentSessionOLD = msg return a.updateAllPages(msg) case pubsub.Event[session.Session]: if msg.Type == session.EventSessionUpdated { - if a.app.CurrentSession.ID == msg.Payload.ID { - a.app.CurrentSession = &msg.Payload + if a.app.CurrentSessionOLD.ID == msg.Payload.ID { + a.app.CurrentSessionOLD = &msg.Payload } } - // Handle SSE events from the TypeScript backend case client.EventStorageWrite: - slog.Debug("Received SSE event", "key", msg.Properties.Key) + parts := strings.Split(msg.Key, "/") + if len(parts) < 3 { + return a, nil + } + + if parts[0] == "session" && parts[1] == "info" { + sessionId := parts[2] + if sessionId == a.app.Session.Id { + var sessionInfo client.SessionInfo + bytes, _ := json.Marshal(msg.Content) + if err := json.Unmarshal(bytes, &sessionInfo); err != nil { + status.Error(err.Error()) + return a, nil + } + + a.app.Session = &sessionInfo + } + return a, nil + } + + if parts[0] == "session" && parts[1] == "message" { + sessionId := parts[2] + if sessionId == a.app.Session.Id { + messageId := parts[3] + var message client.SessionMessage + bytes, _ := json.Marshal(msg.Content) + if err := json.Unmarshal(bytes, &message); err != nil { + status.Error(err.Error()) + return a, nil + } + + for i, m := range a.app.Messages { + if m.Id == messageId { + a.app.Messages[i] = message + slog.Debug("Updated message", "message", message) + return a, nil + } + } + + a.app.Messages = append(a.app.Messages, message) + slog.Debug("Appended message", "message", message) + + // a.app.CurrentSession.MessageCount++ + // a.app.CurrentSession.PromptTokens += message.PromptTokens + // a.app.CurrentSession.CompletionTokens += message.CompletionTokens + // a.app.CurrentSession.Cost += message.Cost + // a.app.CurrentSession.UpdatedAt = message.CreatedAt + } + return a, nil + } + + // log key and content + slog.Debug("Received SSE event", "key", msg.Key, "content", msg.Content) - splits := strings.Split(msg.Properties.Key, "/") current := a.app.State - for i, part := range splits { - if i == len(splits)-1 { - current[part] = msg.Properties.Content + for i, part := range parts { + if i == len(parts)-1 { + current[part] = msg.Content } else { if _, exists := current[part]; !exists { current[part] = make(map[string]any) @@ -566,7 +617,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return a, nil case key.Matches(msg, helpEsc): - if a.app.PrimaryAgent.IsBusy() { + if a.app.PrimaryAgentOLD.IsBusy() { if a.showQuit { return a, nil } @@ -705,27 +756,9 @@ func (a *appModel) RegisterCommand(cmd dialog.Command) { } // getAvailableToolNames returns a list of all available tool names -func getAvailableToolNames(app *app.App) []string { +func getAvailableToolNames(_ *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) - } - - return toolNames - */ } func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd { @@ -785,7 +818,7 @@ func (a appModel) View() string { } - if !a.app.PrimaryAgent.IsBusy() { + if !a.app.PrimaryAgentOLD.IsBusy() { a.status.SetHelpWidgetMsg("ctrl+? help") } else { a.status.SetHelpWidgetMsg("? help") @@ -799,7 +832,7 @@ func (a appModel) View() string { if a.showPermissions { bindings = append(bindings, a.permissions.BindingKeys()...) } - if !a.app.PrimaryAgent.IsBusy() { + if !a.app.PrimaryAgentOLD.IsBusy() { bindings = append(bindings, helpEsc) } a.help.SetBindings(bindings) |
