package onboard import ( "fmt" "os" "path/filepath" "github.com/AlecAivazis/survey/v2" "github.com/titor/fanyi/internal/config" "github.com/titor/fanyi/internal/lang" ) // RunOnboard 启动配置向导 func RunOnboard(force bool) error { fmt.Println("欢迎使用YOYO翻译工具配置向导!") fmt.Println("这个向导将帮助您配置翻译工具。") fmt.Println() // 检查配置文件是否存在 configPath := "configs/config.yaml" if _, err := os.Stat(configPath); err == nil && !force { overwrite := false prompt := &survey.Confirm{ Message: "检测到配置文件已存在,是否要重新配置?", Default: false, } if err := survey.AskOne(prompt, &overwrite); err != nil { return fmt.Errorf("用户输入错误: %w", err) } if !overwrite { fmt.Println("配置已取消。") return nil } } // 步骤1: 选择主要厂商 fmt.Println("步骤1: 选择主要翻译服务提供商") providerName, err := SelectProvider() if err != nil { return fmt.Errorf("选择厂商失败: %w", err) } // 步骤2: 配置主要厂商 fmt.Println("\n步骤2: 配置主要厂商") providerConfig, err := ConfigureProvider(providerName) if err != nil { return fmt.Errorf("配置厂商失败: %w", err) } // 步骤3: 全局设置 fmt.Println("\n步骤3: 全局设置") globalConfig, err := GlobalSettings() if err != nil { return fmt.Errorf("全局设置失败: %w", err) } // 步骤4: 确认并保存配置 fmt.Println("\n步骤4: 保存配置") configData := BuildConfig(providerName, providerConfig, globalConfig) if err := SaveConfig(configData, configPath); err != nil { return fmt.Errorf("保存配置失败: %w", err) } fmt.Printf("\n配置完成! 配置文件已保存到: %s\n", configPath) fmt.Println("\n您现在可以使用以下命令进行翻译:") fmt.Println(" yoyo \"Hello world\"") fmt.Println(" yoyo --lang=cn \"Hello world\"") fmt.Println("\n更多帮助请运行: yoyo --help") return nil } // SelectProvider 选择主要厂商 func SelectProvider() (string, error) { providers := []string{ "siliconflow", "volcano", "national", "qwen", "openai", } providerNames := map[string]string{ "siliconflow": "硅基流动 (推荐,免费额度)", "volcano": "火山引擎", "national": "国家超算", "qwen": "Qwen (通义千问)", "openai": "OpenAI兼容格式", } var selected string prompt := &survey.Select{ Message: "请选择要使用的翻译服务提供商:", Options: func() []string { var opts []string for _, p := range providers { opts = append(opts, providerNames[p]) } return opts }(), Default: providerNames["siliconflow"], } if err := survey.AskOne(prompt, &selected); err != nil { return "", err } // 返回对应的厂商名称 for name, displayName := range providerNames { if displayName == selected { return name, nil } } return "siliconflow", nil } // ConfigureProvider 配置厂商 func ConfigureProvider(providerName string) (config.ProviderConfig, error) { // 厂商默认配置 defaults := map[string]config.ProviderConfig{ "siliconflow": { APIHost: "https://api.siliconflow.cn/v1", Model: "siliconflow-base", Enabled: true, }, "volcano": { APIHost: "https://api.volcengine.com/v1", Model: "volcano-chat", Enabled: true, }, "national": { APIHost: "https://api.nsc.gov.cn/v1", Model: "nsc-base", Enabled: true, }, "qwen": { APIHost: "https://dashscope.aliyuncs.com/compatible-mode/v1", Model: "qwen-turbo", Enabled: true, }, "openai": { APIHost: "https://api.openai.com/v1", Model: "gpt-3.5-turbo", Enabled: true, }, } defaultConfig := defaults[providerName] cfg := config.ProviderConfig{ APIHost: defaultConfig.APIHost, Model: defaultConfig.Model, Enabled: defaultConfig.Enabled, } // 输入API密钥 apiKeyPrompt := &survey.Input{ Message: fmt.Sprintf("请输入 %s 的API密钥:", providerName), Help: "API密钥用于身份验证,将存储在配置文件中", } if err := survey.AskOne(apiKeyPrompt, &cfg.APIKey, survey.WithValidator(survey.Required)); err != nil { return config.ProviderConfig{}, err } // 确认API HOST apiHostPrompt := &survey.Input{ Message: "API HOST (直接回车使用默认值):", Default: cfg.APIHost, } if err := survey.AskOne(apiHostPrompt, &cfg.APIHost); err != nil { return config.ProviderConfig{}, err } // 确认默认模型 modelPrompt := &survey.Input{ Message: "默认模型 (直接回车使用默认值):", Default: cfg.Model, } if err := survey.AskOne(modelPrompt, &cfg.Model); err != nil { return config.ProviderConfig{}, err } return cfg, nil } // GlobalSettings 全局设置 type GlobalConfig struct { DefaultProvider string DefaultModel string Timeout int DefaultSourceLang string DefaultTargetLang string } // GlobalSettings 全局设置 func GlobalSettings() (*GlobalConfig, error) { cfg := &GlobalConfig{ DefaultProvider: "siliconflow", DefaultModel: "siliconflow-base", Timeout: 30, DefaultSourceLang: "auto", DefaultTargetLang: "zh-CN", } // 选择默认语言 targetLangOptions := lang.GetCommonLanguages() var targetLangDisplay []string for _, code := range targetLangOptions { targetLangDisplay = append(targetLangDisplay, fmt.Sprintf("%s (%s)", code, lang.GetLanguageName(code))) } targetLangPrompt := &survey.Select{ Message: "请选择默认目标语言:", Options: targetLangDisplay, Default: fmt.Sprintf("%s (%s)", "zh-CN", lang.GetLanguageName("zh-CN")), } var selectedTarget string if err := survey.AskOne(targetLangPrompt, &selectedTarget); err != nil { return nil, err } // 从选择中提取语言代码 for i, display := range targetLangDisplay { if display == selectedTarget { cfg.DefaultTargetLang = targetLangOptions[i] break } } // 设置超时时间 timeoutPrompt := &survey.Input{ Message: "API超时时间(秒):", Default: fmt.Sprintf("%d", cfg.Timeout), } var timeoutStr string if err := survey.AskOne(timeoutPrompt, &timeoutStr); err != nil { return nil, err } // 解析超时时间 if timeout := parseIntOrDefault(timeoutStr, 30); timeout > 0 { cfg.Timeout = timeout } return cfg, nil } // BuildConfig 构建配置对象 func BuildConfig(providerName string, providerConfig config.ProviderConfig, globalConfig *GlobalConfig) *config.Config { // 创建厂商配置 providers := map[string]config.ProviderConfig{ providerName: providerConfig, } // 创建Prompt配置 prompts := map[string]string{ "technical": "你是一位专业的技术翻译,请准确翻译以下技术文档,保持专业术语的准确性。", "creative": "你是一位富有创造力的翻译家,请用优美流畅的语言翻译以下内容。", "academic": "你是一位学术翻译专家,请用严谨的学术语言翻译以下内容。", "simple": "请用简单易懂的语言翻译以下内容。", } return &config.Config{ DefaultProvider: providerName, DefaultModel: providerConfig.Model, Timeout: globalConfig.Timeout, DefaultSourceLang: globalConfig.DefaultSourceLang, DefaultTargetLang: globalConfig.DefaultTargetLang, Providers: providers, Prompts: prompts, } } // SaveConfig 保存配置文件 func SaveConfig(cfg *config.Config, path string) error { // 确保目录存在 dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建配置目录失败: %w", err) } // 使用config包的Save方法 loader := &config.YAMLConfigLoader{} return loader.Save(cfg, path) } // parseIntOrDefault 解析整数,失败时返回默认值 func parseIntOrDefault(s string, defaultValue int) int { if s == "" { return defaultValue } var result int if _, err := fmt.Sscanf(s, "%d", &result); err != nil { return defaultValue } if result <= 0 { return defaultValue } return result }