diff --git a/cmd/chat.go b/cmd/chat.go index aeff933..7dd2d82 100644 --- a/cmd/chat.go +++ b/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(), - ) -}