gig-gitignore工具实战开发(四):使用ai辅助生成gitignore

发布于:2025-07-26 ⋅ 阅读:(11) ⋅ 点赞:(0)

gig-gitignore工具实战开发(四):使用ai辅助生成gitignore

前言: 在上一篇我们编写了gig add命令基础代码,下面详细介绍ai生成模板代码。

ai配置

由于ai模型众多,这里我们采用最简单的方案,兼容openai的api规范,提供base_url修改,这样无需和一个个模型提供商进行对接。

引导用户进行ai配置输入

func runInteractiveConfig() {
	fmt.Println("Entering interactive configuration mode...")

	// Prompt for API Key
	promptAPIKey := promptui.Prompt{
		Label:   "OpenAI API Key",
		Default: viper.GetString("ai.api_key"),
		Mask:    '*',
	}
	apiKey, err := promptAPIKey.Run()
	if err != nil {
		fmt.Printf("Prompt failed %v\n", err)
		os.Exit(1)
	}
	viper.Set("ai.api_key", apiKey)

	// Prompt for AI URL
	promptURL := promptui.Prompt{
		Label:   "AI Service URL",
		Default: viper.GetString("ai.url"),
	}
	apiURL, err := promptURL.Run()
	if err != nil {
		fmt.Printf("Prompt failed %v\n", err)
		os.Exit(1)
	}
	viper.Set("ai.url", apiURL)

	// Prompt for AI Model
	promptModel := promptui.Prompt{
		Label:   "AI Model",
		Default: viper.GetString("ai.model"),
	}
	model, err := promptModel.Run()
	if err != nil {
		fmt.Printf("Prompt failed %v\n", err)
		os.Exit(1)
	}
	viper.Set("ai.model", model)

	saveConfig()
}

存储

func saveConfig() {
	// Get the config file path from viper
	cfgFile := viper.ConfigFileUsed()
	if cfgFile == "" {
		// If no config file is used, create one in the default path
		home, err := os.UserHomeDir()
		if err != nil {
			fmt.Println("Error finding home directory:", err)
			os.Exit(1)
		}
		cfgFile = filepath.Join(home, ".ciclebyte", "gig", "config", "config.yaml")
	}

	if err := viper.WriteConfigAs(cfgFile); err != nil {
		fmt.Println("Error writing configuration file:", err)
		os.Exit(1)
	}
	fmt.Println("Configuration saved successfully to", cfgFile)
}

编写promot

编写promot,让ai生成符合要求的gitignore文件

 You are an expert assistant specializing in generating high-quality .gitignore files.
    Your task is to create a .gitignore file for a project based on the provided languages and frameworks.

    <instructions>
    1.  **Content**: The file MUST only contain rules for the following languages/frameworks: %s.
    2.  **Structure**: Organize the rules into logical categories (e.g., "Dependencies", "Build output", "Logs", "IDE files").
    3.  **Headers**: Each category MUST start with a header formatted exactly as '# === Category Name ===', followed by a blank line.
    4.  **Comments**: Add concise comments (starting with '#') to explain the purpose of non-obvious rules or groups of rules.
    5.  **Exclusivity**: Do NOT include rules for any other languages, frameworks, or tools not specified.
    6.  **Output Format**: Your response MUST be ONLY the raw text content of the .gitignore file. Do NOT include any introductory text, explanations, or markdown code blocks like ` + "`" + ` ` + "`" + ` or ` + "`" + ` ` + "`" + `gitignore.
    </instructions>`

流失响应

根据配置的ai模型、promot返回生成的gitignore文件内容

// 通用流式AI请求,返回完整内容
func (AIUtil) StreamChat(promptType string, vars ...string) (string, error) {
	apiKey := viper.GetString("ai.api_key")
	if apiKey == "" {
		fmt.Println("AI API Key is not configured. Please run 'gig config' or设置 GIG_AI_API_KEY.")
		os.Exit(1)
	}
	apiURL := viper.GetString("ai.url")
	model := viper.GetString("ai.model")

	promptTpl := models.AppConfig.Prompts[promptType]
	anyVars := make([]any, len(vars))
	for i, v := range vars {
		anyVars[i] = v
	}
	prompt := fmt.Sprintf(promptTpl, anyVars...)

	reqBody, err := json.Marshal(models.OpenAIRequest{
		Model:    model,
		Messages: []models.Message{{Role: "user", Content: prompt}},
		Stream:   true,
	})
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(reqBody))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+apiKey)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := ioutil.ReadAll(resp.Body)
		return "", fmt.Errorf("AI service returned %d: %s", resp.StatusCode, string(bodyBytes))
	}

	var fullContent strings.Builder
	reader := bufio.NewReader(resp.Body)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return "", err
		}
		if strings.HasPrefix(line, "data: ") {
			data := strings.TrimPrefix(line, "data: ")
			if strings.TrimSpace(data) == "[DONE]" {
				break
			}
			var streamResp models.OpenAIStreamResponse
			if err := json.Unmarshal([]byte(data), &streamResp); err == nil {
				if len(streamResp.Choices) > 0 {
					content := streamResp.Choices[0].Delta.Content
					fmt.Print(content) // 实时输出
					fullContent.WriteString(content)
				}
			}
		}
	}
	return fullContent.String(), nil
}

// 建议流式返回切片
func (AIUtil) StreamSuggestions(promptType string, vars ...string) ([]string, error) {
	content, err := AI.StreamChat(promptType, vars...)
	if err != nil {
		return nil, err
	}
	return strings.Split(strings.TrimSpace(content), "\n"), nil
}

网站公告

今日签到

点亮在社区的每一天
去签到