summaryrefslogtreecommitdiffhomepage
path: root/internal/tui
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-05-29 09:42:56 -0500
committeradamdottv <[email protected]>2025-05-29 14:04:48 -0500
commit6759674c0f3205155a0737b929a3fd0346a752c4 (patch)
tree7f90fcf3283985022536a966b09a18547873464c /internal/tui
parenta9799136fe68c6ebac8b60ad963521ba159d9ae9 (diff)
downloadopencode-6759674c0f3205155a0737b929a3fd0346a752c4.tar.gz
opencode-6759674c0f3205155a0737b929a3fd0346a752c4.zip
wip: refactoring tui
Diffstat (limited to 'internal/tui')
-rw-r--r--internal/tui/app/app.go94
-rw-r--r--internal/tui/components/chat/editor.go4
-rw-r--r--internal/tui/components/chat/message.go148
-rw-r--r--internal/tui/components/chat/messages.go271
-rw-r--r--internal/tui/components/chat/sidebar.go4
-rw-r--r--internal/tui/components/core/status.go49
-rw-r--r--internal/tui/page/chat.go14
-rw-r--r--internal/tui/tui.go97
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), &params)
- prompt := strings.ReplaceAll(params.Prompt, "\n", " ")
- return renderParams(paramWidth, prompt)
+ // case agent.AgentToolName:
+ // var params agent.AgentParams
+ // json.Unmarshal([]byte(toolCall.Input), &params)
+ // prompt := strings.ReplaceAll(params.Prompt, "\n", " ")
+ // return renderParams(paramWidth, prompt)
case tools.BashToolName:
var params tools.BashParams
json.Unmarshal([]byte(toolCall.Input), &params)
@@ -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)