-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/pr assistant #14
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
base: main
Are you sure you want to change the base?
Changes from all commits
257fe8c
0ab78cf
c676175
ee80a5d
180ad83
26bee11
93ff26e
7ff3fa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| namespace App\Actions\Github; | ||
|
|
||
| use App\Actions\Github\Auth\GetAuthenticatedAccountGithubClient; | ||
| use App\Enums\GithubWebhookEvents; | ||
| use App\Models\SourceCodeAccount; | ||
| use App\SourceCode\DTO\RepositoryName; | ||
| use Illuminate\Support\Str; | ||
|
|
@@ -22,11 +23,11 @@ public function handle(SourceCodeAccount $account, RepositoryName $repositoryNam | |
| ->create($repositoryName->username, $repositoryName->name, [ | ||
| 'name' => 'web', | ||
| 'config' => [ | ||
| 'url' => route('webhook', ['sourceCodeAccount' => $account]), | ||
| 'url' => config('services.ngrok.active_helper')? config('services.ngrok.ngrok_domain')."/webhook/{$account->id}" : route('webhook', ['sourceCodeAccount' => $account]), | ||
| 'content_type' => 'json', | ||
| 'secret' => $secret, | ||
| ], | ||
| 'events' => ['push'], | ||
| 'events' => [GithubWebhookEvents::PUSH->value, GithubWebhookEvents::PULL_REQUEST_COMMENT->value], | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mola! |
||
| 'active' => true, | ||
| ]); | ||
|
|
||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't you use the SourceCodeProvider of Github here? We have the repo and everything :-) Also, using it will make it easier for when you implement Gitlab and Bitbucket! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <?php | ||
|
|
||
| namespace App\Actions\PullRequestAssistant\Github; | ||
|
|
||
| use App\Services\GithubService; | ||
| use Lorisleiva\Actions\Concerns\AsAction; | ||
|
|
||
| class GetOriginalFileFromRepository | ||
| { | ||
| use AsAction; | ||
|
|
||
| public function __construct( | ||
| private GithubService $githubService | ||
| ) | ||
| {} | ||
|
|
||
| public function handle(string $repositoryOwner, string $repository, string $branch, string $filePath) | ||
| { | ||
| return $this->githubService->getFileFromRepository($repositoryOwner, $repository, $branch, $filePath); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| <?php | ||
|
|
||
| namespace App\Actions\PullRequestAssistant\Github; | ||
|
|
||
| use Exception; | ||
| use Illuminate\Http\Request; | ||
| use Lorisleiva\Actions\Concerns\AsAction; | ||
|
|
||
| class HandlePullRequestComment | ||
| { | ||
| use AsAction; | ||
|
|
||
| public const COMMIT_MESSAGE = 'Codex - PR Assitant commit'; | ||
|
|
||
| public function handle(Request $request): bool | ||
| { | ||
| if($request->get('action') !== 'created') { | ||
| return false; | ||
| } | ||
|
|
||
| if(!str($request->input('comment.body'))->startsWith('/codex')) { | ||
| return false; | ||
| } | ||
|
|
||
| $commentContent = str($request->input('comment.body'))->after('/codex '); | ||
| [$repositoryOwner, $repository, $branch, $filePath, $startLine, $endLine] = $this->getRepositoryInformation($request); | ||
|
|
||
| $fileLines = GetOriginalFileFromRepository::run($repositoryOwner, $repository, $branch, $filePath); | ||
| $requestChangedFileLines = $this->adaptFileLinesToLLMRequest($fileLines, $startLine, $endLine); | ||
| $formattedLLMResponse = SendRequestToLLM::run($requestChangedFileLines, $commentContent); | ||
|
|
||
| if ($formattedLLMResponse === null || $formattedLLMResponse === []) { | ||
| return false; | ||
| } | ||
|
|
||
| $newFile = $this->generateNewFileWithLLMResponse($fileLines, $startLine, $endLine, $formattedLLMResponse); | ||
|
|
||
| PushNewFileToGithub::run($repository, $branch, $filePath, $repositoryOwner, $newFile, self::COMMIT_MESSAGE); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private function getRepositoryInformation(Request $request): array | ||
| { | ||
| return [ | ||
| $request->input('repository.owner.login'), | ||
| $request->input('repository.name'), | ||
| $request->input('pull_request.head.ref'), | ||
| $request->input('comment.path'), | ||
| $request->input('comment.start_line'), | ||
| $request->input('comment.line'), | ||
| ]; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| private function adaptFileLinesToLLMRequest(array $fileLines, ?int $startLine, ?int $endLine) | ||
| { | ||
| if($startLine) { | ||
| array_splice($fileLines, $endLine - 1, 0); | ||
|
|
||
| $originalFileFormatted = []; | ||
| foreach ($fileLines as $line => $content) { | ||
| if ($line >= ($startLine - 1) && $line <= ($endLine - 1)) { | ||
| $originalFileFormatted[] = $content; | ||
| } | ||
| } | ||
| return json_encode($originalFileFormatted); | ||
| } | ||
|
|
||
| //todo: change when no start line | ||
| throw new Exception('You should add lines intervals', 404); | ||
| } | ||
|
|
||
| private function generateNewFileWithLLMResponse($fileLines, $startLine, $endLine, $formattedLLMResponse) | ||
| { | ||
| array_splice($fileLines, $startLine - 1, $endLine - $startLine + 1, $formattedLLMResponse); | ||
| return implode($fileLines); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| namespace App\Actions\PullRequestAssistant\Github; | ||
|
|
||
| use App\Services\GithubService; | ||
| use Lorisleiva\Actions\Concerns\AsAction; | ||
|
|
||
| class PushNewFileToGithub | ||
| { | ||
| use AsAction; | ||
|
|
||
| public function __construct( | ||
| private GithubService $githubService | ||
| ){} | ||
|
|
||
| public function handle(string $repository, string $branch, string $filePath, string $owner, string $newContent, string $commitMessage) | ||
| { | ||
| $this->githubService->pushNewContentToRepository($repository, $branch, $filePath, $owner, $newContent, $commitMessage); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To refactor and add into the SourceCodeProvider |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| namespace App\Actions\PullRequestAssistant\Github; | ||
|
|
||
| use App\LLM\Contracts\Llm; | ||
| use GuzzleHttp\Client; | ||
| use Lorisleiva\Actions\Concerns\AsAction; | ||
|
|
||
| class SendRequestToLLM | ||
| { | ||
| use AsAction; | ||
|
|
||
| public function handle(string $requestChangesFileLines, $commentContent) | ||
| { | ||
| $prompt = 'I am giving to you an array with this format: '.$requestChangesFileLines.'. Each /n means a new line in the file and I want you give me back the same format but: '.$commentContent; | ||
| $systemPrompt = 'You are an expert in modifying files changing exactly only the things you are asked to and return the same format you are asked'; | ||
|
|
||
| $llm = app(Llm::class); | ||
| $response = $llm->completion($systemPrompt, $prompt); | ||
| $formattedResponse = $this->formatResponse($response->completion); | ||
| if (! array_key_exists(0, $formattedResponse)) { | ||
| return []; | ||
| } | ||
|
Comment on lines
+15
to
+23
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be a Prompt class like you made for the Technical description? |
||
|
|
||
| return json_decode($formattedResponse[0]); | ||
| } | ||
|
|
||
|
|
||
| private function formatResponse($content) | ||
| { | ||
| preg_match("/\[(.*?)\]/", $content, $formattedResponse); | ||
|
|
||
| return $formattedResponse; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php | ||
|
|
||
| namespace App\Clients; | ||
|
|
||
| use App\Clients\Requests\Github\Contract\GithubRequest; | ||
| use GuzzleHttp\Client; | ||
| use GuzzleHttp\Exception\ClientException; | ||
|
|
||
| class GithubClient { | ||
|
|
||
| private Client $client; | ||
|
|
||
| public function send(GithubRequest $request) { | ||
| $this->setClient($request); | ||
| $options = $this->getOptions($request); | ||
| $response = $this->client->request($request->getMethod(), $request->getUri(), $options); | ||
| return $request->transformResponse(json_decode($response->getBody()->getContents(), true)); | ||
| } | ||
|
|
||
| private function setClient(GithubRequest $githubRequest) | ||
| { | ||
| $this->client = new Client([ | ||
| 'base_uri' => config('services.github.api_endpoint'), | ||
| 'headers' => [ | ||
| 'Authorization' => 'Bearer '.$githubRequest->getAccessToken(), | ||
| 'Accept' => 'application/vnd.github.v3+json', | ||
| ] | ||
| ]); | ||
|
Comment on lines
+22
to
+28
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not entirely clear why we should use raw HTTP calls when we have also the libraries of GH, GL & BB installed! Can you explain the reasoning? :-) |
||
| } | ||
|
|
||
| private function getOptions(GithubRequest $request): array | ||
| { | ||
| $options = []; | ||
|
|
||
| if($request->getBody()) { | ||
| $options['body'] = json_encode($request->getBody()); | ||
| } | ||
|
|
||
| return $options; | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <?php | ||
|
|
||
| namespace App\Clients\Requests\Github\Contract; | ||
|
|
||
| use App\Models\SourceCodeAccount; | ||
|
|
||
| abstract class GithubRequest | ||
| { | ||
| public abstract function getMethod(): string; | ||
| public abstract function getUri(): string; | ||
| public abstract function getAccessToken(): ?string; | ||
|
|
||
| public function transformResponse(array $response): array|string | ||
| { | ||
| return $response; | ||
| } | ||
|
Comment on lines
+13
to
+16
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of like this pattern but would like it more if the transform returns something typed |
||
| public function getBody(): ?array | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be changed to use library? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?php | ||
|
|
||
| namespace App\Clients\Requests\Github; | ||
|
|
||
| use App\Clients\Requests\Github\Contract\GithubRequest; | ||
| use App\Enums\RestRequest; | ||
| use App\SourceCode\Traits\GetGithubAccessToken; | ||
|
|
||
| class GetFileRequest extends GithubRequest | ||
| { | ||
| use GetGithubAccessToken; | ||
|
|
||
| public function __construct( | ||
| private string $repositoryOwner, | ||
| private string $repository, | ||
| private string $branch, | ||
| private string $filePath | ||
| ){} | ||
|
|
||
| public function getMethod(): string | ||
| { | ||
| return RestRequest::GET->value; | ||
| } | ||
|
|
||
| public function getUri(): string | ||
| { | ||
| return "/repos/{$this->repositoryOwner}/{$this->repository}/contents/{$this->filePath}?ref=$this->branch"; | ||
| } | ||
|
|
||
| public function transformResponse(array $response): array | ||
| { | ||
| $fileContent = base64_decode($response['content']); | ||
| $file = preg_split('/(?<=\n)/', $fileContent); | ||
| return $file; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to use Laravel's HTTP client