diff --git a/src/config.rs b/src/config.rs index f52a98b..ce3be59 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,15 @@ use serde::{Deserialize, Serialize}; use std::{fmt::Display, fs}; -use crate::constants::{DEFAULT_OPENAI_API_BASE, DEFAULT_OPENAI_MODEL}; +use crate::constants::{DEFAULT_MAX_TOKENS, DEFAULT_OPENAI_API_BASE, DEFAULT_OPENAI_MODEL}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { pub api_base: Option, pub api_key: String, pub model: Option, + /// The maximum number of tokens to generate in the commit message. + pub max_tokens: Option, /// Whether to use conventional commit message format. pub conventional: bool, pub language: CommitLanguage, @@ -78,6 +80,7 @@ impl Default for Config { api_base: Some(DEFAULT_OPENAI_API_BASE.into()), api_key: "".to_owned(), model: Some(DEFAULT_OPENAI_MODEL.into()), + max_tokens: Some(DEFAULT_MAX_TOKENS), conventional: true, language: CommitLanguage::default(), verbosity: Verbosity::default(), diff --git a/src/constants.rs b/src/constants.rs index bbd18a4..d380337 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -7,16 +7,56 @@ pub const DEFAULT_PROMPT_TEMPLATE: &str = r#" 作为代码版本控制专家,请分析以下变更并生成commit message。 -要求: +# 要求: +- **使用约定式提交规范?: {{conventional_commit}}** +- **只输出最终message** - 使用{{language}}编写 - 详细程度:{{verbosity_level}} -- 只输出最终message -- 使用约定式提交规范? {{conventional_commit}} + +# 什么是约定式提交规范? + +约定式提交 1.0.0 +概述 +约定式提交规范是一种基于提交信息的轻量级约定。 它提供了一组简单规则来创建清晰的提交历史; 这更有利于编写自动化工具。 通过在提交信息中描述功能、修复和破坏性变更, 使这种惯例与 SemVer 相互对应。 + +提交说明的结构如下所示: + +原文: + +[optional scope]: + +[optional body] + +[optional footer(s)] +译文: + +<类型>[可选 范围]: <描述> + +[可选 正文] + +[可选 脚注] +提交说明包含了下面的结构化元素,以向类库使用者表明其意图: + +fix: 类型 为 fix 的提交表示在代码库中修复了一个 bug(这和语义化版本中的 PATCH 相对应)。 +feat: 类型 为 feat 的提交表示在代码库中新增了一个功能(这和语义化版本中的 MINOR 相对应)。 +BREAKING CHANGE: 在脚注中包含 BREAKING CHANGE: 或 <类型>(范围) 后面有一个 ! 的提交,表示引入了破坏性 API 变更(这和语义化版本中的 MAJOR 相对应)。 破坏性变更可以是任意 类型 提交的一部分。 +除 fix: 和 feat: 之外,也可以使用其它提交 类型 ,例如 @commitlint/config-conventional(基于 Angular 约定)中推荐的 build:、chore:、 ci:、docs:、style:、refactor:、perf:、test:,等等。 +build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等; +chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等; +ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置; +docs: 用于修改文档,例如修改 README 文件、API 文档等; +style: 用于修改代码的样式,例如调整缩进、空格、空行等; +refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑; +perf: 用于优化性能,例如提升代码的性能、减少内存占用等; +test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等。 +脚注中除了 BREAKING CHANGE: ,其它条目应该采用类似 git trailer format 这样的惯例。 +其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。 可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 feat(parser): adds ability to parse arrays.。 # 输出格式案例 message内容要使用标签包裹,例如: +(这里是commit标题) (这里是commit message内容) @@ -27,7 +67,6 @@ message内容要使用标签包裹,例如: {{diff}} "#; - #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum PromptTemplateReplaceLabel { Language, @@ -49,4 +88,6 @@ impl PromptTemplateReplaceLabel { lazy_static! { pub static ref STOP_WORDS: Vec = vec![String::from("")]; -} \ No newline at end of file +} + +pub const DEFAULT_MAX_TOKENS: u32 = 2048; diff --git a/src/generate.rs b/src/generate.rs index b81b49f..91e8e0a 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,11 +1,12 @@ - use openai_api_rust::chat::*; use openai_api_rust::*; use std::process::Command; use crate::cli; use crate::config::{self, Config}; -use crate::constants::{DEFAULT_OPENAI_MODEL, DEFAULT_PROMPT_TEMPLATE, STOP_WORDS}; +use crate::constants::{ + DEFAULT_MAX_TOKENS, DEFAULT_OPENAI_MODEL, DEFAULT_PROMPT_TEMPLATE, STOP_WORDS, +}; use crate::template_engine::{render_template, TemplateContext}; async fn generate_commit_message(diff: &str, config: &config::Config) -> anyhow::Result { @@ -48,7 +49,7 @@ async fn generate_commit_message(diff: &str, config: &config::Config) -> anyhow: n: None, stream: Some(false), stop: Some(STOP_WORDS.to_owned()), - max_tokens: Some(2048), + max_tokens: Some(DEFAULT_MAX_TOKENS as i32), presence_penalty: None, frequency_penalty: None, logit_bias: None, @@ -67,16 +68,46 @@ async fn generate_commit_message(diff: &str, config: &config::Config) -> anyhow: Ok(commit_message) } +fn delete_thinking_contents(orig: &str) -> String { + let start_tag = ""; + let end_tag = ""; + + let start_idx = orig.find(start_tag).unwrap_or(orig.len()); + let end_idx = orig.find(end_tag).unwrap_or_else(|| 0); + let s = if start_idx < end_idx { + let mut result = orig[..start_idx].to_string(); + result.push_str(&orig[end_idx..]); + log::debug!( + "Delete thinking contents, start_idx: {}, end_idx: {}: {:?} => {:?}", + start_idx, + end_idx, + orig, + result + ); + result + } else { + orig.to_string() + }; + s +} + fn extract_commit_message(response: &str) -> anyhow::Result { let start_tag = ""; let end_tag = ""; + let response = delete_thinking_contents(response); + let start_idx = response .find(start_tag) .ok_or(anyhow::anyhow!("Start tag not found"))? + start_tag.len(); - let end_idx = response - .find(end_tag).unwrap_or_else(|| response.len()); + let end_idx = response.find(end_tag).unwrap_or_else(|| response.len()); + + if start_idx >= end_idx { + return Err(anyhow::anyhow!( + "End tag not found or misplaced" + )); + } let commit_message = response[start_idx..end_idx].trim().to_string(); Ok(commit_message)