From 8346fb179c98e7cd109f39e62dec1c01366c2c3a Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sat, 17 May 2025 01:00:40 +0800 Subject: [PATCH] feat: add chat style --- internal/version/VERSION | 2 +- parser.go | 3 -- pkg/mcphost/chat.go | 65 ++++++++++++++++++++++++++++------------ pkg/mcphost/chat_test.go | 18 +++++------ 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/internal/version/VERSION b/internal/version/VERSION index 8a74caf0..aa8eb441 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2505170008 +v5.0.0-beta-2505170100 diff --git a/parser.go b/parser.go index ba1ea788..d74cac9e 100644 --- a/parser.go +++ b/parser.go @@ -311,9 +311,6 @@ func (p *Parser) CallMCPTool(ctx context.Context, serverName, return nil, fmt.Errorf("mcphost is not initialized") } - tools := p.MCPHost.GetTools(ctx) - log.Warn().Interface("tools", tools).Msg("tools") - result, err := p.MCPHost.InvokeTool(ctx, serverName, funcName, arguments) if err != nil { return nil, errors.Wrapf(err, "invoke tool %s/%s failed", serverName, funcName) diff --git a/pkg/mcphost/chat.go b/pkg/mcphost/chat.go index 91dba14d..ba31c922 100644 --- a/pkg/mcphost/chat.go +++ b/pkg/mcphost/chat.go @@ -1,7 +1,6 @@ package mcphost import ( - "bufio" "context" "fmt" "os" @@ -10,6 +9,7 @@ import ( "github.com/bytedance/sonic" "github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour/styles" + "github.com/charmbracelet/huh" "github.com/charmbracelet/huh/spinner" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/list" @@ -24,8 +24,8 @@ import ( "golang.org/x/term" ) -// Tokyo Night theme colors var ( + // Tokyo Night theme colors tokyoPurple = lipgloss.Color("99") // #9d7cd8 tokyoCyan = lipgloss.Color("73") // #7dcfff tokyoBlue = lipgloss.Color("111") // #7aa2f7 @@ -36,6 +36,18 @@ var ( tokyoGray = lipgloss.Color("237") // #3b4261 tokyoBg = lipgloss.Color("234") // #1a1b26 + promptStyle = lipgloss.NewStyle(). + Foreground(tokyoBlue). + PaddingLeft(2) + + responseStyle = lipgloss.NewStyle(). + Foreground(tokyoFg). + PaddingLeft(2) + + errorStyle = lipgloss.NewStyle(). + Foreground(tokyoRed). + Bold(true) + toolNameStyle = lipgloss.NewStyle(). Foreground(tokyoCyan). Bold(true) @@ -127,10 +139,25 @@ func (c *Chat) Start() error { c.showWelcome() for { - fmt.Print("\nYou: ") - input, err := readInput() + var input string + err := huh.NewForm(huh.NewGroup(huh.NewText(). + Title("Enter your prompt (Type /help for commands, Ctrl+C to quit)"). + Value(&input). + CharLimit(5000)), + ).WithWidth(getTerminalWidth()). + WithTheme(huh.ThemeCharm()). + Run() if err != nil { - return err + // Check if it's a user abort (Ctrl+C) + if errors.Is(err, huh.ErrUserAborted) { + fmt.Println("\nGoodbye!") + return nil // Exit cleanly + } + return err // Return other errors normally + } + + if input == "" { + continue } // Handle commands @@ -150,6 +177,8 @@ func (c *Chat) Start() error { // runPrompt run prompt with MCP tools func (c *Chat) runPrompt(prompt string) error { + fmt.Printf("\n%s\n", promptStyle.Render("You: "+prompt)) + // Create user message userMsg := &schema.Message{ Role: schema.User, @@ -158,8 +187,12 @@ func (c *Chat) runPrompt(prompt string) error { c.history = append(c.history, userMsg) for { ctx := context.Background() - spinner.New().Type(spinner.Dots).Title("Thinking...").Run() - resp, err := c.model.Generate(ctx, c.history) + var resp *schema.Message + var err error + action := func() { + resp, err = c.model.Generate(ctx, c.history) + } + _ = spinner.New().Title("Thinking...").Action(action).Run() if err != nil { return err } @@ -214,10 +247,11 @@ func (c *Chat) runPrompt(prompt string) error { // Render and display response if rendered, err := c.renderer.Render(resp.Content); err == nil { - fmt.Printf("\nAssistant: %s\n", rendered) + fmt.Printf("\n%s", responseStyle.Render("Assistant: "+rendered)) } else { - fmt.Printf("\nAssistant: %s\n", resp.Content) + fmt.Printf("\n%s", errorStyle.Render("Assistant: "+resp.Content)) } + return nil } } @@ -328,7 +362,9 @@ func (c *Chat) showTools() { } else { for _, tool := range serverTools.Tools { descStyle := lipgloss.NewStyle().Foreground(tokyoFg).Width(contentWidth).Align(lipgloss.Left) - toolDesc := list.New().EnumeratorStyle(lipgloss.NewStyle().Foreground(tokyoGreen).MarginRight(1)).Item(descStyle.Render(tool.Description)) + toolDesc := list.New().EnumeratorStyle( + lipgloss.NewStyle().Foreground(tokyoGreen).MarginRight(1), + ).Item(descStyle.Render(tool.Description)) serverList.Item(toolNameStyle.Render(tool.Name)).Item(toolDesc) } } @@ -354,15 +390,6 @@ func loadSystemPrompt(filePath string) (string, error) { return string(data), nil } -func readInput() (string, error) { - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - return "", err - } - return strings.TrimSpace(input), nil -} - func getTerminalWidth() int { width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { diff --git a/pkg/mcphost/chat_test.go b/pkg/mcphost/chat_test.go index 3709be8d..3df8dd7a 100644 --- a/pkg/mcphost/chat_test.go +++ b/pkg/mcphost/chat_test.go @@ -24,17 +24,17 @@ func TestNewChat(t *testing.T) { assert.NotNil(t, chat.tools) } -// func TestRunPromptWithNoToolCall(t *testing.T) { -// host, err := NewMCPHost("./testdata/test.mcp.json") -// require.NoError(t, err) +func TestRunPromptWithNoToolCall(t *testing.T) { + host, err := NewMCPHost("./testdata/test.mcp.json") + require.NoError(t, err) -// chat, err := host.NewChat(context.Background(), "") -// assert.NoError(t, err) + chat, err := host.NewChat(context.Background(), "") + assert.NoError(t, err) -// err = chat.runPrompt("hi") -// assert.NoError(t, err) -// assert.True(t, len(chat.history) > 1) -// } + err = chat.runPrompt("hi") + assert.NoError(t, err) + assert.True(t, len(chat.history) > 1) +} // func TestRunPromptWithToolCall(t *testing.T) { // host, err := NewMCPHost("./testdata/test.mcp.json")