Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/commit more changes #37

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# Logs
logs
*.log
Expand Down Expand Up @@ -40,6 +41,7 @@ build/Release
# Dependency directories
node_modules/
jspm_packages/
.idea/

# TypeScript v1 declaration files
typings/
Expand Down
123 changes: 120 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { addGitmojiToCommitMessage } from './gitmoji.js';
import { AI_PROVIDER, MODEL, args } from "./config.js"
import openai from "./openai.js"
import ollama from "./ollama.js"
import { encode } from 'gpt-3-encoder';

const REGENERATE_MSG = "♻️ Regenerate Commit Messages";
const MAX_TOKENS = 128000;

console.log('Ai provider: ', AI_PROVIDER);

Expand Down Expand Up @@ -170,6 +172,33 @@ const filterLockFiles = (diff) => {
return filteredLines.join('\n');
};

function parseDiffByFile(diff) {
const files = [];
const lines = diff.split('\n');
let currentFile = null;
let currentDiff = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const diffGitMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
if (diffGitMatch) {
// Début d'un nouveau fichier
Arthur-MONNET marked this conversation as resolved.
Show resolved Hide resolved
if (currentFile) {
files.push({ filename: currentFile, diff: currentDiff.join('\n') });
}
currentFile = diffGitMatch[2];
currentDiff = [line];
} else if (currentFile) {
currentDiff.push(line);
}
}
// Ajouter le dernier fichier
Arthur-MONNET marked this conversation as resolved.
Show resolved Hide resolved
if (currentFile && currentDiff.length) {
files.push({ filename: currentFile, diff: currentDiff.join('\n') });
}
return files;
}


async function generateAICommit() {
const isGitRepository = checkGitRepository();

Expand All @@ -196,9 +225,97 @@ async function generateAICommit() {
process.exit(1);
}

args.list
? await generateListCommits(diff)
: await generateSingleCommit(diff);
const prompt = getPromptForSingleCommit(diff);

const numTokens = encode(prompt).length;

if (numTokens > MAX_TOKENS) {
// Diviser le diff par fichier et générer des résumés
Arthur-MONNET marked this conversation as resolved.
Show resolved Hide resolved
console.log("The commit diff is too large for the ChatGPT API. Splitting by files...");

// Parse diff into per-file diffs
const fileDiffs = parseDiffByFile(diff);

const summaries = [];

for (const { filename, diff: fileDiff } of fileDiffs) {
const summaryPrompt = provider.getPromptForDiffSummary(fileDiff, filename, { language });
const numTokens = encode(summaryPrompt).length;

if (numTokens > MAX_TOKENS) {
console.log(`Skipping ${filename} because its diff is too large.`);
continue;
}

if (!await provider.filterApi({ prompt: summaryPrompt, filterFee: args['filter-fee'] })) continue;

const summary = await provider.sendMessage(summaryPrompt, { apiKey, model: MODEL });

summaries.push(`- **${filename}**: ${summary}`);
}

if (summaries.length === 0) {
console.log("No files to summarize.");
process.exit(1);
}

// Générer le message de commit à partir des résumés
const summariesText = summaries.join('\n');

const commitPrompt =
`Based on the following summaries of changes, create a useful commit message in ${language} language` +
(commitType ? ` with commit type '${commitType}'. ` : ". ") +
"Use the summaries below to create the commit message. Do not preface the commit with anything, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>):\n\n" +
summariesText;

if (!await provider.filterApi({ prompt: commitPrompt, filterFee: args['filter-fee'] })) process.exit(1);

const commitMessage = await provider.sendMessage(commitPrompt, { apiKey, model: MODEL });

let finalCommitMessage = processEmoji(commitMessage, args.emoji);

if (args.template) {
finalCommitMessage = processTemplate({
template: args.template,
commitMessage: finalCommitMessage,
})

console.log(
`Proposed Commit With Template:\n------------------------------\n${finalCommitMessage}\n------------------------------`
);
} else {
console.log(
`Proposed Commit:\n------------------------------\n${finalCommitMessage}\n------------------------------`
);
}

if (args.force) {
makeCommit(finalCommitMessage);
return;
}

const answer = await inquirer.prompt([
{
type: "confirm",
name: "continue",
message: "Do you want to continue?",
default: true,
},
]);

if (!answer.continue) {
console.log("Commit aborted by user 🙅‍♂️");
process.exit(1);
}

makeCommit(finalCommitMessage);

} else {
// Procéder comme d'habitude si le diff n'est pas trop grand
Arthur-MONNET marked this conversation as resolved.
Show resolved Hide resolved
args.list
? await generateListCommits(diff)
: await generateSingleCommit(diff);
}
}

await generateAICommit();
116 changes: 60 additions & 56 deletions openai.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ChatGPTAPI } from "chatgpt";

import { encode } from 'gpt-3-encoder';
import inquirer from "inquirer";
import { AI_PROVIDER } from "./config.js"
Expand All @@ -10,70 +9,75 @@ const MAX_TOKENS = 128000;
const FEE_COMPLETION = 0.001;

const openai = {
sendMessage: async (input, {apiKey, model}) => {
console.log("prompting chat gpt...");
console.log("prompt: ", input);
const api = new ChatGPTAPI({
apiKey,
completionParams: {
model: "gpt-4-1106-preview",
},
});
const { text } = await api.sendMessage(input);

return text;
},

getPromptForSingleCommit: (diff, {commitType, language}) => {
sendMessage: async (input, {apiKey, model}) => {
console.log("prompting chat gpt...");
console.log("prompt: ", input);
const api = new ChatGPTAPI({
apiKey,
completionParams: {
model: "gpt-4-1106-preview",
},
});
const { text } = await api.sendMessage(input);

return (
"I want you to act as the author of a commit message in git." +
`I'll enter a git diff, and your job is to convert it into a useful commit message in ${language} language` +
(commitType ? ` with commit type '${commitType}'. ` : ". ") +
"Do not preface the commit with anything, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>): " +
'\n\n'+
diff
);
},
return text;
},

getPromptForMultipleCommits: (diff, {commitType, numOptions, language}) => {
const prompt =
"I want you to act as the author of a commit message in git." +
`I'll enter a git diff, and your job is to convert it into a useful commit message in ${language} language` +
(commitType ? ` with commit type '${commitType}.', ` : ", ") +
`and make ${numOptions} options that are separated by ";".` +
"For each option, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>):" +
diff;
getPromptForSingleCommit: (diff, {commitType, language}) => {
return (
"I want you to act as the author of a commit message in git. " +
`I'll enter a git diff, and your job is to convert it into a useful commit message in ${language} language` +
(commitType ? ` with commit type '${commitType}'. ` : ". ") +
"Do not preface the commit with anything, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>): " +
'\n\n'+
diff
);
},

return prompt;
},
getPromptForMultipleCommits: (diff, {commitType, numOptions, language}) => {
const prompt =
"I want you to act as the author of a commit message in git. " +
`I'll enter a git diff, and your job is to convert it into a useful commit message in ${language} language` +
(commitType ? ` with commit type '${commitType}.', ` : ", ") +
`and make ${numOptions} options that are separated by ";".` +
"For each option, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>):" +
diff;

filterApi: async ({ prompt, numCompletion = 1, filterFee }) => {
const numTokens = encode(prompt).length;
const fee = numTokens / 1000 * FEE_PER_1K_TOKENS + (FEE_COMPLETION * numCompletion);
return prompt;
},

if (numTokens > MAX_TOKENS) {
console.log("The commit diff is too large for the ChatGPT API. Max 4k tokens or ~8k characters. ");
return false;
}
// Nouvelle méthode pour générer le prompt de résumé du diff
getPromptForDiffSummary: (diff, filename, {language}) => {
return (
`Summarize the following git diff for the file '${filename}' in ${language} language. Be concise and focus on the main changes:\n\n` +
diff
);
},

if (filterFee) {
console.log(`This will cost you ~$${+fee.toFixed(3)} for using the API.`);
const answer = await inquirer.prompt([
{
type: "confirm",
name: "continue",
message: "Do you want to continue 💸?",
default: true,
},
]);
if (!answer.continue) return false;
}
filterApi: async ({ prompt, numCompletion = 1, filterFee }) => {
const numTokens = encode(prompt).length;
const fee = numTokens / 1000 * FEE_PER_1K_TOKENS + (FEE_COMPLETION * numCompletion);

return true;
}
if (numTokens > MAX_TOKENS) {
console.log(`The commit diff is too large for the ChatGPT API. Max ${MAX_TOKENS/1000}k tokens or ~${MAX_TOKENS/500} characters.`);
return false;
}

if (filterFee) {
console.log(`This will cost you ~$${+fee.toFixed(3)} for using the API.`);
const answer = await inquirer.prompt([
{
type: "confirm",
name: "continue",
message: "Do you want to continue 💸?",
default: true,
},
]);
if (!answer.continue) return false;
}

return true;
}
};

export default openai;
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
},
"homepage": "https://github.com/insulineru/ai-commit#readme",
"dependencies": {
"chatgpt": "^5.0.0",
"dotenv": "^16.0.3",
"chatgpt": "^5.2.5",
"dotenv": "^16.4.5",
"gpt-3-encoder": "^1.1.4",
"inquirer": "^9.1.4"
"inquirer": "^11.1.0"
Arthur-MONNET marked this conversation as resolved.
Show resolved Hide resolved
}
}