Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub api_key: String,
pub model: Option<String>,
/// The maximum number of tokens to generate in the commit message.
pub max_tokens: Option<u32>,
/// Whether to use conventional commit message format.
pub conventional: bool,
pub language: CommitLanguage,
Expand Down Expand Up @@ -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(),
Expand Down
51 changes: 46 additions & 5 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 相互对应。

提交说明的结构如下所示:

原文:

<type>[optional scope]: <description>

[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: <description> ,其它条目应该采用类似 git trailer format 这样的惯例。
其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。 可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 feat(parser): adds ability to parse arrays.。

# 输出格式案例

message内容要使用<aicommit>标签包裹,例如:
<aicommit>
(这里是commit标题)

(这里是commit message内容)

Expand All @@ -27,7 +67,6 @@ message内容要使用<aicommit>标签包裹,例如:
{{diff}}
"#;


#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum PromptTemplateReplaceLabel {
Language,
Expand All @@ -49,4 +88,6 @@ impl PromptTemplateReplaceLabel {

lazy_static! {
pub static ref STOP_WORDS: Vec<String> = vec![String::from("</aicommit>")];
}
}

pub const DEFAULT_MAX_TOKENS: u32 = 2048;
41 changes: 36 additions & 5 deletions src/generate.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
Expand Down Expand Up @@ -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,
Expand All @@ -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 = "<think>";
let end_tag = "</think>";

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<String> {
let start_tag = "<aicommit>";
let end_tag = "</aicommit>";

let response = delete_thinking_contents(response);

let start_idx = response
.find(start_tag)
.ok_or(anyhow::anyhow!("Start tag <aicommit> 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 </aicommit> not found or misplaced"
));
}

let commit_message = response[start_idx..end_idx].trim().to_string();
Ok(commit_message)
Expand Down