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