* 🚀 feat(generateCommitMessageFromGitDiff.ts): add mergeStrings utility function

* 🐛 fix(generateCommitMessageFromGitDiff.ts): remove unnecessary separator variable

* 🐛 fix: split file diffs by files
*  feat: add support for generating commit messages by file diffs

*  feat(generateCommitMessageFromGitDiff.ts): add mergeStrings utility function to merge string arrays
* 🐛 fix(generateCommitMessageFromGitDiff.ts): reduce MAX_REQ_TOKENS to 1000
*  feat(generateCommitMessageFromGitDiff.ts): add support for generating commit messages for large diffs by splitting them into smaller chunks and generating commit messages for each chunk using OpenAI's GPT-3 model.

* ♻️ refactor: extract getCommitMsgsPromisesFromFileDiffs function from generateCommitMessage function
* ♻️ refactor: extract getMessagesPromisesByLines function from getCommitMsgsPromisesFromFileDiffs function
*  feat: add support for merging file diffs into one commit message if it exceeds MAX_REQ_TOKENS limit

*  feat(mergeStrings.ts): add mergeStrings function to merge strings in an array based on maxStringLength
This commit is contained in:
di-sukharev
2023-03-07 16:19:00 +08:00
parent 9e2a3d8988
commit bf29c260ca
4 changed files with 175 additions and 26 deletions
+120
View File
@@ -0,0 +1,120 @@
diff --git a/src/generateCommitMessageFromGitDiff.ts b/src/generateCommitMessageFromGitDiff.ts
index c3fb638..82b8bde 100644
--- a/src/generateCommitMessageFromGitDiff.ts
+++ b/src/generateCommitMessageFromGitDiff.ts
@@ -4,6 +4,7 @@ import {
} from 'openai';
import { api } from './api';
import { getConfig } from './commands/config';
+import { mergeStrings } from './utils/mergeStrings';
const config = getConfig();
@@ -88,37 +89,64 @@ export const generateCommitMessageWithChatCompletion = async (
): Promise<string | GenerateCommitMessageError> => {
try {
if (diff.length >= MAX_REQ_TOKENS) {
- const separator = 'diff --git ';
-
- const diffByFiles = diff.split(separator).slice(1);
-
- const commitMessagePromises = diffByFiles
- .map((fileDiff) => {
- // TODO: split by files
- if (fileDiff.length >= MAX_REQ_TOKENS) return null;
-
- const messages = generateCommitMessageChatCompletionPrompt(
- separator + fileDiff
- );
-
- return api.generateCommitMessage(messages);
- })
- .filter(Boolean);
+ const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(diff);
const commitMessages = await Promise.all(commitMessagePromises);
return commitMessages.join('\n\n');
- }
-
- const messages = generateCommitMessageChatCompletionPrompt(diff);
+ } else {
+ const messages = generateCommitMessageChatCompletionPrompt(diff);
- const commitMessage = await api.generateCommitMessage(messages);
+ const commitMessage = await api.generateCommitMessage(messages);
- if (!commitMessage)
- return { error: GenerateCommitMessageErrorEnum.emptyMessage };
+ if (!commitMessage)
+ return { error: GenerateCommitMessageErrorEnum.emptyMessage };
- return commitMessage;
+ return commitMessage;
+ }
} catch (error) {
return { error: GenerateCommitMessageErrorEnum.internalError };
}
};
+
+function getMessagesPromisesByLines(fileDiff: string, separator: string) {
+ const [fileHeader, ...fileDiffByLines] = fileDiff.split('@@');
+ const lineDiffsWithHeader = fileDiffByLines.map((d) => fileHeader + '@@' + d);
+
+ const mergedLines = mergeStrings(lineDiffsWithHeader, MAX_REQ_TOKENS);
+
+ const commitMsgsFromFileLineDiffs = mergedLines.map((d) => {
+ const messages = generateCommitMessageChatCompletionPrompt(separator + d);
+
+ return api.generateCommitMessage(messages);
+ });
+
+ return commitMsgsFromFileLineDiffs;
+}
+
+function getCommitMsgsPromisesFromFileDiffs(diff: string) {
+ const separator = 'diff --git ';
+
+ const diffByFiles = diff.split(separator).slice(1);
+
+ const mergedDiffs = mergeStrings(diffByFiles, MAX_REQ_TOKENS);
+
+ const commitMessagePromises = [];
+
+ for (const fileDiff of mergedDiffs) {
+ if (fileDiff.length >= MAX_REQ_TOKENS) {
+ // split fileDiff into lineDiff
+ const messagesPromises = getMessagesPromisesByLines(fileDiff, separator);
+
+ commitMessagePromises.push(...messagesPromises);
+ } else {
+ // generate commits for files
+ const messages = generateCommitMessageChatCompletionPrompt(
+ separator + fileDiff
+ );
+
+ commitMessagePromises.push(api.generateCommitMessage(messages));
+ }
+ }
+ return commitMessagePromises;
+}
diff --git a/src/utils/mergeStrings.ts b/src/utils/mergeStrings.ts
new file mode 100644
index 0000000..a8beb37
--- /dev/null
+++ b/src/utils/mergeStrings.ts
@@ -0,0 +1,14 @@
+export function mergeStrings(arr: string[], maxStringLength: number): string[] {
+ const mergedArr: string[] = [];
+ let currentItem: string = arr[0];
+ for (const item of arr.slice(1)) {
+ if (currentItem.length + item.length <= maxStringLength) {
+ currentItem += item;
+ } else {
+ mergedArr.push(currentItem);
+ currentItem = item;
+ }
+ }
+ mergedArr.push(currentItem);
+ return mergedArr;
+}
+2
View File
@@ -43,6 +43,8 @@ class OpenAi {
public generateCommitMessage = async (
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> => {
console.log({ messages });
try {
const { data } = await this.openAI.createChatCompletion({
model: 'gpt-3.5-turbo',
+39 -26
View File
@@ -4,6 +4,7 @@ import {
} from 'openai';
import { api } from './api';
import { getConfig } from './commands/config';
import { mergeStrings } from './utils/mergeStrings';
const config = getConfig();
@@ -81,43 +82,28 @@ const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => msg.content
).join('').length;
const MAX_REQ_TOKENS = 3900 - INIT_MESSAGES_PROMPT_LENGTH;
const MAX_REQ_TOKENS = 1000 - INIT_MESSAGES_PROMPT_LENGTH;
export const generateCommitMessageWithChatCompletion = async (
diff: string
): Promise<string | GenerateCommitMessageError> => {
try {
if (diff.length >= MAX_REQ_TOKENS) {
const separator = 'diff --git ';
const diffByFiles = diff.split(separator).slice(1);
const commitMessagePromises = diffByFiles
.map((fileDiff) => {
// TODO: split by files
if (fileDiff.length >= MAX_REQ_TOKENS) return null;
const messages = generateCommitMessageChatCompletionPrompt(
separator + fileDiff
);
return api.generateCommitMessage(messages);
})
.filter(Boolean);
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(diff);
const commitMessages = await Promise.all(commitMessagePromises);
return commitMessages.join('\n\n');
} else {
const messages = generateCommitMessageChatCompletionPrompt(diff);
const commitMessage = await api.generateCommitMessage(messages);
if (!commitMessage)
return { error: GenerateCommitMessageErrorEnum.emptyMessage };
return commitMessage;
}
const messages = generateCommitMessageChatCompletionPrompt(diff);
const commitMessage = await api.generateCommitMessage(messages);
if (!commitMessage)
return { error: GenerateCommitMessageErrorEnum.emptyMessage };
return commitMessage;
} catch (error) {
return { error: GenerateCommitMessageErrorEnum.internalError };
}
@@ -139,3 +125,30 @@ function getMessagesPromisesByLines(fileDiff: string, separator: string) {
return commitMsgsFromFileLineDiffs;
}
function getCommitMsgsPromisesFromFileDiffs(diff: string) {
const separator = 'diff --git ';
const diffByFiles = diff.split(separator).slice(1);
const mergedDiffs = mergeStrings(diffByFiles, MAX_REQ_TOKENS);
const commitMessagePromises = [];
for (const fileDiff of mergedDiffs) {
if (fileDiff.length >= MAX_REQ_TOKENS) {
// split fileDiff into lineDiff
const messagesPromises = getMessagesPromisesByLines(fileDiff, separator);
commitMessagePromises.push(...messagesPromises);
} else {
// generate commits for files
const messages = generateCommitMessageChatCompletionPrompt(
separator + fileDiff
);
commitMessagePromises.push(api.generateCommitMessage(messages));
}
}
return commitMessagePromises;
}
+14
View File
@@ -0,0 +1,14 @@
export function mergeStrings(arr: string[], maxStringLength: number): string[] {
const mergedArr: string[] = [];
let currentItem: string = arr[0];
for (const item of arr.slice(1)) {
if (currentItem.length + item.length <= maxStringLength) {
currentItem += item;
} else {
mergedArr.push(currentItem);
currentItem = item;
}
}
mergedArr.push(currentItem);
return mergedArr;
}