refactor(chat): simplify to CLI interface

Replace Bubble Tea TUI with a simple bufio-based CLI loop for chatting.
Add strong system prompt to history for model identity.
Update quitting logic and output formatting with colors.
This commit is contained in:
Greg Gauthier 2026-02-28 23:03:53 +00:00
parent f540f5fc24
commit 28f75965bb

View File

@ -1,14 +1,11 @@
package cmd package cmd
import ( import (
"bufio"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gmgauthier.com/grokkit/config" "gmgauthier.com/grokkit/config"
@ -17,162 +14,51 @@ import (
var chatCmd = &cobra.Command{ var chatCmd = &cobra.Command{
Use: "chat", Use: "chat",
Short: "Beautiful interactive TUI chat with Grok (full history + streaming)", Short: "Simple interactive CLI chat with Grok (full history + streaming)",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
modelFlag, _ := cmd.Flags().GetString("model") modelFlag, _ := cmd.Flags().GetString("model")
model := config.GetModel(modelFlag) model := config.GetModel(modelFlag)
p := tea.NewProgram(initialModel(model), tea.WithAltScreen(), tea.WithMouseCellMotion()) client := grok.NewClient()
if _, err := p.Run(); err != nil {
color.Red("TUI error: %v", err) // Strong system prompt to lock in correct model identity
os.Exit(1) systemPrompt := map[string]string{
"role": "system",
"content": fmt.Sprintf("You are Grok 4, the latest and most powerful model from xAI (2026). You are currently running as `%s`. Be helpful, truthful, and a little irreverent. Never claim to be an older model.", model),
}
history := []map[string]string{systemPrompt}
color.Cyan("┌──────────────────────────────────────────────────────────────┐")
color.Cyan("│ Grokkit Chat — Model: %s │", model)
color.Cyan("│ Type /quit or Ctrl+C to exit │")
color.Cyan("└──────────────────────────────────────────────────────────────┘\n")
scanner := bufio.NewScanner(os.Stdin)
for {
color.Yellow("You > ")
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}
if input == "/quit" || input == "/q" || input == "exit" {
color.Cyan("\nGoodbye 👋\n")
break
}
history = append(history, map[string]string{"role": "user", "content": input})
color.Green("Grok > ")
reply := client.Stream(history, model)
history = append(history, map[string]string{"role": "assistant", "content": reply})
fmt.Println()
} }
}, },
} }
type replyMsg string
type model struct {
viewport viewport.Model
textarea textarea.Model
history []string
client *grok.Client
model string
width int
height int
quitting bool
}
func initialModel(modelName string) model {
ta := textarea.New()
ta.Placeholder = "Type your message... (Enter to send, /quit to exit)"
ta.Focus()
ta.SetHeight(3)
ta.ShowLineNumbers = false
vp := viewport.New(0, 0)
return model{
textarea: ta,
viewport: vp,
history: []string{},
client: grok.NewClient(),
model: modelName,
}
}
func (m model) Init() tea.Cmd {
return textarea.Blink
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - m.textarea.Height() - 5
m.textarea.SetWidth(msg.Width - 4)
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.quitting = true
return m, tea.Quit
case "enter":
input := strings.TrimSpace(m.textarea.Value())
if input == "" {
return m, nil
}
if input == "/quit" || input == "/q" {
m.quitting = true
return m, tea.Quit
}
m.history = append(m.history, "You > "+input)
m.viewport.SetContent(buildHistoryView(m.history))
m.viewport.GotoBottom()
m.textarea.SetValue("")
historyForAPI := buildHistoryForAPI(m.history)
cmds = append(cmds, streamReplyCmd(m.client, historyForAPI, m.model))
return m, tea.Batch(cmds...)
}
case replyMsg:
m.history = append(m.history, "Grok > "+string(msg))
m.viewport.SetContent(buildHistoryView(m.history))
m.viewport.GotoBottom()
return m, nil
}
m.textarea, cmd = m.textarea.Update(msg)
cmds = append(cmds, cmd)
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func buildHistoryView(lines []string) string {
userStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("33"))
grokStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("10"))
var b strings.Builder
for _, line := range lines {
if strings.HasPrefix(line, "You > ") {
b.WriteString(userStyle.Render(line) + "\n")
} else if strings.HasPrefix(line, "Grok > ") {
b.WriteString(grokStyle.Render(line) + "\n")
} else {
b.WriteString(line + "\n")
}
}
return b.String()
}
func buildHistoryForAPI(lines []string) []map[string]string {
h := []map[string]string{
{"role": "system", "content": fmt.Sprintf("You are Grok 4, the latest model from xAI (2026). You are currently running as `%s`.", "grok-4")},
}
for _, line := range lines {
if strings.HasPrefix(line, "You > ") {
h = append(h, map[string]string{"role": "user", "content": strings.TrimPrefix(line, "You > ")})
} else if strings.HasPrefix(line, "Grok > ") {
h = append(h, map[string]string{"role": "assistant", "content": strings.TrimPrefix(line, "Grok > ")})
}
}
return h
}
func streamReplyCmd(client *grok.Client, history []map[string]string, model string) tea.Cmd {
return func() tea.Msg {
reply := client.StreamSilent(history, model)
return replyMsg(reply)
}
}
func (m model) View() string {
if m.quitting {
return "Goodbye 👋\n"
}
header := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("12")).
Width(m.width).
Align(lipgloss.Center).
Render(fmt.Sprintf(" Grokkit Chat — Model: %s ", m.model))
return lipgloss.JoinVertical(lipgloss.Left,
header,
m.viewport.View(),
m.textarea.View(),
)
}