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

AI 요약 기능 도입을 위한 사이드바뷰 도입 및 엔진 호출방식 변경 #754

Merged
merged 8 commits into from
Oct 6, 2024
119 changes: 119 additions & 0 deletions packages/analysis-engine/src/engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import "reflect-metadata";

import { container } from "tsyringe";

import { buildCommitDict } from "./commit.util";
import { buildCSMDict } from "./csm";
import getCommitRaws from "./parser";
import { PluginOctokit } from "./pluginOctokit";
import { buildStemDict } from "./stem";
import { getSummary } from "./summary";
import type { CommitNode } from "./types";

export type AnalysisEngineArgs = {
isDebugMode?: boolean;
gitLog: string;
owner: string;
repo: string;
baseBranchName: string;
auth?: string;
};

export class AnalysisEngine {
private static instance: AnalysisEngine | null = null;
private gitLog!: string;
private isDebugMode?: boolean;
private octokit!: PluginOctokit;
private baseBranchName!: string;
private nodes?: CommitNode[];
private isInitialized: boolean = false;

private constructor() {}

public static getInstance(): AnalysisEngine {
if (!AnalysisEngine.instance) {
AnalysisEngine.instance = new AnalysisEngine();
}
return AnalysisEngine.instance;
}

public initialize(args: AnalysisEngineArgs): void {
const { isDebugMode, gitLog, owner, repo, auth, baseBranchName } = args;
this.gitLog = gitLog;
this.baseBranchName = baseBranchName;
this.isDebugMode = isDebugMode;
container.register("OctokitOptions", {
useValue: {
owner,
repo,
options: {
auth,
},
},
});
this.octokit = container.resolve(PluginOctokit);
this.isInitialized = true;
}

private checkInitialization() {
if (!this.isInitialized) {
throw new Error("AnalysisEngine is not initialized. Call initialize() first.");
}
}

public async analyzeGit() {
this.checkInitialization();

if (!this.gitLog) {
throw new Error("AnalysisEngine is not initialized. Call initialize() first.");
}

let isPRSuccess = true;
if (this.isDebugMode) console.log("baseBranchName: ", this.baseBranchName);

const commitRaws = getCommitRaws(this.gitLog);
if (this.isDebugMode) console.log("commitRaws: ", commitRaws);

const commitDict = buildCommitDict(commitRaws);
if (this.isDebugMode) console.log("commitDict: ", commitDict);

const pullRequests = await this.octokit
.getPullRequests()
.catch((err) => {
console.error(err);
isPRSuccess = false;
return [];
})
.then((pullRequests) => {
console.log("success, pr = ", pullRequests);
return pullRequests;
});
if (this.isDebugMode) console.log("pullRequests: ", pullRequests);

const stemDict = buildStemDict(commitDict, this.baseBranchName);
if (this.isDebugMode) console.log("stemDict: ", stemDict);
const csmDict = buildCSMDict(commitDict, stemDict, this.baseBranchName, pullRequests);
if (this.isDebugMode) console.log("csmDict: ", csmDict);
this.nodes = stemDict.get(this.baseBranchName)?.nodes;

return {
isPRSuccess,
csmDict,
};
}

public updateArgs(args: AnalysisEngineArgs) {
if (container.isRegistered("OctokitOptions")) container.clearInstances();
this.initialize(args);
}

public async geminiCommitSummary() {
this.checkInitialization();
if (!this.nodes) {
throw new Error("No commits available. Run analyzeGit() first.");
}
return await getSummary(this.nodes.slice(-10).map(({ commit }) => commit));
}
}

export default AnalysisEngine;
98 changes: 2 additions & 96 deletions packages/analysis-engine/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,2 @@
import "reflect-metadata";

import { container } from "tsyringe";

import { buildCommitDict } from "./commit.util";
import { buildCSMDict } from "./csm";
import getCommitRaws from "./parser";
import { PluginOctokit } from "./pluginOctokit";
import { buildStemDict } from "./stem";
import { getCurrentUserCommitSummary, getLatestCommitSummary } from "./summary";

type AnalysisEngineArgs = {
isDebugMode?: boolean;
gitLog: string;
owner: string;
repo: string;
baseBranchName: string;
auth?: string;
};

export class AnalysisEngine {
private gitLog!: string;

private isDebugMode?: boolean;

private octokit!: PluginOctokit;

private baseBranchName!: string;

constructor(args: AnalysisEngineArgs) {
this.insertArgs(args);
}

private insertArgs = (args: AnalysisEngineArgs) => {
const { isDebugMode, gitLog, owner, repo, auth, baseBranchName } = args;
this.gitLog = gitLog;
this.baseBranchName = baseBranchName;
this.isDebugMode = isDebugMode;
container.register("OctokitOptions", {
useValue: {
owner,
repo,
options: {
auth,
},
},
});
this.octokit = container.resolve(PluginOctokit);
};

public analyzeGit = async () => {
let isPRSuccess = true;
if (this.isDebugMode) console.log("baseBranchName: ", this.baseBranchName);

const commitRaws = getCommitRaws(this.gitLog);
if (this.isDebugMode) console.log("commitRaws: ", commitRaws);

const commitDict = buildCommitDict(commitRaws);
if (this.isDebugMode) console.log("commitDict: ", commitDict);

const pullRequests = await this.octokit
.getPullRequests()
.catch((err) => {
console.error(err);
isPRSuccess = false;
return [];
})
.then((pullRequests) => {
console.log("success, pr = ", pullRequests);
return pullRequests;
});
if (this.isDebugMode) console.log("pullRequests: ", pullRequests);

const stemDict = buildStemDict(commitDict, this.baseBranchName);
if (this.isDebugMode) console.log("stemDict: ", stemDict);
const csmDict = buildCSMDict(commitDict, stemDict, this.baseBranchName, pullRequests);
if (this.isDebugMode) console.log("csmDict: ", csmDict);
const latestCommitSummary = await getLatestCommitSummary(stemDict, this.baseBranchName);
if (this.isDebugMode) console.log("latestCommitSummary: ", latestCommitSummary);

const currentUserCommitSummary = await getCurrentUserCommitSummary(stemDict, this.baseBranchName, this.octokit);
if (this.isDebugMode) console.log("currentUserCommitSummary: ", currentUserCommitSummary);

return {
isPRSuccess,
csmDict,
};
};

public updateArgs = (args: AnalysisEngineArgs) => {
if (container.isRegistered("OctokitOptions")) container.clearInstances();
this.insertArgs(args);
};
}

export default AnalysisEngine;
export type { AnalysisEngineArgs } from "./engine";
export { AnalysisEngine } from "./engine";
21 changes: 20 additions & 1 deletion packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"visual analytics"
],
"activationEvents": [
"*"
"*",
"onView:githruSidebar"
],
"main": "./dist/extension.js",
"contributes": {
Expand Down Expand Up @@ -68,6 +69,24 @@
"description": "Insert your primary color."
}
}
},
"viewsContainers": {
"activitybar": [
{
"id": "githruSidebarView",
"title": "Githru Sidebar",
"icon": "images/logo.png"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(질문) 요 로고 이미지는 vscode 패키지 안에 올려두지 않아도 동작이 되는 장치가 있는 걸까요?? diff에 이미지는 없는 것 같아서 여쭤봅니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 vscode 패키지안에 있던 로고입니다!

}
]
},
"views": {
"githruSidebarView": [
{
"type": "webview",
"id": "githruSidebar",
"name": "Githru Sidebar"
}
]
}
},
"scripts": {
Expand Down
8 changes: 7 additions & 1 deletion packages/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { COMMAND_LAUNCH, COMMAND_LOGIN_WITH_GITHUB, COMMAND_RESET_GITHUB_AUTH }
import { Credentials } from "./credentials";
import { GithubTokenUndefinedError, WorkspacePathUndefinedError } from "./errors/ExtensionError";
import { deleteGithubToken, getGithubToken, setGithubToken } from "./setting-repository";
import { SidebarProvider } from "./sidebar";
import { mapClusterNodesFrom } from "./utils/csm.mapper";
import {
findGit,
Expand All @@ -25,6 +26,10 @@ function normalizeFsPath(fsPath: string) {
}

export async function activate(context: vscode.ExtensionContext) {
const provider = new SidebarProvider(context.extensionUri);

context.subscriptions.push(vscode.window.registerWebviewViewProvider("githruSidebar", provider));

const { subscriptions, extensionPath, secrets } = context;
const credentials = new Credentials();
let currentPanel: vscode.WebviewPanel | undefined = undefined;
Expand Down Expand Up @@ -82,7 +87,8 @@ export async function activate(context: vscode.ExtensionContext) {
const { owner, repo: initialRepo } = getRepo(gitConfig);
webLoader.setGlobalOwnerAndRepo(owner, initialRepo);
const repo = initialRepo[0];
const engine = new AnalysisEngine({
const engine = AnalysisEngine.getInstance();
engine.initialize({
isDebugMode: true,
gitLog,
owner,
Expand Down
66 changes: 66 additions & 0 deletions packages/vscode/src/sidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { AnalysisEngine } from "@githru-vscode-ext/analysis-engine";
import type * as vscode from "vscode";

export class SidebarProvider implements vscode.WebviewViewProvider {
constructor(private readonly _extensionUri: vscode.Uri) {}

resolveWebviewView(webviewView: vscode.WebviewView) {
webviewView.webview.options = {
enableScripts: true,
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

webviewView.webview.onDidReceiveMessage(async (data) => {
const result = await this.callApi(data.apiNumber);
webviewView.webview.postMessage({ type: "apiResult", result });
});
}

private async callApi(apiNumber: number): Promise<string> {
const engine = AnalysisEngine.getInstance();
try {
const summary = await engine.geminiCommitSummary();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

epic에 머지되면 여기에 각각 함수 부르게 하면 되겠군요! 👍

console.log("Commit summary:", summary);
} catch (error) {
console.error("Error getting commit summary:", error);
}
return `API ${apiNumber} 호출 결과`;
}

private _getHtmlForWebview(webview: vscode.Webview) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Caller</title>
</head>
<body>
<button onclick="callApi(1)">Call AI Analyze 1</button>
<button onclick="callApi(2)">Call AI Analyze 2</button>
<button onclick="callApi(3)">Call AI Analyze 3</button>
<div id="result"></div>

<script>
const vscode = acquireVsCodeApi();

function callApi(apiNumber) {
vscode.postMessage({ type: 'apiCall', apiNumber });
}

window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'apiResult':
document.getElementById('result').innerText = message.result;
break;
}
});
</script>
</body>
</html>
`;
}
}