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 } func FindParagraphEnd(text string, startPos int) int { if startPos >= len(text) { return 0 } inCodeBlock := false inMathBlock := false for i := startPos; i < len(text); i++ { if i+3 < len(text) && text[i:i+3] == "```" { if !inCodeBlock { inCodeBlock = true } else { inCodeBlock = false } continue } if i+2 < len(text) && (text[i:i+2] == "$$" || text[i:i+2] == "\\[") { inMathBlock = !inMathBlock continue } if inCodeBlock || inMathBlock { continue } if i+1 < len(text) && text[i] == '\n' && text[i+1] == '\n' { return i + 2 } } return 0 }