Files
HxClaw/cmd/hxclaw/internal/config.go
Z.To 88a110e87e fix: 恢复 markdown 渲染修复,应用配置系统重构
- 恢复 e070461 修复:wrap_width=-1 禁用换行,使用 lipgloss.Print
- 应用 dd3c8a0 配置重构:字段名变更(theme/line_width/user_icon)
- 添加用户配置文件支持(~/.config/hxclaw/config.yml)
- 添加 TTS 配置到用户配置(enabled, auto)
2026-04-26 06:44:47 +08:00

278 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"`
// TTS TTS 语音配置
TTS TTSConfig `yaml:"tts"`
}
// 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"`
}
// TTSConfig TTS 语音配置
type TTSConfig struct {
// Enabled 全局开关
Enabled bool `yaml:"enabled"`
// Port 端口
Port int `yaml:"port"`
// Auto AI 回复后自动朗读
Auto bool `yaml:"auto"`
}
var (
// defaultCfg 默认配置值,当配置文件不存在或字段为空时使用
defaultCfg = Config{
Streaming: StreamingConfig{
LineDelayMs: 1000,
LastLineDelayMs: 600,
},
Markdown: MarkdownConfig{
Theme: "dark",
LineWidth: -1,
},
UI: UIConfig{
Logo: "🦐",
UserIcon: "👀 ",
},
TTS: TTSConfig{
Enabled: false,
Port: 9876,
Auto: true,
},
}
cfg *Config // 已合并的配置(用户配置 + 项目配置)
cfgLock 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()
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 nil
}
return nil
}
var userCfg Config
if err := yaml.Unmarshal(data, &userCfg); err != nil {
return nil
}
return &userCfg
}
// createUserConfig 创建默认的用户配置文件
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 # 自动换行宽度(-1=禁用0=自动,>0=固定宽度)
# UI 配置
ui:
logo: "🦐"
user_icon: "👀 "
# TTS 语音配置
tts:
enabled: false # 全局开关(默认关闭)
auto: true # AI 回复后自动朗读
`
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 projCfg.TTS.Port > 0 {
result.TTS.Port = projCfg.TTS.Port
}
}
// 再应用用户配置(覆盖项目配置)
if userCfg != nil {
// Markdown
if userCfg.Markdown.Theme != "" {
result.Markdown.Theme = userCfg.Markdown.Theme
}
if userCfg.Markdown.LineWidth != 0 {
result.Markdown.LineWidth = userCfg.Markdown.LineWidth
}
// UI
if userCfg.UI.Logo != "" {
result.UI.Logo = userCfg.UI.Logo
}
if userCfg.UI.UserIcon != "" {
result.UI.UserIcon = userCfg.UI.UserIcon
}
// TTS用户配置可以覆盖 enabled 和 auto
if userCfg.TTS.Enabled {
result.TTS.Enabled = userCfg.TTS.Enabled
}
if userCfg.TTS.Port > 0 {
result.TTS.Port = userCfg.TTS.Port
}
if userCfg.TTS.Auto {
result.TTS.Auto = userCfg.TTS.Auto
}
}
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")
}