// Package internal 包含 hxclaw 的内部工具模块 // 提供配置管理、Markdown 渲染、输入读取等功能 package internal import ( "os" "path/filepath" "sync" "gopkg.in/yaml.v3" ) // Config 是项目配置结构体,包含流式输出、Markdown 渲染和 UI 相关配置 type Config struct { // Streaming 流式输出配置 Streaming StreamingConfig `yaml:"streaming"` // Markdown Markdown 渲染配置 Markdown MarkdownConfig `yaml:"markdown"` // UI UI 显示配置 UI UIConfig `yaml:"ui"` } // StreamingConfig 流式输出配置,控制模拟打字效果的延迟时间 type StreamingConfig struct { // LineDelayMs 每行输出延迟(毫秒) LineDelayMs int `yaml:"line_delay_ms"` // LastLineDelayMs 最后一行输出延迟(毫秒) LastLineDelayMs int `yaml:"last_line_delay_ms"` } // MarkdownConfig Markdown 渲染配置,控制渲染主题和换行行为 type MarkdownConfig struct { // Theme 渲染主题,支持 dark、light、dracula、tokyo-night 等 Theme string `yaml:"theme"` // LineWidth 自动换行宽度(0=自动,>0=固定宽度,-1=禁用) LineWidth int `yaml:"line_width"` } // UIConfig UI 显示配置,控制 Logo 和用户提示符 type UIConfig struct { // Logo 应用 Logo 字符 Logo string `yaml:"logo"` // UserIcon 用户输入提示符 UserIcon string `yaml:"user_icon"` } var ( // defaultCfg 默认配置值,当配置文件不存在或字段为空时使用 defaultCfg = Config{ Streaming: StreamingConfig{ LineDelayMs: 1000, LastLineDelayMs: 600, }, Markdown: MarkdownConfig{ Theme: "dark", LineWidth: 0, }, UI: UIConfig{ Logo: "🦐", UserIcon: "👀 ", }, } cfg *Config // 已合并的配置(用户配置 + 项目配置) cfgLock sync.RWMutex // 配置读写锁 userCfg *Config // 用户配置文件解析结果 userLock sync.RWMutex // 用户配置读写锁 ) // 用户配置文件路径常量 const ( userConfigDir = ".config/hxclaw" // 用户配置目录(相对于用户家目录) userConfigFile = "config.yml" // 用户配置文件名 ) // LoadProjectConfig 加载并合并项目配置 // 加载顺序:用户配置(~/.config/hxclaw/config.yml) > 项目配置(project.config.yml) // 最终配置由两者合并得出,用户配置优先于项目配置 func LoadProjectConfig() error { cfgLock.Lock() defer cfgLock.Unlock() userLock.Lock() defer userLock.Unlock() userCfg = loadUserConfig() projCfg := loadProjectConfig() merged := mergeConfig(userCfg, projCfg) cfg = merged return nil } // loadUserConfig 加载用户配置文件 // 路径:~/.config/hxclaw/config.yml // 如果文件不存在,则自动创建默认配置文件 func loadUserConfig() *Config { userPath := getUserConfigPath() if userPath == "" { return nil } data, err := os.ReadFile(userPath) if err != nil { if os.IsNotExist(err) { createUserConfig(userPath) return &defaultCfg } return nil } var userCfg Config if err := yaml.Unmarshal(data, &userCfg); err != nil { return nil } return &userCfg } // createUserConfig 创建默认的用户配置文件 // userPath: 配置文件完整路径 func createUserConfig(userPath string) error { dir := filepath.Dir(userPath) if err := os.MkdirAll(dir, 0755); err != nil { return err } defaultContent := `# hxclaw 用户配置文件 # 此文件位于 ~/.config/hxclaw/config.yml # Markdown 渲染配置 markdown: theme: dark # 渲染主题:dark, light, dracula, tokyo-night 等 line_width: 0 # 自动换行宽度(0=自动,>0=固定宽度,-1=禁用) # UI 配置 ui: logo: "🦐" user_icon: "👀 " ` return os.WriteFile(userPath, []byte(defaultContent), 0644) } // loadProjectConfig 加载项目级配置文件 // 路径优先级:环境变量 HXCLAW_CONFIG 指定路径 > ./project.config.yml func loadProjectConfig() *Config { cfgPath := getProjectConfigPath() if cfgPath == "" { return &defaultCfg } data, err := os.ReadFile(cfgPath) if err != nil { if os.IsNotExist(err) { return &defaultCfg } return &defaultCfg } var projCfg Config if err := yaml.Unmarshal(data, &projCfg); err != nil { return &defaultCfg } return &projCfg } // mergeConfig 合并用户配置和项目配置 // 合并规则:用户配置优先于项目配置,项目配置优先于默认配置 // 仅当配置值非空/非零时才覆盖默认值 func mergeConfig(userCfg, projCfg *Config) *Config { result := defaultCfg if projCfg != nil { if projCfg.Streaming.LineDelayMs > 0 { result.Streaming.LineDelayMs = projCfg.Streaming.LineDelayMs } if projCfg.Streaming.LastLineDelayMs > 0 { result.Streaming.LastLineDelayMs = projCfg.Streaming.LastLineDelayMs } if projCfg.Markdown.Theme != "" { result.Markdown.Theme = projCfg.Markdown.Theme } if projCfg.Markdown.LineWidth != 0 { result.Markdown.LineWidth = projCfg.Markdown.LineWidth } if projCfg.UI.Logo != "" { result.UI.Logo = projCfg.UI.Logo } if projCfg.UI.UserIcon != "" { result.UI.UserIcon = projCfg.UI.UserIcon } } if userCfg != nil { if userCfg.Markdown.Theme != "" { result.Markdown.Theme = userCfg.Markdown.Theme } if userCfg.Markdown.LineWidth != 0 { result.Markdown.LineWidth = userCfg.Markdown.LineWidth } if userCfg.UI.Logo != "" { result.UI.Logo = userCfg.UI.Logo } if userCfg.UI.UserIcon != "" { result.UI.UserIcon = userCfg.UI.UserIcon } } return &result } // GetProjectConfig 获取当前生效的配置(线程安全) // 如果配置未加载,返回默认配置 func GetProjectConfig() *Config { cfgLock.RLock() defer cfgLock.RUnlock() if cfg == nil { return &defaultCfg } return cfg } // getUserConfigPath 获取用户配置文件路径 // 路径格式:~/.config/hxclaw/config.yml // 支持 Windows (USERPROFILE) 和 Unix (HOME) 环境变量 func getUserConfigPath() string { homeDir := os.Getenv("USERPROFILE") if homeDir == "" { homeDir = os.Getenv("HOME") } if homeDir == "" { return "" } return filepath.Join(homeDir, userConfigDir, userConfigFile) } // getProjectConfigPath 获取项目配置文件路径 // 优先使用环境变量 HXCLAW_CONFIG 指定的路径,否则使用当前目录的 project.config.yml func getProjectConfigPath() string { if path := os.Getenv("HXCLAW_CONFIG"); path != "" { return path } return filepath.Join(".", "project.config.yml") }