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
|
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user