Compare commits

..

1 Commits

Author SHA1 Message Date
di-sukharev
ea271276c4 2.2.4 2023-05-26 12:51:59 +08:00
7 changed files with 108 additions and 173 deletions

View File

@@ -16272,7 +16272,7 @@ function G3(t, e2) {
// package.json // package.json
var package_default = { var package_default = {
name: "opencommit", name: "opencommit",
version: "2.2.6", version: "2.2.3",
description: "Auto-generate impressive commits in 1 second. Killing lame commits with AI \u{1F92F}\u{1F52B}", description: "Auto-generate impressive commits in 1 second. Killing lame commits with AI \u{1F92F}\u{1F52B}",
keywords: [ keywords: [
"git", "git",
@@ -16313,8 +16313,9 @@ var package_default = {
start: "node ./out/cli.cjs", start: "node ./out/cli.cjs",
dev: "ts-node ./src/cli.ts", dev: "ts-node ./src/cli.ts",
build: "rimraf out && node esbuild.config.js", build: "rimraf out && node esbuild.config.js",
deploy: "npm run build:push && git push --tags && npm publish --tag latest", deploy: "npm run build:push && npm version patch && npm run postversion",
"build:push": "npm run build && git add . && git commit -m 'build' && git push", "build:push": "npm run build && git add . && git commit -m 'build' && git push",
postversion: "npm run build && git add . && git commit --amend --no-edit && git push && git push --tags && npm publish --tag latest",
lint: "eslint src --ext ts && tsc --noEmit", lint: "eslint src --ext ts && tsc --noEmit",
format: "prettier --write src" format: "prettier --write src"
}, },

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "opencommit", "name": "opencommit",
"version": "2.2.7", "version": "2.2.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "opencommit", "name": "opencommit",
"version": "2.2.7", "version": "2.2.4",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "opencommit", "name": "opencommit",
"version": "2.2.7", "version": "2.2.4",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫", "description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [ "keywords": [
"git", "git",
@@ -41,8 +41,9 @@
"start": "node ./out/cli.cjs", "start": "node ./out/cli.cjs",
"dev": "ts-node ./src/cli.ts", "dev": "ts-node ./src/cli.ts",
"build": "rimraf out && node esbuild.config.js", "build": "rimraf out && node esbuild.config.js",
"deploy": "npm run build:push && git push --tags && npm publish --tag latest", "deploy": "npm run build:push && npm version patch && npm run postversion",
"build:push": "npm run build && git add . && git commit -m 'build' && git push", "build:push": "npm run build && git add . && git commit -m 'build' && git push",
"postversion": "npm run build && git add . && git commit --amend --no-edit && git push && git push --tags && npm publish --tag latest",
"lint": "eslint src --ext ts && tsc --noEmit", "lint": "eslint src --ext ts && tsc --noEmit",
"format": "prettier --write src" "format": "prettier --write src"
}, },

View File

@@ -1,4 +1,4 @@
export enum COMMANDS { export enum COMMANDS {
config = 'config', hook = 'hook',
hook = 'hook' config = 'config'
} }

View File

@@ -7,9 +7,7 @@ import {
OpenAIApi OpenAIApi
} from 'openai'; } from 'openai';
import {CONFIG_MODES, DEFAULT_MODEL_TOKEN_LIMIT, getConfig} from './commands/config'; import { CONFIG_MODES, getConfig } from './commands/config';
import {tokenCount} from './utils/tokenCount';
import {GenerateCommitMessageErrorEnum} from './generateCommitMessageFromGitDiff';
const config = getConfig(); const config = getConfig();
@@ -58,14 +56,6 @@ class OpenAi {
max_tokens: maxTokens || 500 max_tokens: maxTokens || 500
}; };
try { try {
const REQUEST_TOKENS = messages.map(
(msg) => tokenCount(msg.content) + 4
).reduce((a, b) => a + b, 0);
if (REQUEST_TOKENS > (DEFAULT_MODEL_TOKEN_LIMIT - maxTokens)) {
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
}
const { data } = await this.openAI.createChatCompletion(params); const { data } = await this.openAI.createChatCompletion(params);
const message = data.choices[0].message; const message = data.choices[0].message;

View File

@@ -22,8 +22,6 @@ export enum CONFIG_KEYS {
OCO_LANGUAGE = 'OCO_LANGUAGE' OCO_LANGUAGE = 'OCO_LANGUAGE'
} }
export const DEFAULT_MODEL_TOKEN_LIMIT = 4096;
export enum CONFIG_MODES { export enum CONFIG_MODES {
get = 'get', get = 'get',
set = 'set' set = 'set'

View File

@@ -1,28 +1,28 @@
import { import {
ChatCompletionRequestMessage, ChatCompletionRequestMessage,
ChatCompletionRequestMessageRoleEnum ChatCompletionRequestMessageRoleEnum
} from 'openai'; } from 'openai';
import {api} from './api'; import { api } from './api';
import {DEFAULT_MODEL_TOKEN_LIMIT, getConfig} from './commands/config'; import { getConfig } from './commands/config';
import {mergeDiffs} from './utils/mergeDiffs'; import { mergeDiffs } from './utils/mergeDiffs';
import {i18n, I18nLocals} from './i18n'; import { i18n, I18nLocals } from './i18n';
import {tokenCount} from './utils/tokenCount'; import { tokenCount } from './utils/tokenCount';
const config = getConfig(); const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en']; const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [ const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [
{ {
role: ChatCompletionRequestMessageRoleEnum.System, role: ChatCompletionRequestMessageRoleEnum.System,
// prettier-ignore // prettier-ignore
content: `You are to act as the author of a commit message in git. Your mission is to create clean and comprehensive commit messages in the conventional commit convention and explain WHAT were the changes and WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message. content: `You are to act as the author of a commit message in git. Your mission is to create clean and comprehensive commit messages in the conventional commit convention and explain WHAT were the changes and WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
${config?.OCO_EMOJI ? 'Use GitMoji convention to preface the commit.' : 'Do not preface the commit with anything.'} ${config?.OCO_EMOJI ? 'Use GitMoji convention to preface the commit.': 'Do not preface the commit with anything.'}
${config?.OCO_DESCRIPTION ? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.' : "Don't add any descriptions to the commit, only commit message."} ${config?.OCO_DESCRIPTION ? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.': "Don't add any descriptions to the commit, only commit message."}
Use the present tense. Lines must not be longer than 74 characters. Use ${translation.localLanguage} to answer.` Use the present tense. Lines must not be longer than 74 characters. Use ${translation.localLanguage} to answer.`
}, },
{ {
role: ChatCompletionRequestMessageRoleEnum.User, role: ChatCompletionRequestMessageRoleEnum.User,
content: `diff --git a/src/server.ts b/src/server.ts content: `diff --git a/src/server.ts b/src/server.ts
index ad4db42..f3b18a9 100644 index ad4db42..f3b18a9 100644
--- a/src/server.ts --- a/src/server.ts
+++ b/src/server.ts +++ b/src/server.ts
@@ -46,183 +46,128 @@ app.use((_, res, next) => {
+app.listen(process.env.PORT || PORT, () => { +app.listen(process.env.PORT || PORT, () => {
+ console.log(\`Server listening on port \${PORT}\`); + console.log(\`Server listening on port \${PORT}\`);
});` });`
}, },
{ {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix} content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat} ${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}` ${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
} }
]; ];
const generateCommitMessageChatCompletionPrompt = ( const generateCommitMessageChatCompletionPrompt = (
diff: string diff: string
): Array<ChatCompletionRequestMessage> => { ): Array<ChatCompletionRequestMessage> => {
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT]; const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
chatContextAsCompletionRequest.push({ chatContextAsCompletionRequest.push({
role: ChatCompletionRequestMessageRoleEnum.User, role: ChatCompletionRequestMessageRoleEnum.User,
content: diff content: diff
}); });
return chatContextAsCompletionRequest; return chatContextAsCompletionRequest;
}; };
export enum GenerateCommitMessageErrorEnum { export enum GenerateCommitMessageErrorEnum {
tooMuchTokens = 'TOO_MUCH_TOKENS', tooMuchTokens = 'TOO_MUCH_TOKENS',
internalError = 'INTERNAL_ERROR', internalError = 'INTERNAL_ERROR',
emptyMessage = 'EMPTY_MESSAGE' emptyMessage = 'EMPTY_MESSAGE'
} }
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map( const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => tokenCount(msg.content) + 4 (msg) => tokenCount(msg.content) + 4
).reduce((a, b) => a + b, 0); ).reduce((a, b) => a + b, 0);
const ADJUSTMENT_FACTOR = 20; const MAX_REQ_TOKENS = 3000 - INIT_MESSAGES_PROMPT_LENGTH;
export const generateCommitMessageByDiff = async ( export const generateCommitMessageByDiff = async (
diff: string diff: string
): Promise<string> => { ): Promise<string> => {
try { try {
const MAX_REQUEST_TOKENS = DEFAULT_MODEL_TOKEN_LIMIT if (tokenCount(diff) >= MAX_REQ_TOKENS) {
- ADJUSTMENT_FACTOR const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
- INIT_MESSAGES_PROMPT_LENGTH diff,
- config?.OCO_OPENAI_MAX_TOKENS; MAX_REQ_TOKENS
);
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) { const commitMessages = await Promise.all(commitMessagePromises);
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
diff,
MAX_REQUEST_TOKENS
);
const commitMessages = []; return commitMessages.join('\n\n');
for (const promise of commitMessagePromises) { } else {
commitMessages.push(await promise); const messages = generateCommitMessageChatCompletionPrompt(diff);
await delay(2000);
}
return commitMessages.join('\n\n'); const commitMessage = await api.generateCommitMessage(messages);
} else {
const messages = generateCommitMessageChatCompletionPrompt(diff);
const commitMessage = await api.generateCommitMessage(messages); if (!commitMessage)
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
if (!commitMessage) return commitMessage;
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
}
} catch (error) {
throw error;
} }
} catch (error) {
throw error;
}
}; };
function getMessagesPromisesByChangesInFile( function getMessagesPromisesByChangesInFile(
fileDiff: string, fileDiff: string,
separator: string, separator: string,
maxChangeLength: number maxChangeLength: number
) { ) {
const hunkHeaderSeparator = '@@ '; const hunkHeaderSeparator = '@@ ';
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator); const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
// merge multiple line-diffs into 1 to save tokens // merge multiple line-diffs into 1 to save tokens
const mergedChanges = mergeDiffs( const mergedChanges = mergeDiffs(
fileDiffByLines.map((line) => hunkHeaderSeparator + line), fileDiffByLines.map((line) => hunkHeaderSeparator + line),
maxChangeLength maxChangeLength
);
const lineDiffsWithHeader = mergedChanges.map(
(change) => fileHeader + change
);
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((lineDiff) => {
const messages = generateCommitMessageChatCompletionPrompt(
separator + lineDiff
); );
const lineDiffsWithHeader = []; return api.generateCommitMessage(messages);
for (const change of mergedChanges) { });
const totalChange = fileHeader + change;
if (tokenCount(totalChange) > maxChangeLength) {
// If the totalChange is too large, split it into smaller pieces
const splitChanges = splitDiff(totalChange, maxChangeLength);
lineDiffsWithHeader.push(...splitChanges);
} else {
lineDiffsWithHeader.push(totalChange);
}
}
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((lineDiff) => { return commitMsgsFromFileLineDiffs;
const messages = generateCommitMessageChatCompletionPrompt(
separator + lineDiff
);
return api.generateCommitMessage(messages);
});
return commitMsgsFromFileLineDiffs;
}
function splitDiff(diff: string, maxChangeLength: number) {
const lines = diff.split('\n');
const splitDiffs = [];
let currentDiff = '';
for (let line of lines) {
// If a single line exceeds maxChangeLength, split it into multiple lines
while (tokenCount(line) > maxChangeLength) {
const subLine = line.substring(0, maxChangeLength);
line = line.substring(maxChangeLength);
splitDiffs.push(subLine);
}
// Check the tokenCount of the currentDiff and the line separately
if (tokenCount(currentDiff) + tokenCount('\n' + line) > maxChangeLength) {
// If adding the next line would exceed the maxChangeLength, start a new diff
splitDiffs.push(currentDiff);
currentDiff = line;
} else {
// Otherwise, add the line to the current diff
currentDiff += '\n' + line;
}
}
// Add the last diff
if (currentDiff) {
splitDiffs.push(currentDiff);
}
return splitDiffs;
} }
export function getCommitMsgsPromisesFromFileDiffs( export function getCommitMsgsPromisesFromFileDiffs(
diff: string, diff: string,
maxDiffLength: number maxDiffLength: number
) { ) {
const separator = 'diff --git '; const separator = 'diff --git ';
const diffByFiles = diff.split(separator).slice(1); const diffByFiles = diff.split(separator).slice(1);
// merge multiple files-diffs into 1 prompt to save tokens // merge multiple files-diffs into 1 prompt to save tokens
const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength); const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength);
const commitMessagePromises = []; const commitMessagePromises = [];
for (const fileDiff of mergedFilesDiffs) { for (const fileDiff of mergedFilesDiffs) {
if (tokenCount(fileDiff) >= maxDiffLength) { if (tokenCount(fileDiff) >= maxDiffLength) {
// if file-diff is bigger than gpt context — split fileDiff into lineDiff // if file-diff is bigger than gpt context — split fileDiff into lineDiff
const messagesPromises = getMessagesPromisesByChangesInFile( const messagesPromises = getMessagesPromisesByChangesInFile(
fileDiff, fileDiff,
separator, separator,
maxDiffLength maxDiffLength
); );
commitMessagePromises.push(...messagesPromises); commitMessagePromises.push(...messagesPromises);
} else { } else {
const messages = generateCommitMessageChatCompletionPrompt( const messages = generateCommitMessageChatCompletionPrompt(
separator + fileDiff separator + fileDiff
); );
commitMessagePromises.push(api.generateCommitMessage(messages)); commitMessagePromises.push(api.generateCommitMessage(messages));
}
} }
}
return commitMessagePromises;
return commitMessagePromises;
}
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
} }