SuperCoder/internal/tui/page/chat.go

175 lines
4.3 KiB
Go
Raw Normal View History

2025-04-11 18:54:13 +02:00
package page
import (
2025-04-13 13:17:17 +02:00
"context"
"github.com/charmbracelet/bubbles/key"
2025-04-11 18:54:13 +02:00
tea "github.com/charmbracelet/bubbletea"
2025-04-24 18:25:52 +02:00
"github.com/opencode-ai/opencode/internal/app"
2025-05-03 01:53:58 +05:30
"github.com/opencode-ai/opencode/internal/message"
2025-04-24 18:25:52 +02:00
"github.com/opencode-ai/opencode/internal/session"
"github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/opencode-ai/opencode/internal/tui/components/dialog"
2025-04-24 18:25:52 +02:00
"github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/opencode-ai/opencode/internal/tui/util"
2025-04-11 18:54:13 +02:00
)
var ChatPage PageID = "chat"
2025-04-12 02:01:45 +02:00
type chatPage struct {
2025-04-23 12:08:45 +02:00
app *app.App
editor layout.Container
messages layout.Container
layout layout.SplitPaneLayout
session session.Session
2025-04-12 02:01:45 +02:00
}
type ChatKeyMap struct {
NewSession key.Binding
2025-04-13 14:37:05 +02:00
Cancel key.Binding
}
var keyMap = ChatKeyMap{
NewSession: key.NewBinding(
key.WithKeys("ctrl+n"),
key.WithHelp("ctrl+n", "new session"),
),
2025-04-13 14:37:05 +02:00
Cancel: key.NewBinding(
2025-04-23 12:08:45 +02:00
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
2025-04-13 14:37:05 +02:00
),
}
2025-04-12 02:01:45 +02:00
func (p *chatPage) Init() tea.Cmd {
cmds := []tea.Cmd{
p.layout.Init(),
}
return tea.Batch(cmds...)
2025-04-12 02:01:45 +02:00
}
func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
2025-04-12 02:01:45 +02:00
switch msg := msg.(type) {
case tea.WindowSizeMsg:
cmd := p.layout.SetSize(msg.Width, msg.Height)
cmds = append(cmds, cmd)
2025-04-12 02:01:45 +02:00
case chat.SendMsg:
2025-05-03 01:53:58 +05:30
cmd := p.sendMessage(msg.Text, msg.Attachments)
2025-04-12 02:01:45 +02:00
if cmd != nil {
return p, cmd
}
case dialog.CommandRunCustomMsg:
// Check if the agent is busy before executing custom commands
if p.app.CoderAgent.IsBusy() {
return p, util.ReportWarn("Agent is busy, please wait before executing a command...")
}
// Handle custom command execution
cmd := p.sendMessage(msg.Content)
if cmd != nil {
return p, cmd
}
2025-04-19 16:35:45 +02:00
case chat.SessionSelectedMsg:
if p.session.ID == "" {
cmd := p.setSidebar()
if cmd != nil {
cmds = append(cmds, cmd)
}
}
p.session = msg
case tea.KeyMsg:
switch {
case key.Matches(msg, keyMap.NewSession):
p.session = session.Session{}
return p, tea.Batch(
p.clearSidebar(),
util.CmdHandler(chat.SessionClearedMsg{}),
)
case key.Matches(msg, keyMap.Cancel):
if p.session.ID != "" {
// Cancel the current session's generation process
// This allows users to interrupt long-running operations
p.app.CoderAgent.Cancel(p.session.ID)
return p, nil
}
}
2025-04-12 02:01:45 +02:00
}
u, cmd := p.layout.Update(msg)
cmds = append(cmds, cmd)
2025-04-12 02:01:45 +02:00
p.layout = u.(layout.SplitPaneLayout)
return p, tea.Batch(cmds...)
2025-04-12 02:01:45 +02:00
}
func (p *chatPage) setSidebar() tea.Cmd {
sidebarContainer := layout.NewContainer(
chat.NewSidebarCmp(p.session, p.app.History),
2025-04-11 18:54:13 +02:00
layout.WithPadding(1, 1, 1, 1),
)
return tea.Batch(p.layout.SetRightPanel(sidebarContainer), sidebarContainer.Init())
2025-04-12 02:01:45 +02:00
}
func (p *chatPage) clearSidebar() tea.Cmd {
2025-04-19 16:35:45 +02:00
return p.layout.ClearRightPanel()
}
2025-05-03 01:53:58 +05:30
func (p *chatPage) sendMessage(text string, attachments []message.Attachment) tea.Cmd {
2025-04-12 02:01:45 +02:00
var cmds []tea.Cmd
if p.session.ID == "" {
2025-04-13 13:17:17 +02:00
session, err := p.app.Sessions.Create(context.Background(), "New Session")
2025-04-12 02:01:45 +02:00
if err != nil {
return util.ReportError(err)
}
p.session = session
cmd := p.setSidebar()
if cmd != nil {
cmds = append(cmds, cmd)
}
cmds = append(cmds, util.CmdHandler(chat.SessionSelectedMsg(session)))
}
2025-05-03 01:53:58 +05:30
_, err := p.app.CoderAgent.Run(context.Background(), p.session.ID, text, attachments...)
if err != nil {
return util.ReportError(err)
}
2025-04-12 02:01:45 +02:00
return tea.Batch(cmds...)
}
func (p *chatPage) SetSize(width, height int) tea.Cmd {
return p.layout.SetSize(width, height)
}
func (p *chatPage) GetSize() (int, int) {
return p.layout.GetSize()
}
2025-04-12 02:01:45 +02:00
func (p *chatPage) View() string {
return p.layout.View()
}
func (p *chatPage) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(keyMap)
2025-04-23 12:08:45 +02:00
bindings = append(bindings, p.messages.BindingKeys()...)
2025-05-03 01:53:58 +05:30
bindings = append(bindings, p.editor.BindingKeys()...)
return bindings
}
2025-04-12 02:01:45 +02:00
func NewChatPage(app *app.App) tea.Model {
messagesContainer := layout.NewContainer(
chat.NewMessagesCmp(app),
layout.WithPadding(1, 1, 0, 1),
2025-04-11 18:54:13 +02:00
)
editorContainer := layout.NewContainer(
chat.NewEditorCmp(app),
2025-04-11 18:54:13 +02:00
layout.WithBorder(true, false, false, false),
)
2025-04-12 02:01:45 +02:00
return &chatPage{
2025-04-23 12:08:45 +02:00
app: app,
editor: editorContainer,
messages: messagesContainer,
2025-04-12 02:01:45 +02:00
layout: layout.NewSplitPane(
layout.WithLeftPanel(messagesContainer),
layout.WithBottomPanel(editorContainer),
),
}
2025-04-11 18:54:13 +02:00
}