feat(ai): Add comprehensive pre-commit AI code review
Introduces a robust system for running automated, staged diff reviews against various large language models. This feature allows users to submit their changes to external AI services and receive structured feedback on potential bugs, security issues, and maintainability risks before committing. The implementation covers the entire stack: * Backend logic was added to handle API communication with OpenAI, Anthropic, and custom endpoints. * A dedicated parser ensures that complex JSON outputs from LLMs are reliably converted into structured findings (severity, title, description). * New components and UI elements provide a clear visualization of the AI's assessment and actionable suggestions. - Supports multiple major LLM providers (OpenAI, Anthropic) - Parses structured JSON output for consistent review results - Adds dedicated UI dialog to display AI findings and risk level
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import RepoToolbar from "./lib/RepoToolbar.svelte";
|
||||
import RepoTabs from "./lib/RepoTabs.svelte";
|
||||
import AiSettingsDialog from "./lib/components/AiSettingsDialog.svelte";
|
||||
import AiReviewDialog from "./lib/components/AiReviewDialog.svelte";
|
||||
import AnalyticsNoticeDialog from "./lib/components/AnalyticsNoticeDialog.svelte";
|
||||
import AppSettingsDialog from "./lib/components/AppSettingsDialog.svelte";
|
||||
import BranchDeleteConfirmDialog from "./lib/components/BranchDeleteConfirmDialog.svelte";
|
||||
@@ -46,6 +47,7 @@
|
||||
cloneRepository,
|
||||
commit,
|
||||
commitAiGenerate,
|
||||
commitAiReview,
|
||||
commitAiLoad,
|
||||
commitAiLocalModels,
|
||||
commitAiStatus,
|
||||
@@ -107,6 +109,7 @@
|
||||
} from "./lib/git";
|
||||
|
||||
import type {
|
||||
AiReviewResult,
|
||||
AiSettings,
|
||||
AppLanguage,
|
||||
AppTheme,
|
||||
@@ -250,6 +253,9 @@
|
||||
let lastLocalAiGeneratedMessage = "";
|
||||
let commitAiPhase: CommitAiPhase = "idle";
|
||||
let commitAiGenerating = false;
|
||||
let commitAiReviewing = false;
|
||||
let aiReviewResult: AiReviewResult | null = null;
|
||||
let aiReviewOpen = false;
|
||||
let commitAiPollTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let aiSettings: AiSettings = defaultAiSettings();
|
||||
let aiSettingsOpen = false;
|
||||
@@ -931,6 +937,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function reviewStagedWithAi() {
|
||||
if (!activeRepoPath || commitAiReviewing || stagedCount === 0) return;
|
||||
if (aiSettings.provider === "local") {
|
||||
errorMessage = "Pre-commit review currently requires OpenAI, Anthropic, or a custom endpoint.";
|
||||
return;
|
||||
}
|
||||
commitAiReviewing = true;
|
||||
errorMessage = "";
|
||||
try {
|
||||
if (aiSettings.provider === "openai") {
|
||||
const cred = await credLoad("ai:openai");
|
||||
aiReviewResult = await commitAiReview(activeRepoPath, {
|
||||
provider: "openai",
|
||||
model: aiSettings.openaiModel,
|
||||
apiKey: cred?.password,
|
||||
});
|
||||
} else if (aiSettings.provider === "anthropic") {
|
||||
const cred = await credLoad("ai:anthropic");
|
||||
aiReviewResult = await commitAiReview(activeRepoPath, {
|
||||
provider: "anthropic",
|
||||
model: aiSettings.anthropicModel,
|
||||
apiKey: cred?.password,
|
||||
});
|
||||
} else {
|
||||
const cred = await credLoad("ai:custom");
|
||||
aiReviewResult = await commitAiReview(activeRepoPath, {
|
||||
provider: "custom",
|
||||
model: aiSettings.customModel,
|
||||
baseUrl: aiSettings.customBaseUrl,
|
||||
apiKey: cred?.password,
|
||||
});
|
||||
}
|
||||
aiReviewOpen = true;
|
||||
trackEvent("ai_precommit_review", {
|
||||
provider: aiSettings.provider,
|
||||
findings: aiReviewResult.findings.length,
|
||||
risk: aiReviewResult.risk,
|
||||
});
|
||||
} catch (error) {
|
||||
errorMessage = errorToMessage(error);
|
||||
} finally {
|
||||
commitAiReviewing = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAutoRefresh() {
|
||||
autoRefreshEnabled = !autoRefreshEnabled;
|
||||
if (autoRefreshEnabled) void autoRefreshTick();
|
||||
@@ -4012,11 +4063,13 @@
|
||||
commitAiProvider={aiSettings.provider}
|
||||
{commitAiPhase}
|
||||
{commitAiGenerating}
|
||||
{commitAiReviewing}
|
||||
{canAmend}
|
||||
{amendMode}
|
||||
onCommit={commitChanges}
|
||||
onCommitMessageChange={updateCommitMessage}
|
||||
onGenerateCommitMessage={generateCommitMessageWithAi}
|
||||
onReviewStaged={reviewStagedWithAi}
|
||||
onOpenAiSettings={() => { aiSettingsOpen = true; }}
|
||||
onToggleAmend={toggleAmendMode}
|
||||
onUndoLastCommit={undoLastCommitChange}
|
||||
@@ -4147,6 +4200,16 @@
|
||||
<HelpOverlay language={appLanguage} onClose={() => { helpOpen = false; }} />
|
||||
{/if}
|
||||
|
||||
{#if aiReviewOpen && aiReviewResult}
|
||||
<AiReviewDialog
|
||||
result={aiReviewResult}
|
||||
provider={aiSettings.provider}
|
||||
isReviewing={commitAiReviewing}
|
||||
onRerun={reviewStagedWithAi}
|
||||
onClose={() => { aiReviewOpen = false; }}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if linePatchOpen && linePatchFile}
|
||||
<LinePatchDialog
|
||||
file={linePatchFile}
|
||||
|
||||
Reference in New Issue
Block a user