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:
parent
f540f5fc24
commit
28f75965bb
198
cmd/chat.go
198
cmd/chat.go
@ -1,14 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"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/spf13/cobra"
|
||||
"gmgauthier.com/grokkit/config"
|
||||
@ -17,162 +14,51 @@ import (
|
||||
|
||||
var chatCmd = &cobra.Command{
|
||||
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) {
|
||||
modelFlag, _ := cmd.Flags().GetString("model")
|
||||
model := config.GetModel(modelFlag)
|
||||
|
||||
p := tea.NewProgram(initialModel(model), tea.WithAltScreen(), tea.WithMouseCellMotion())
|
||||
if _, err := p.Run(); err != nil {
|
||||
color.Red("TUI error: %v", err)
|
||||
os.Exit(1)
|
||||
client := grok.NewClient()
|
||||
|
||||
// Strong system prompt to lock in correct model identity
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user