summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/components/repl
diff options
context:
space:
mode:
authorKujtim Hoxha <[email protected]>2025-03-23 19:19:08 +0100
committerKujtim Hoxha <[email protected]>2025-03-23 19:19:08 +0100
commit8daa6e774a6e02698c90392e7b2008542f789594 (patch)
tree8053fc1a4de87fc8dc3fd6e776577b0221c02ac5 /internal/tui/components/repl
parent796bbf4d66c0fc70e750c7f582019cb9a7298e59 (diff)
downloadopencode-8daa6e774a6e02698c90392e7b2008542f789594.tar.gz
opencode-8daa6e774a6e02698c90392e7b2008542f789594.zip
add initial stuff
Diffstat (limited to 'internal/tui/components/repl')
-rw-r--r--internal/tui/components/repl/editor.go127
-rw-r--r--internal/tui/components/repl/messages.go15
-rw-r--r--internal/tui/components/repl/sessions.go161
-rw-r--r--internal/tui/components/repl/threads.go21
4 files changed, 290 insertions, 34 deletions
diff --git a/internal/tui/components/repl/editor.go b/internal/tui/components/repl/editor.go
index 8a04889ab..1de216bd0 100644
--- a/internal/tui/components/repl/editor.go
+++ b/internal/tui/components/repl/editor.go
@@ -1,21 +1,130 @@
package repl
-import tea "github.com/charmbracelet/bubbletea"
+import (
+ "github.com/charmbracelet/bubbles/key"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/kujtimiihoxha/termai/internal/app"
+ "github.com/kujtimiihoxha/termai/internal/tui/layout"
+ "github.com/kujtimiihoxha/vimtea"
+)
-type editorCmp struct{}
+type EditorCmp interface {
+ tea.Model
+ layout.Focusable
+ layout.Sizeable
+ layout.Bordered
+}
+
+type editorCmp struct {
+ app *app.App
+ editor vimtea.Editor
+ editorMode vimtea.EditorMode
+ sessionID string
+ focused bool
+ width int
+ height int
+}
+
+type localKeyMap struct {
+ SendMessage key.Binding
+ SendMessageI key.Binding
+}
+
+var keyMap = localKeyMap{
+ SendMessage: key.NewBinding(
+ key.WithKeys("enter"),
+ key.WithHelp("enter", "send message normal mode"),
+ ),
+ SendMessageI: key.NewBinding(
+ key.WithKeys("ctrl+s"),
+ key.WithHelp("ctrl+s", "send message insert mode"),
+ ),
+}
+
+func (m *editorCmp) Init() tea.Cmd {
+ return m.editor.Init()
+}
+
+func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case vimtea.EditorModeMsg:
+ m.editorMode = msg.Mode
+ case SelectedSessionMsg:
+ if msg.SessionID != m.sessionID {
+ m.sessionID = msg.SessionID
+ }
+ }
+ if m.IsFocused() {
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch {
+ case key.Matches(msg, keyMap.SendMessage):
+ if m.editorMode == vimtea.ModeNormal {
+ return m, m.Send()
+ }
+ case key.Matches(msg, keyMap.SendMessageI):
+ if m.editorMode == vimtea.ModeInsert {
+ return m, m.Send()
+ }
+ }
+ }
+ u, cmd := m.editor.Update(msg)
+ m.editor = u.(vimtea.Editor)
+ return m, cmd
+ }
+ return m, nil
+}
-func (i *editorCmp) Init() tea.Cmd {
+// Blur implements EditorCmp.
+func (m *editorCmp) Blur() tea.Cmd {
+ m.focused = false
return nil
}
-func (i *editorCmp) Update(_ tea.Msg) (tea.Model, tea.Cmd) {
- return i, nil
+// BorderText implements EditorCmp.
+func (m *editorCmp) BorderText() map[layout.BorderPosition]string {
+ return map[layout.BorderPosition]string{
+ layout.TopLeftBorder: "New Message",
+ }
+}
+
+// Focus implements EditorCmp.
+func (m *editorCmp) Focus() tea.Cmd {
+ m.focused = true
+ return m.editor.Tick()
+}
+
+// GetSize implements EditorCmp.
+func (m *editorCmp) GetSize() (int, int) {
+ return m.width, m.height
+}
+
+// IsFocused implements EditorCmp.
+func (m *editorCmp) IsFocused() bool {
+ return m.focused
+}
+
+// SetSize implements EditorCmp.
+func (m *editorCmp) SetSize(width int, height int) {
+ m.width = width
+ m.height = height
+ m.editor.SetSize(width, height)
+}
+
+func (m *editorCmp) Send() tea.Cmd {
+ return func() tea.Msg {
+ // TODO: Send message
+ return nil
+ }
}
-func (i *editorCmp) View() string {
- return "Editor"
+func (m *editorCmp) View() string {
+ return m.editor.View()
}
-func NewEditorCmp() tea.Model {
- return &editorCmp{}
+func NewEditorCmp(app *app.App) EditorCmp {
+ return &editorCmp{
+ app: app,
+ editor: vimtea.NewEditor(),
+ }
}
diff --git a/internal/tui/components/repl/messages.go b/internal/tui/components/repl/messages.go
index edef26502..cd3ddac71 100644
--- a/internal/tui/components/repl/messages.go
+++ b/internal/tui/components/repl/messages.go
@@ -1,8 +1,13 @@
package repl
-import tea "github.com/charmbracelet/bubbletea"
+import (
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/kujtimiihoxha/termai/internal/app"
+)
-type messagesCmp struct{}
+type messagesCmp struct {
+ app *app.App
+}
func (i *messagesCmp) Init() tea.Cmd {
return nil
@@ -16,6 +21,8 @@ func (i *messagesCmp) View() string {
return "Messages"
}
-func NewMessagesCmp() tea.Model {
- return &messagesCmp{}
+func NewMessagesCmp(app *app.App) tea.Model {
+ return &messagesCmp{
+ app,
+ }
}
diff --git a/internal/tui/components/repl/sessions.go b/internal/tui/components/repl/sessions.go
new file mode 100644
index 000000000..aef9b993e
--- /dev/null
+++ b/internal/tui/components/repl/sessions.go
@@ -0,0 +1,161 @@
+package repl
+
+import (
+ "fmt"
+
+ "github.com/charmbracelet/bubbles/key"
+ "github.com/charmbracelet/bubbles/list"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/kujtimiihoxha/termai/internal/app"
+ "github.com/kujtimiihoxha/termai/internal/session"
+ "github.com/kujtimiihoxha/termai/internal/tui/layout"
+ "github.com/kujtimiihoxha/termai/internal/tui/styles"
+ "github.com/kujtimiihoxha/termai/internal/tui/util"
+)
+
+type SessionsCmp interface {
+ tea.Model
+ layout.Sizeable
+ layout.Focusable
+ layout.Bordered
+ layout.Bindings
+}
+type sessionsCmp struct {
+ app *app.App
+ list list.Model
+ focused bool
+}
+
+type listItem struct {
+ id, title, desc string
+}
+
+func (i listItem) Title() string { return i.title }
+func (i listItem) Description() string { return i.desc }
+func (i listItem) FilterValue() string { return i.title }
+
+type InsertSessionsMsg struct {
+ sessions []session.Session
+}
+
+type SelectedSessionMsg struct {
+ SessionID string
+}
+
+func (i *sessionsCmp) Init() tea.Cmd {
+ existing, err := i.app.Sessions.List()
+ if err != nil {
+ return util.ReportError(err)
+ }
+ if len(existing) == 0 || existing[0].MessageCount > 0 {
+ session, err := i.app.Sessions.Create(
+ "New Session",
+ )
+ if err != nil {
+ return util.ReportError(err)
+ }
+ existing = append(existing, session)
+ }
+ return tea.Batch(
+ util.CmdHandler(InsertSessionsMsg{existing}),
+ util.CmdHandler(SelectedSessionMsg{existing[0].ID}),
+ )
+}
+
+func (i *sessionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case InsertSessionsMsg:
+ items := make([]list.Item, len(msg.sessions))
+ for i, s := range msg.sessions {
+ items[i] = listItem{
+ id: s.ID,
+ title: s.Title,
+ desc: fmt.Sprintf("Tokens: %d, Cost: %.2f", s.Tokens, s.Cost),
+ }
+ }
+ return i, i.list.SetItems(items)
+ }
+ if i.focused {
+ u, cmd := i.list.Update(msg)
+ i.list = u
+ return i, cmd
+ }
+ return i, nil
+}
+
+func (i *sessionsCmp) View() string {
+ return i.list.View()
+}
+
+func (i *sessionsCmp) Blur() tea.Cmd {
+ i.focused = false
+ return nil
+}
+
+func (i *sessionsCmp) Focus() tea.Cmd {
+ i.focused = true
+ return nil
+}
+
+func (i *sessionsCmp) GetSize() (int, int) {
+ return i.list.Width(), i.list.Height()
+}
+
+func (i *sessionsCmp) IsFocused() bool {
+ return i.focused
+}
+
+func (i *sessionsCmp) SetSize(width int, height int) {
+ i.list.SetSize(width, height)
+}
+
+func (i *sessionsCmp) BorderText() map[layout.BorderPosition]string {
+ totalCount := len(i.list.Items())
+ itemsPerPage := i.list.Paginator.PerPage
+ currentPage := i.list.Paginator.Page
+
+ current := min(currentPage*itemsPerPage+itemsPerPage, totalCount)
+
+ pageInfo := fmt.Sprintf(
+ "%d-%d of %d",
+ currentPage*itemsPerPage+1,
+ current,
+ totalCount,
+ )
+ return map[layout.BorderPosition]string{
+ layout.TopMiddleBorder: "Sessions",
+ layout.BottomMiddleBorder: pageInfo,
+ }
+}
+
+func (i *sessionsCmp) BindingKeys() []key.Binding {
+ return layout.KeyMapToSlice(i.list.KeyMap)
+}
+
+func NewSessionsCmp(app *app.App) SessionsCmp {
+ listDelegate := list.NewDefaultDelegate()
+ defaultItemStyle := list.NewDefaultItemStyles()
+ defaultItemStyle.SelectedTitle = defaultItemStyle.SelectedTitle.BorderForeground(styles.Secondary).Foreground(styles.Primary)
+ defaultItemStyle.SelectedDesc = defaultItemStyle.SelectedDesc.BorderForeground(styles.Secondary).Foreground(styles.Primary)
+
+ defaultStyle := list.DefaultStyles()
+ defaultStyle.FilterPrompt = defaultStyle.FilterPrompt.Foreground(styles.Secondary)
+ defaultStyle.FilterCursor = defaultStyle.FilterCursor.Foreground(styles.Flamingo)
+
+ listDelegate.Styles = defaultItemStyle
+
+ listComponent := list.New([]list.Item{}, listDelegate, 0, 0)
+ listComponent.FilterInput.PromptStyle = defaultStyle.FilterPrompt
+ listComponent.FilterInput.Cursor.Style = defaultStyle.FilterCursor
+ listComponent.SetShowTitle(false)
+ listComponent.SetShowPagination(false)
+ listComponent.SetShowHelp(false)
+ listComponent.SetShowStatusBar(false)
+ listComponent.DisableQuitKeybindings()
+
+ return &sessionsCmp{
+ app: app,
+ list: listComponent,
+ focused: false,
+ }
+}
diff --git a/internal/tui/components/repl/threads.go b/internal/tui/components/repl/threads.go
deleted file mode 100644
index aa2bc080b..000000000
--- a/internal/tui/components/repl/threads.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package repl
-
-import tea "github.com/charmbracelet/bubbletea"
-
-type threadsCmp struct{}
-
-func (i *threadsCmp) Init() tea.Cmd {
- return nil
-}
-
-func (i *threadsCmp) Update(_ tea.Msg) (tea.Model, tea.Cmd) {
- return i, nil
-}
-
-func (i *threadsCmp) View() string {
- return "Threads"
-}
-
-func NewThreadsCmp() tea.Model {
- return &threadsCmp{}
-}