Skip to content

Commit

Permalink
feat: adds formatting for parse responses.
Browse files Browse the repository at this point in the history
This only includes questions, and doesn't yet provide the logic for
handling multiple questions, and displaying actions.
  • Loading branch information
tartavull committed Jun 18, 2023
1 parent 4ee570e commit a3b3b8a
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 45 deletions.
43 changes: 31 additions & 12 deletions mods/auto/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/cursor"
"github.com/charmbracelet/mods/common"


)

type Auto struct {
Expand All @@ -17,14 +19,24 @@ type Auto struct {
}

func New(s common.Styles) *Auto {
ta := textarea.New()
ta.Placeholder = ">"
ta.Focus()

return &Auto{
textarea: ta,
a := &Auto{
textarea: textarea.New(),
styles: s,
}

a.textarea.Placeholder = ">"
a.textarea.ShowLineNumbers = false
a.textarea.Focus()

testJson := `
{
"context": "Some context",
"goal": "Some goal",
"actions": [{"question":"Some question"}]
}`
a.Response, _ = a.ParseResponse(testJson)

return a
}

func (a *Auto) Focus() tea.Cmd {
Expand All @@ -33,7 +45,7 @@ func (a *Auto) Focus() tea.Cmd {

func (a *Auto) SetSize(width, height int) {
a.textarea.SetWidth(width)
a.textarea.SetHeight(height/2)
a.textarea.MaxHeight = height / 2
}

func (a *Auto) Update(msg tea.Msg) (*Auto, tea.Cmd) {
Expand All @@ -56,11 +68,18 @@ func (a *Auto) Update(msg tea.Msg) (*Auto, tea.Cmd) {
}

func (a *Auto) View() string {
return fmt.Sprintf("\n%s\n\n%s\n%s",
"Question?",
a.textarea.View(),
"Press ctrl+d to submit answer",
)
view := ""

view += a.styles.ContextTag.String() + " " + a.styles.Context.Render(a.Response.Context)
view += "\n\n"
view += a.styles.GoalTag.String() + " " + a.styles.Goal.Render(a.Response.Goal)
view += "\n\n"
view += a.styles.QuestionTag.String() + " " + a.styles.Question.Render(a.Response.Actions[0].String())
view += "\n\n"
view += a.textarea.View()
view += "\n"
view += a.styles.Comment.Render("press ctrl+d to submit answer")
return a.styles.App.Render(view)
}

func (a *Auto) AddPrompt(prompt string) string {
Expand Down
42 changes: 29 additions & 13 deletions mods/auto/auto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package auto

import (
"testing"
"github.com/charmbracelet/mods/common"
"github.com/charmbracelet/lipgloss"
)

func TestAddPrompt(t *testing.T) {
a := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
msg := "Hello, world"
result := a.AddPrompt(msg)
if len(result) <= len(msg) {
Expand All @@ -14,83 +18,95 @@ func TestAddPrompt(t *testing.T) {
}

func TestParseResponse(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `{
"context": "Some context",
"goal": "Some goal",
"actions": [{"question":"Some question"}]
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err != nil {
t.Errorf("Error parsing JSON: %v", err)
}
}

func TestParseCmd(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `
{
"context": "Some context",
"goal": "Some goal",
"actions": [{"cmd":"Some cmd"}]
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err != nil {
t.Errorf("Error parsing JSON: %v", err)
}
}

func TestParseQuestion(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `
{
"context": "Some context",
"goal": "Some goal",
"actions": [{"question":"Some question"}]
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err != nil {
t.Errorf("Error parsing JSON: %v", err)
}
}

func TestParseMissingField(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `
{
"context": "Some context",
"actions": [{"question":"Some question"}]
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err == nil || err.Error() != "missing field 'goal' in JSON" {
t.Errorf("Expected error due to missing field but got: %v", err)
}
}

func TestParseExtraField(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `
{
"context": "Some context",
"goal": "Some goal",
"actions": [{"question":"Some question"}],
"responses": "Some response"
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err == nil || err.Error() != "extra field 'responses' in JSON" {
t.Errorf("Expected error due to extra field but got: %v", err)
}
}

func TestParseInvalidAction(t *testing.T) {
auto := New()
r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
a := New(s)
testJson := `
{
"context": "Some context",
"goal": "Some goal",
"actions": [{"invalid":"Some invalid action"}]
}`
err := auto.ParseResponse(testJson)
_, err := a.ParseResponse(testJson)
if err == nil || err.Error() != "actions must contain either a valid 'question' or 'cmd' object" {
t.Errorf("Expected error due to invalid action but got: %v", err)
}
Expand Down
23 changes: 15 additions & 8 deletions mods/auto/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type LLMResponse struct {

type Action interface {
IsValid() bool
String() string
}

type Question struct {
Expand All @@ -28,10 +29,18 @@ func (q Question) IsValid() bool {
return q.Question != ""
}

func (q Question) String() string {
return q.Question
}

func (c Cmd) IsValid() bool {
return c.Cmd != ""
}

func (c Cmd) String() string {
return c.Cmd
}

func (c *LLMResponse) UnmarshalJSON(data []byte) error {
type Alias LLMResponse
aux := &struct {
Expand Down Expand Up @@ -61,27 +70,27 @@ func (c *LLMResponse) UnmarshalJSON(data []byte) error {
return nil
}

func (a *Auto) ParseResponse(jsonStr string) error {
func (a *Auto) ParseResponse(jsonStr string) (*LLMResponse, error) {
jsonBlob := []byte(jsonStr)

response := LLMResponse{}
err := json.Unmarshal([]byte(jsonBlob), &response)

if err != nil {
return err
return nil, err
}

// Create a map to hold the JSON data
var data map[string]json.RawMessage
if err := json.Unmarshal([]byte(jsonBlob), &data); err != nil {
return err
return nil, err
}

// Check for extra fields
responseType := reflect.TypeOf(response)
for key := range data {
if !structHasField(responseType, key) {
return fmt.Errorf("extra field '%s' in JSON", key)
return nil, fmt.Errorf("extra field '%s' in JSON", key)
}
}

Expand All @@ -90,12 +99,10 @@ func (a *Auto) ParseResponse(jsonStr string) error {
field := responseType.Field(i)
_, ok := data[field.Tag.Get("json")]
if !ok {
return fmt.Errorf("missing field '%s' in JSON", field.Tag.Get("json"))
return nil, fmt.Errorf("missing field '%s' in JSON", field.Tag.Get("json"))
}
}

a.Response = &response
return nil
return &response, nil
}

// Helper function to check if a JSON field name matches any struct fields
Expand Down
18 changes: 18 additions & 0 deletions mods/common/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

type Styles struct {
AppName lipgloss.Style
App lipgloss.Style
CliArgs lipgloss.Style
Comment lipgloss.Style
CyclingChars lipgloss.Style
Expand All @@ -18,10 +19,18 @@ type Styles struct {
Link lipgloss.Style
Pipe lipgloss.Style
Quote lipgloss.Style

Context lipgloss.Style
ContextTag lipgloss.Style
Goal lipgloss.Style
GoalTag lipgloss.Style
Question lipgloss.Style
QuestionTag lipgloss.Style
}

func MakeStyles(r *lipgloss.Renderer) (s Styles) {
s.AppName = r.NewStyle().Bold(true)
s.App = r.NewStyle().Margin(1, 2)
s.CliArgs = r.NewStyle().Foreground(lipgloss.Color("#585858"))
s.Comment = r.NewStyle().Foreground(lipgloss.Color("#757575"))
s.CyclingChars = r.NewStyle().Foreground(lipgloss.Color("#FF87D7"))
Expand All @@ -34,5 +43,14 @@ func MakeStyles(r *lipgloss.Renderer) (s Styles) {
s.Link = r.NewStyle().Foreground(lipgloss.Color("#00AF87")).Underline(true)
s.Quote = r.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#FF71D0", Dark: "#FF78D2"})
s.Pipe = r.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#8470FF", Dark: "#745CFF"})

s.Context = s.CliArgs.Copy()
s.ContextTag = s.Context.Copy().Bold(true).SetString("Context:")

s.Goal = r.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#00B594", Dark: "#3EEFCF"})
s.GoalTag = s.Goal.Copy().Bold(true).SetString("Goal:")

s.Question = s.Quote.Copy()
s.QuestionTag = s.Question.Copy().Bold(true).SetString("Question:")
return s
}
10 changes: 8 additions & 2 deletions mods/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,21 @@ func newConfig() (config, error) {
flag.StringVar(&c.StatusText, "status-text", c.StatusText, help["status-text"])
flag.BoolVar(&c.Auto, "auto", c.Auto, help["auto"])
flag.Lookup("prompt").NoOptDefVal = "-1"
flag.Usage = usage
flag.Usage = func() {
usage(c)
}
flag.CommandLine.SortFlags = false
flag.Parse()
c.Prefix = strings.Join(flag.Args(), " ")

return c, nil
}

func usage() {
func usage(c config) {
if c.Auto {
return
}

r := lipgloss.DefaultRenderer()
s := common.MakeStyles(r)
appName := filepath.Base(os.Args[0])
Expand Down
12 changes: 2 additions & 10 deletions mods/mods.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,8 @@ func (m *Mods) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.state = stateCompletion
return m, m.startCompletionCmd(msg.content)
case completionOutput:
if m.Config.Auto {
// we do this such that it can be pretty printed by glow
m.auto.ParseResponse(msg.content)
m.Output = "```json\n" + msg.content + "\n```"
//m.state = questionState
//return m, tea.Quit
} else {
m.Output = msg.content
return m, tea.Quit
}
m.Output = msg.content
return m, tea.Quit
case modsError:
m.Error = &msg
m.state = stateError
Expand Down

0 comments on commit a3b3b8a

Please sign in to comment.