From bf29c260ca23a551a40d238ad35feea93d69a644 Mon Sep 17 00:00:00 2001 From: di-sukharev Date: Tue, 7 Mar 2023 16:19:00 +0800 Subject: [PATCH] =?UTF-8?q?*=20=F0=9F=9A=80=20feat(generateCommitMessageFr?= =?UTF-8?q?omGitDiff.ts):=20add=20mergeStrings=20utility=20function=20*=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(generateCommitMessageFromGitDiff.ts):=20remo?= =?UTF-8?q?ve=20unnecessary=20separator=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 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 --- di.txt | 120 ++++++++++++++++++++++++ src/api.ts | 2 + src/generateCommitMessageFromGitDiff.ts | 65 ++++++++----- src/utils/mergeStrings.ts | 14 +++ 4 files changed, 175 insertions(+), 26 deletions(-) create mode 100644 di.txt create mode 100644 src/utils/mergeStrings.ts diff --git a/di.txt b/di.txt new file mode 100644 index 0000000..0affa10 --- /dev/null +++ b/di.txt @@ -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 => { + 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; ++} diff --git a/src/api.ts b/src/api.ts index cf6e1fe..b53a656 100644 --- a/src/api.ts +++ b/src/api.ts @@ -43,6 +43,8 @@ class OpenAi { public generateCommitMessage = async ( messages: Array ): Promise => { + console.log({ messages }); + try { const { data } = await this.openAI.createChatCompletion({ model: 'gpt-3.5-turbo', diff --git a/src/generateCommitMessageFromGitDiff.ts b/src/generateCommitMessageFromGitDiff.ts index 87ca579..c6b5598 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(); @@ -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 => { 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; +} 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; +}