Skip to content

Commit ccd9c17

Browse files
committed
feature-233-implement-openrouter: Enhance CLI and add AskActionUseCase for AI interactions
- Updated CLI to include new commands for asking AI and improved command descriptions using constants. - Introduced AskActionUseCase to handle AI queries based on issue comments and pull request reviews. - Refactored SingleAction class to support new action types and improved action handling logic. - Enhanced logging throughout the application for better tracking of AI interactions and processing steps. - Updated constants for better maintainability and clarity in the codebase.
1 parent b29bf7f commit ccd9c17

9 files changed

Lines changed: 284 additions & 33 deletions

File tree

src/actions/local_action.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Tokens } from '../data/model/tokens';
2121
import { Workflows } from '../data/model/workflows';
2222
import { ProjectRepository } from '../data/repository/project_repository';
2323
import { DEFAULT_IMAGE_CONFIG, INPUT_KEYS } from '../utils/constants';
24+
import { logInfo } from '../utils/logger';
2425
import { getActionInputsWithDefaults } from '../utils/yml_utils';
2526
import { mainRun } from './common_action';
2627

@@ -616,5 +617,7 @@ export async function runLocalAction(additionalParams: any): Promise<void> {
616617
additionalParams,
617618
)
618619

619-
await mainRun(execution);
620+
const results = await mainRun(execution);
621+
622+
logInfo(`Results: ${JSON.stringify(results, null, 2)}`);
620623
}

src/cli.ts

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22

33
import { Command } from 'commander';
4-
import { INPUT_KEYS, ERRORS } from './utils/constants';
4+
import { INPUT_KEYS, ERRORS, ACTIONS, TITLE, COMMAND } from './utils/constants';
55
import { runLocalAction } from './actions/local_action';
66
import * as dotenv from 'dotenv';
77
import { execSync } from 'child_process';
@@ -34,11 +34,11 @@ function getGitInfo() {
3434
}
3535

3636
program
37-
.name('git-board-flow')
38-
.description('CLI tool for Git Board Flow')
37+
.name(COMMAND)
38+
.description(`CLI tool for ${TITLE}`)
3939
.version('1.0.0');
4040

41-
program
41+
program
4242
.command('build-ai')
4343
.description('Build AI')
4444
.option('-i, --issue <number>', 'Issue number to process', '1')
@@ -55,7 +55,7 @@ program
5555

5656
const params = {
5757
[INPUT_KEYS.DEBUG]: options.debug.toString(),
58-
[INPUT_KEYS.SINGLE_ACTION]: 'vector_action',
58+
[INPUT_KEYS.SINGLE_ACTION]: ACTIONS.VECTOR,
5959
[INPUT_KEYS.SINGLE_ACTION_ISSUE]: options.issue,
6060
[INPUT_KEYS.SUPABASE_URL]: process.env.SUPABASE_URL,
6161
[INPUT_KEYS.SUPABASE_KEY]: process.env.SUPABASE_KEY,
@@ -82,7 +82,69 @@ program
8282
margin: 1,
8383
borderStyle: 'round',
8484
borderColor: 'cyan',
85-
title: 'Git Board Flow',
85+
title: TITLE,
86+
titleAlignment: 'center'
87+
}
88+
)
89+
);
90+
91+
runLocalAction(params);
92+
});
93+
94+
program
95+
.command('ask-ai')
96+
.description('Ask AI')
97+
.option('-i, --issue <number>', 'Issue number to process', '1')
98+
.option('-b, --branch <name>', 'Branch name', 'master')
99+
.option('-d, --debug', 'Debug mode', false)
100+
.option('-t, --token <token>', 'Personal access token', process.env.PERSONAL_ACCESS_TOKEN)
101+
.action((options) => {
102+
const gitInfo = getGitInfo();
103+
104+
if ('error' in gitInfo) {
105+
console.log(gitInfo.error);
106+
return;
107+
}
108+
109+
const commentBody = `@landa-bot where is should add new constants?`;
110+
111+
const params = {
112+
[INPUT_KEYS.DEBUG]: options.debug.toString(),
113+
[INPUT_KEYS.SINGLE_ACTION]: ACTIONS.ASK,
114+
[INPUT_KEYS.SINGLE_ACTION_ISSUE]: options.issue,
115+
[INPUT_KEYS.SUPABASE_URL]: process.env.SUPABASE_URL,
116+
[INPUT_KEYS.SUPABASE_KEY]: process.env.SUPABASE_KEY,
117+
[INPUT_KEYS.TOKEN]: process.env.PERSONAL_ACCESS_TOKEN,
118+
[INPUT_KEYS.AI_IGNORE_FILES]: 'dist/*,bin/*',
119+
repo: {
120+
owner: gitInfo.owner,
121+
repo: gitInfo.repo,
122+
},
123+
commits: {
124+
ref: `refs/heads/${options.branch}`,
125+
},
126+
eventName: 'issue_comment',
127+
comment: {
128+
body: commentBody,
129+
},
130+
pull_request_review_comment: {
131+
body: commentBody,
132+
},
133+
issue: {
134+
number: parseInt(options.issue),
135+
},
136+
}
137+
138+
logInfo(
139+
boxen(
140+
chalk.cyan('🚀 Asking AI started\n') +
141+
chalk.gray(`Asking AI on ${gitInfo.owner}/${gitInfo.repo}/${options.branch}...`),
142+
{
143+
padding: 1,
144+
margin: 1,
145+
borderStyle: 'round',
146+
borderColor: 'cyan',
147+
title: TITLE,
86148
titleAlignment: 'center'
87149
}
88150
)

src/data/model/single_action.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1+
import { ACTIONS } from "../../utils/constants";
12
import { logError } from "../../utils/logger";
23

3-
const deployedAction = 'deployed_action'
4-
const vectorAction = 'vector_action'
5-
64
export class SingleAction {
75
currentSingleAction: string;
86
currentSingleActionIssue: number = -1;
9-
actions: string[] = [deployedAction, vectorAction];
7+
actions: string[] = [ACTIONS.DEPLOYED, ACTIONS.VECTOR, ACTIONS.ASK];
108
isIssue: boolean = false;
119
isPullRequest: boolean = false;
1210
isPush: boolean = false;
1311

1412
get isDeployedAction(): boolean {
15-
return this.currentSingleAction === deployedAction;
13+
return this.currentSingleAction === ACTIONS.DEPLOYED;
1614
}
1715

1816
get isVectorAction(): boolean {
19-
return this.currentSingleAction === vectorAction;
17+
return this.currentSingleAction === ACTIONS.VECTOR;
18+
}
19+
20+
get isAskAction(): boolean {
21+
return this.currentSingleAction === ACTIONS.ASK;
2022
}
2123

2224
get enabledSingleAction(): boolean {

src/data/repository/ai_repository.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { REPO_URL, TITLE } from '../../utils/constants';
12
import { logDebugInfo, logError } from '../../utils/logger';
23
import { Ai } from '../model/ai';
34

@@ -41,8 +42,8 @@ export class AiRepository {
4142
method: 'POST',
4243
headers: {
4344
Authorization: `Bearer ${apiKey}`,
44-
'HTTP-Referer': 'https://github.com/landamessenger/git-board-flow',
45-
'X-Title': 'Git Board Flow',
45+
'HTTP-Referer': REPO_URL,
46+
'X-Title': TITLE,
4647
'Content-Type': 'application/json',
4748
},
4849
body: JSON.stringify(requestBody),

src/data/repository/supabase_repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class SupabaseRepository {
110110
doc.type,
111111
doc.index,
112112
doc.chunk_index,
113-
doc.chunk,
113+
doc.content,
114114
doc.shasum,
115115
doc.vector
116116
));
@@ -271,7 +271,7 @@ export class SupabaseRepository {
271271
doc.type,
272272
doc.index,
273273
doc.chunk_index,
274-
doc.chunk,
274+
doc.content,
275275
doc.shasum,
276276
doc.vector
277277
));
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { ChunkedFile } from '../../data/model/chunked_file';
2+
import { ChunkedFileChunk } from '../../data/model/chunked_file_chunk';
3+
import { Execution } from '../../data/model/execution';
4+
import { Result } from '../../data/model/result';
5+
import { DockerRepository } from '../../data/repository/docker_repository';
6+
import { FileRepository } from '../../data/repository/file_repository';
7+
import { SupabaseRepository } from '../../data/repository/supabase_repository';
8+
import { logDebugInfo, logError, logInfo, logSingleLine } from '../../utils/logger';
9+
import { ParamUseCase } from '../base/param_usecase';
10+
11+
export class AskActionUseCase implements ParamUseCase<Execution, Result[]> {
12+
taskId: string = 'AskActionUseCase';
13+
private dockerRepository: DockerRepository = new DockerRepository();
14+
private fileRepository: FileRepository = new FileRepository();
15+
private readonly CODE_INSTRUCTION_BLOCK = "Represent the code for semantic search";
16+
private readonly CODE_INSTRUCTION_LINE = "Represent each line of code for retrieval";
17+
private readonly CODE_INSTRUCTION_ASK = "Represent the question for retrieving relevant code snippets";
18+
19+
async invoke(param: Execution): Promise<Result[]> {
20+
logInfo(`Executing ${this.taskId}.`)
21+
22+
const results: Result[] = [];
23+
24+
try {
25+
/**
26+
* Check if the user from the token is found.
27+
*/
28+
if (!param.tokenUser) {
29+
results.push(
30+
new Result({
31+
id: this.taskId,
32+
success: false,
33+
executed: false,
34+
errors: [
35+
`User from token not found.`,
36+
],
37+
})
38+
)
39+
return results;
40+
}
41+
42+
/**
43+
* Get the comment body.
44+
*/
45+
let commentBody = '';
46+
if (param.issue.isIssueComment) {
47+
commentBody = param.issue.commentBody;
48+
} else if (param.pullRequest.isPullRequestReviewComment) {
49+
commentBody = param.pullRequest.commentBody;
50+
} else {
51+
logError(`Not a valid comment body.`);
52+
results.push(
53+
new Result({
54+
id: this.taskId,
55+
success: true,
56+
executed: false,
57+
})
58+
)
59+
return results;
60+
}
61+
62+
/**
63+
* Check if the comment body includes the user from the token.
64+
*/
65+
if (!commentBody.includes(param.tokenUser)) {
66+
results.push(
67+
new Result({
68+
id: this.taskId,
69+
success: true,
70+
executed: false,
71+
})
72+
)
73+
return results;
74+
} else {
75+
commentBody = commentBody.replace(param.tokenUser, '').trim();
76+
}
77+
78+
/**
79+
* Check if the supabase config is found.
80+
*/
81+
if (!param.supabaseConfig) {
82+
results.push(
83+
new Result({
84+
id: this.taskId,
85+
success: false,
86+
executed: true,
87+
steps: [
88+
`Supabase config not found.`,
89+
],
90+
})
91+
)
92+
return results;
93+
}
94+
95+
const supabaseRepository: SupabaseRepository = new SupabaseRepository(param.supabaseConfig);
96+
97+
await this.dockerRepository.startContainer(param);
98+
99+
const systemInfo = await this.dockerRepository.getSystemInfo(param);
100+
const chunkSize = systemInfo.parameters.chunk_size as number;
101+
const maxWorkers = systemInfo.parameters.max_workers as number;
102+
103+
logInfo(`🧑‍🏭 Max workers: ${maxWorkers}`);
104+
logInfo(`🚚 Chunk size: ${chunkSize}`);
105+
logInfo(`📦 Getting chunked files for ${param.owner}/${param.repo}/${param.commit.branch}`);
106+
107+
const startTime = Date.now();
108+
109+
const embeddings = await this.dockerRepository.getEmbedding(
110+
param,
111+
[
112+
[this.CODE_INSTRUCTION_ASK, commentBody]
113+
]
114+
);
115+
116+
logInfo(`🔎 Embeddings: ${JSON.stringify(embeddings, null, 2)}`);
117+
118+
const types = ['line', 'block'];
119+
const chunks: ChunkedFileChunk[] = [];
120+
for (const type of types) {
121+
logInfo(`📦 🔎 Matching chunks for ${param.owner}/${param.repo}/${param.commit.branch}`);
122+
const foundChunks = await supabaseRepository.matchChunks(
123+
param.owner,
124+
param.repo,
125+
param.commit.branch,
126+
type,
127+
embeddings[0],
128+
5
129+
);
130+
131+
for (const chunk of foundChunks) {
132+
logDebugInfo(`📦 🔎 Chunk type: ${type} - ${chunk.path}`);
133+
}
134+
135+
chunks.push(...foundChunks);
136+
}
137+
138+
139+
140+
const totalDurationSeconds = (Date.now() - startTime) / 1000;
141+
logInfo(`📦 🔎 Matched chunks for ${param.owner}/${param.repo}/${param.commit.branch}:\n Total duration: ${Math.ceil(totalDurationSeconds)} seconds`);
142+
143+
results.push(
144+
new Result({
145+
id: this.taskId,
146+
success: true,
147+
executed: true,
148+
steps: [
149+
`Vector action executed successfully.`,
150+
],
151+
})
152+
);
153+
154+
} catch (error) {
155+
logError(`Error in ${this.taskId}: ${JSON.stringify(error, null, 2)}`);
156+
results.push(
157+
new Result({
158+
id: this.taskId,
159+
success: false,
160+
executed: true,
161+
steps: [
162+
`Error in ${this.taskId}: ${JSON.stringify(error, null, 2)}`,
163+
],
164+
})
165+
);
166+
} finally {
167+
await this.dockerRepository.stopContainer(param);
168+
}
169+
170+
return results;
171+
}
172+
}

src/usecase/vector_action_use_case.ts renamed to src/usecase/actions/vector_action_use_case.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ChunkedFile } from '../data/model/chunked_file';
2-
import { ChunkedFileChunk } from '../data/model/chunked_file_chunk';
3-
import { Execution } from '../data/model/execution';
4-
import { Result } from '../data/model/result';
5-
import { DockerRepository } from '../data/repository/docker_repository';
6-
import { FileRepository } from '../data/repository/file_repository';
7-
import { SupabaseRepository } from '../data/repository/supabase_repository';
8-
import { logDebugInfo, logError, logInfo, logSingleLine } from '../utils/logger';
9-
import { ParamUseCase } from './base/param_usecase';
1+
import { ChunkedFile } from '../../data/model/chunked_file';
2+
import { ChunkedFileChunk } from '../../data/model/chunked_file_chunk';
3+
import { Execution } from '../../data/model/execution';
4+
import { Result } from '../../data/model/result';
5+
import { DockerRepository } from '../../data/repository/docker_repository';
6+
import { FileRepository } from '../../data/repository/file_repository';
7+
import { SupabaseRepository } from '../../data/repository/supabase_repository';
8+
import { logDebugInfo, logError, logInfo, logSingleLine } from '../../utils/logger';
9+
import { ParamUseCase } from '../base/param_usecase';
1010

1111
export class VectorActionUseCase implements ParamUseCase<Execution, Result[]> {
1212
taskId: string = 'VectorActionUseCase';
@@ -140,14 +140,14 @@ export class VectorActionUseCase implements ParamUseCase<Execution, Result[]> {
140140
);
141141

142142
} catch (error) {
143-
logError('Error in VectorActionUseCase: ' + JSON.stringify(error, null, 2));
143+
logError(`Error in ${this.taskId}: ${JSON.stringify(error, null, 2)}`);
144144
results.push(
145145
new Result({
146146
id: this.taskId,
147147
success: false,
148148
executed: true,
149-
steps: [
150-
`Error in VectorActionUseCase: ${JSON.stringify(error, null, 2)}`,
149+
errors: [
150+
`Error in ${this.taskId}: ${JSON.stringify(error, null, 2)}`,
151151
],
152152
})
153153
);

0 commit comments

Comments
 (0)