|
| 1 | +import * as vscode from "vscode" |
| 2 | +import * as path from "path" |
| 3 | +import * as fs from "fs/promises" |
| 4 | +import { buildApiHandler } from "../../api" |
| 5 | +import { simpleGit, SimpleGit } from "simple-git" |
| 6 | +import { ContextProxy } from "../../core/config/ContextProxy" |
| 7 | +import { Anthropic } from "@anthropic-ai/sdk" |
| 8 | + |
| 9 | +async function getGitApi(): Promise<any | undefined> { |
| 10 | + const extension = vscode.extensions.getExtension("vscode.git") |
| 11 | + if (!extension) { |
| 12 | + vscode.window.showErrorMessage("Git extension not found.") |
| 13 | + return |
| 14 | + } |
| 15 | + await extension.activate() |
| 16 | + return extension.exports.getAPI(1) |
| 17 | +} |
| 18 | + |
| 19 | +async function getChanges(git: SimpleGit, repoPath: string): Promise<string> { |
| 20 | + const trackedFilesDiff = await git.diff() |
| 21 | + const status = await git.status() |
| 22 | + const untrackedFiles = status.not_added |
| 23 | + |
| 24 | + let untrackedFilesContent = "" |
| 25 | + for (const file of untrackedFiles) { |
| 26 | + const filePath = path.join(repoPath, file) |
| 27 | + try { |
| 28 | + const content = await fs.readFile(filePath, "utf-8") |
| 29 | + untrackedFilesContent += `\n--- a/${file}\n+++ b/${file}\n${content}` |
| 30 | + } catch (e) { |
| 31 | + console.error(`Could not read untracked file ${file}`, e) |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + return `${trackedFilesDiff}\n${untrackedFilesContent}`.trim() |
| 36 | +} |
| 37 | + |
| 38 | +function createPrompt(diff: string): string { |
| 39 | + return `Create a git commit message from the following diff:\n${diff}` |
| 40 | +} |
| 41 | + |
| 42 | +export async function generateCommitMessage(context: vscode.ExtensionContext) { |
| 43 | + const gitApi = await getGitApi() |
| 44 | + if (!gitApi) { |
| 45 | + return |
| 46 | + } |
| 47 | + |
| 48 | + if (gitApi.repositories.length === 0) { |
| 49 | + vscode.window.showErrorMessage("No Git repository found.") |
| 50 | + return |
| 51 | + } |
| 52 | + |
| 53 | + const repoPath = gitApi.repositories[0].rootUri.fsPath |
| 54 | + const git = simpleGit(repoPath) |
| 55 | + const diff = await getChanges(git, repoPath) |
| 56 | + |
| 57 | + if (!diff) { |
| 58 | + vscode.window.showInformationMessage("No changes found.") |
| 59 | + return |
| 60 | + } |
| 61 | + |
| 62 | + const contextProxy = await ContextProxy.getInstance(context) |
| 63 | + const providerSettings = contextProxy.getProviderSettings() |
| 64 | + if (!providerSettings) { |
| 65 | + vscode.window.showErrorMessage("AI provider not configured.") |
| 66 | + return |
| 67 | + } |
| 68 | + const provider = buildApiHandler(providerSettings) |
| 69 | + const modelName = provider.getModel().id |
| 70 | + const prompt = createPrompt(diff) |
| 71 | + const messages: Anthropic.Messages.MessageParam[] = [ |
| 72 | + { |
| 73 | + role: "user", |
| 74 | + content: prompt, |
| 75 | + }, |
| 76 | + ] |
| 77 | + |
| 78 | + await vscode.window.withProgress( |
| 79 | + { |
| 80 | + location: vscode.ProgressLocation.Notification, |
| 81 | + title: `Generating commit message with ${modelName}...`, |
| 82 | + cancellable: false, |
| 83 | + }, |
| 84 | + async () => { |
| 85 | + const stream = provider.createMessage("", messages, { taskId: "generate-commit" }) |
| 86 | + |
| 87 | + let commitMessage = "" |
| 88 | + for await (const chunk of stream) { |
| 89 | + if (chunk.type === "text") { |
| 90 | + commitMessage += chunk.text |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + const finalMessage = commitMessage.replace(/```/g, "").trim() |
| 95 | + gitApi.repositories[0].inputBox.value = finalMessage |
| 96 | + }, |
| 97 | + ) |
| 98 | +} |
0 commit comments