diff --git a/talk/auto/auto.go b/talk/auto/auto.go index 44efd1a..b4938a1 100644 --- a/talk/auto/auto.go +++ b/talk/auto/auto.go @@ -1,14 +1,12 @@ package auto import ( - "fmt" "log" - "io/ioutil" "strings" + "os" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/cursor" "github.com/mitchellh/go-wordwrap" "github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/lipgloss" @@ -26,9 +24,11 @@ const ( ) type Auto struct { - common common.Common + common *common.Common styles common.Styles + picked pane tabs *tabs.Tabs + panes []tea.Model textarea textarea.Model state State @@ -41,13 +41,18 @@ type Auto struct { height int } -func New(c common.Common, s common.Styles) *Auto { +func New(c *common.Common, s common.Styles) *Auto { a := &Auto{ common: c, textarea: textarea.New(), styles: s, - tabs: tabs.New(c, []string{"chat", "history"}), + picked: paneChat, viewport: viewport.New(0, 0), + tabs: InitTabs(c), + panes: []tea.Model{ + nil, + nil, + }, } a.viewport.YPosition = 0 @@ -55,7 +60,7 @@ func New(c common.Common, s common.Styles) *Auto { a.textarea.ShowLineNumbers = false a.textarea.Focus() - data, err := ioutil.ReadFile("chat/llm_1.json") + data, err := os.ReadFile("chat/llm_1.json") if err != nil { log.Fatal(err) } @@ -67,20 +72,65 @@ func New(c common.Common, s common.Styles) *Auto { return a } +func (a *Auto) Init() tea.Cmd { + return nil +} + +type pane int + +const ( + paneChat pane = iota + paneHistory + paneLast +) + +func (p pane) String() string { + return []string{ + "Chat", + "History", + }[p] +} + +func InitTabs(c *common.Common) *tabs.Tabs { + ts := make([]string, paneLast) + for i, b := range []pane{paneChat, paneHistory} { + ts[i] = b.String() + } + t := tabs.New(c, ts) + return t +} + func (a *Auto) Focus() tea.Cmd { return textarea.Blink } func (a *Auto) SetSize(width, height int) { - a.width = width - a.height = height + a.common.SetSize(width, height) a.textarea.SetWidth(width) a.textarea.MaxHeight = height / 2 a.viewport.Width = width a.viewport.Height = height } +func (a *Auto) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + cmds := make([]tea.Cmd, 0) + switch msg := msg.(type) { + case tabs.ActiveTabMsg: + a.picked = pane(msg) + } + model, cmd := a.tabs.Update(msg) + cmds = append(cmds, cmd) + a.tabs = model.(*tabs.Tabs) + + /* + model, cmd = a.panes[a.picked].Update(msg) + cmds = append(cmds, cmd) + a.panes[a.picked] = model + */ + return a, tea.Batch(cmds...) +} +/* func (a *Auto) Update(msg tea.Msg) (*Auto, tea.Cmd) { var cmd tea.Cmd cmds := make([]tea.Cmd, 0) @@ -120,38 +170,46 @@ func (a *Auto) Update(msg tea.Msg) (*Auto, tea.Cmd) { } return a, tea.Batch(cmds...) } +*/ func (a *Auto) wrap(in string) string { return wordwrap.WrapString(in, uint(a.textarea.Width()/2)) } func (a *Auto) View() string { - // FIXME use string builder - view := a.styles.Logo.Render(" Alfredo ") - view += "\n\n" - view += a.tabs.View() - view += "\n\n" - view += a.styles.ContextTag.String() + " " + a.styles.Context.Render(a.wrap(a.Response.Context)) - view += "\n\n" - view += a.styles.GoalTag.String() + " " + a.styles.Goal.Render(a.wrap(a.Response.Goal)) - view += "\n\n" + var builder strings.Builder + + builder.WriteString(a.styles.Logo.Render(" Alfredo ")) + builder.WriteString("\n\n") + builder.WriteString(a.tabs.View()) + builder.WriteString("\n\n") + builder.WriteString(a.styles.ContextTag.String() + " " + a.styles.Context.Render(a.wrap(a.Response.Context))) + /* + builder.WriteString("\n\n") + builder.WriteString(a.styles.GoalTag.String() + " " + a.styles.Goal.Render(a.wrap(a.Response.Goal))) + builder.WriteString("\n\n") + if a.state == stateAnswering { if len(a.outputs) < len(a.Response.Commands) { - var styledCmds []string - for _, cmd := range a.Response.Commands { - styledCmds = append(styledCmds, fmt.Sprintf("$ %s", a.wrap(cmd))) - } - styledCmdStr := strings.Join(styledCmds, "\n") - view += a.styles.Command.Render(styledCmdStr) + "\n\n" - view += a.styles.Comment.Render("Press y to execute all commands or esc to exit") + var styledCmds []string + for _, cmd := range a.Response.Commands { + styledCmds = append(styledCmds, fmt.Sprintf("$ %s", a.wrap(cmd))) + } + styledCmdStr := strings.Join(styledCmds, "\n") + builder.WriteString(a.styles.Command.Render(styledCmdStr) + "\n\n") + builder.WriteString(a.styles.Comment.Render("Press y to execute all commands or esc to exit")) } else if len(a.answers) < len(a.Response.Questions) { question := a.Response.Questions[len(a.answers)] - view += a.styles.QuestionTag.String() + " " + a.styles.Question.Render(a.wrap(question)) - view += "\n\n" - view += a.textarea.View() - view += "\n" - view += a.styles.Comment.Render(fmt.Sprintf("Question %d of %d: press ctrl+d to submit answer", len(a.answers)+1, len(a.Response.Questions))) + builder.WriteString(a.styles.QuestionTag.String() + " " + a.styles.Question.Render(a.wrap(question))) + builder.WriteString("\n\n") + builder.WriteString(a.textarea.View()) + builder.WriteString("\n") + builder.WriteString(a.styles.Comment.Render(fmt.Sprintf("Question %d of %d: press ctrl+d to submit answer", len(a.answers)+1, len(a.Response.Questions)))) } } - return lipgloss.Place(a.width, a.height, lipgloss.Left, lipgloss.Top, a.styles.App.Render(view)) + */ + + // Use String method to get the final string + view := builder.String() + return lipgloss.Place(a.common.Width, a.common.Height, lipgloss.Left, lipgloss.Top, a.styles.App.Render(view)) } diff --git a/talk/common/styles.go b/talk/common/styles.go index 5c6b231..c091c76 100644 --- a/talk/common/styles.go +++ b/talk/common/styles.go @@ -60,6 +60,10 @@ type Styles struct { TabInactive Style TabActive Style TabSeparator Style + + TopLevelNormalTab Style + TopLevelActiveTab Style + TopLevelActiveTabDot Style } func MakeStyles(r *Renderer) (s Styles) { @@ -107,5 +111,16 @@ func MakeStyles(r *Renderer) (s Styles) { SetString("│"). Padding(0, 1). Foreground(Color("238")) + + + s.TopLevelNormalTab = NewStyle(). + MarginRight(2) + + s.TopLevelActiveTab = s.TopLevelNormalTab.Copy(). + Foreground(Color("36")) + + s.TopLevelActiveTabDot = NewStyle(). + Foreground(Color("36")) + return s } diff --git a/talk/components/chat/chat.go b/talk/components/chat/chat.go new file mode 100644 index 0000000..c93f630 --- /dev/null +++ b/talk/components/chat/chat.go @@ -0,0 +1,8 @@ +package chat + +type Chat struct { +} + +func New() *Chat { + return &Chat{} +} diff --git a/talk/components/tabs/tabs.go b/talk/components/tabs/tabs.go index e6d08bb..993abb5 100644 --- a/talk/components/tabs/tabs.go +++ b/talk/components/tabs/tabs.go @@ -16,7 +16,7 @@ type ActiveTabMsg int // Tabs is bubbletea component that displays a list of tabs. type Tabs struct { - common common.Common + common *common.Common tabs []string activeTab int TabSeparator lipgloss.Style @@ -27,7 +27,7 @@ type Tabs struct { } // New creates a new Tabs component. -func New(c common.Common, tabs []string) *Tabs { +func New(c *common.Common, tabs []string) *Tabs { r := &Tabs{ common: c, tabs: tabs, @@ -86,28 +86,25 @@ func (t *Tabs) View() string { s := strings.Builder{} sep := t.TabSeparator for i, tab := range t.tabs { - style := t.TabInactive.Copy() - prefix := " " + style := t.TabInactive + prefix := " " if i == t.activeTab { - style = t.TabActive.Copy() - prefix = t.TabDot.Render("• ") + style = t.TabActive + prefix = t.TabDot.Render(" • ") } if t.UseDot { s.WriteString(prefix) } - s.WriteString( - t.common.Zone.Mark( - tab, - style.Render(tab), - ), - ) + s.WriteString(style.Render(tab)) if i != len(t.tabs)-1 { s.WriteString(sep.String()) } } + + out := s.String() return lipgloss.NewStyle(). MaxWidth(t.common.Width). - Render(s.String()) + Render(out) } func (t *Tabs) activeTabCmd() tea.Msg { diff --git a/talk/mods.go b/talk/mods.go index 7d01a96..2b4e93b 100644 --- a/talk/mods.go +++ b/talk/mods.go @@ -56,7 +56,7 @@ func newMods(r *lipgloss.Renderer) *Mods { state: stateStart, renderer: r, styles: s, - auto: auto.New(c, s), + auto: auto.New(&c, s), } } @@ -121,9 +121,9 @@ func (m *Mods) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } if m.Config.Auto { - var cmd tea.Cmd m.state = stateAuto - m.auto, cmd = m.auto.Update(msg) + model, cmd := m.auto.Update(msg) + m.auto = model.(*auto.Auto) return m, cmd } if m.state == stateConfigLoaded || m.state == stateCompletion {