- Add language code intelligent parsing module (internal/lang) - Support --lang parameter for target language specification - Support multiple language code formats (BCP47, aliases, Chinese names) - Implement interactive onboard configuration wizard - Update Config struct with language fields - Add survey library dependency for interactive UI - Improve CLI command interface - Add comprehensive unit tests for language module - Update documentation (AGENTS.md, changelog.md, taolun.md, memory.md) Supported language codes: - Standard: zh-CN, zh-TW, en-US, en-GB, ja, ko, es, fr, de - Aliases: cn, en, jp, kr, es, fr, de - Chinese names: chinese, english, japanese Commands: - yoyo "Hello world" - basic translation - yoyo --lang=cn "Hello world" - specify target language - yoyo onboard - start configuration wizard - yoyo onboard --force - force reconfiguration Version: 0.2.0
306 lines
7.8 KiB
Go
306 lines
7.8 KiB
Go
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
|
||
}
|