feat: 实现流式输出功能 v0.1.0
- 创建 hxclaw 项目,基于 picoclaw 的 CLI 增强工具 - 实现流式输出,使用 fmt.Print + os.Stdout.Sync() 实时刷新 - 解决 onChunk 回调累积文本导致的重复输出问题 - 使用 strings.Builder 收集完整响应并保存到 session - 添加讨论记录和更新日志文档
This commit is contained in:
120
cmd/hxclaw/internal/helpers.go
Normal file
120
cmd/hxclaw/internal/helpers.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ergochat/readline"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrInterrupt = errors.New("interrupt")
|
||||
ErrEOF = errors.New("EOF")
|
||||
)
|
||||
|
||||
// Logo 是 hxclaw 的 Logo
|
||||
const Logo = "🦐"
|
||||
|
||||
// GetPicoclawHome 返回 picoclaw 的家目录
|
||||
// 优先级: $PICOCLAW_HOME > ~/.picoclaw
|
||||
func GetPicoclawHome() string {
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
// LoadConfig 加载配置文件
|
||||
// 复用 picoclaw 的配置加载逻辑
|
||||
func LoadConfig() (*config.Config, error) {
|
||||
cfg, err := config.LoadConfig(GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.SetLevelFromString(cfg.Gateway.LogLevel)
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// GetConfigPath 获取配置文件路径
|
||||
func GetConfigPath() string {
|
||||
if configPath := os.Getenv(config.EnvConfig); configPath != "" {
|
||||
return configPath
|
||||
}
|
||||
return filepath.Join(GetPicoclawHome(), "config.json")
|
||||
}
|
||||
|
||||
// Readline 实例包装
|
||||
type Readline struct {
|
||||
rl *readline.Instance
|
||||
}
|
||||
|
||||
// NewReadline 创建一个新的 Readline 实例
|
||||
func NewReadline(prompt string) (*Readline, error) {
|
||||
// 确保历史文件目录存在
|
||||
historyDir := filepath.Dir(filepath.Join(GetPicoclawHome(), ".hxclaw_history"))
|
||||
os.MkdirAll(historyDir, 0755)
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: prompt,
|
||||
HistoryFile: filepath.Join(GetPicoclawHome(), ".hxclaw_history"),
|
||||
HistoryLimit: 100,
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Readline{rl: rl}, nil
|
||||
}
|
||||
|
||||
// Readline 读取一行输入
|
||||
func (r *Readline) Readline() (string, error) {
|
||||
line, err := r.rl.Readline()
|
||||
if err != nil {
|
||||
if err == readline.ErrInterrupt {
|
||||
return "", ErrInterrupt
|
||||
}
|
||||
if err == io.EOF {
|
||||
return "", ErrEOF
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// Close 关闭 Readline 实例
|
||||
func (r *Readline) Close() error {
|
||||
return r.rl.Close()
|
||||
}
|
||||
|
||||
// SimpleReader 简单输入读取器(无历史记录)
|
||||
type SimpleReader struct {
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
// NewSimpleReader 创建一个新的简单读取器
|
||||
func NewSimpleReader() *SimpleReader {
|
||||
return &SimpleReader{
|
||||
reader: bufio.NewReader(os.Stdin),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadString 读取一行输入
|
||||
func (r *SimpleReader) ReadString() (string, error) {
|
||||
line, err := r.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return "", ErrEOF
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
// 去掉换行符
|
||||
if len(line) > 0 && line[len(line)-1] == '\n' {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
Reference in New Issue
Block a user