mirror of
https://github.com/Pull-Pal/pull-pal.git
synced 2026-06-13 23:19:09 -04:00
Implement Github client
Added "version control client" interface and created a Github implementation of it. Right now it only creates pull requests and lists issues. Restructued some code. Idk if it will be permanent. Next I plan to add behavior around replacing files in the local repo, and creating commits.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package pullpal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
"context"
|
||||
|
||||
"github.com/mobyvb/pull-pal/vc"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// PullPal is the service responsible for:
|
||||
@@ -12,104 +14,22 @@ import (
|
||||
// * Parsing LLM responses
|
||||
// * Interacting with LLM (e.g. with GPT via OpenAI API)
|
||||
type PullPal struct {
|
||||
ctx context.Context
|
||||
log *zap.Logger
|
||||
|
||||
vcClient vc.VCClient
|
||||
}
|
||||
|
||||
// File represents a file in a git repository.
|
||||
type File struct {
|
||||
Path string
|
||||
Contents string
|
||||
}
|
||||
|
||||
// CodeChangeRequest contains all necessary information for generating a prompt for a LLM.
|
||||
type CodeChangeRequest struct {
|
||||
Files []File
|
||||
Subject string
|
||||
Body string
|
||||
}
|
||||
|
||||
// String is the string representation of a CodeChangeRequest. Functionally, it contains the LLM prompt.
|
||||
func (req CodeChangeRequest) String() string {
|
||||
prompt := req.MustGetPrompt()
|
||||
return "START OF PROMPT\n" + prompt + "\nEND OF PROMPT"
|
||||
}
|
||||
|
||||
// MustGetPrompt only returns the prompt, but panics if the data in the request cannot populate the template.
|
||||
func (req CodeChangeRequest) MustGetPrompt() string {
|
||||
prompt, err := req.GetPrompt()
|
||||
func NewPullPal(ctx context.Context, log *zap.Logger, self vc.Author, repo vc.Repository) (*PullPal, error) {
|
||||
ghClient, err := vc.NewGithubClient(ctx, log, self, repo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
// GetPrompt converts the information in the request to a prompt for an LLM.
|
||||
func (req CodeChangeRequest) GetPrompt() (string, error) {
|
||||
tmpl, err := template.ParseFiles("../template/code-change-request.tmpl")
|
||||
if err != nil {
|
||||
return "", err
|
||||
log.Error("Failed to setup Github client.", zap.Error(err))
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
err = tmpl.Execute(&result, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return &PullPal{
|
||||
ctx: ctx,
|
||||
log: log,
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// CodeChangeResponse contains data derived from an LLM response to a prompt generated via a CodeChangeRequest.
|
||||
type CodeChangeResponse struct {
|
||||
Files []File
|
||||
Notes string
|
||||
}
|
||||
|
||||
// String is a string representation of CodeChangeResponse.
|
||||
func (res CodeChangeResponse) String() string {
|
||||
out := "Notes:\n"
|
||||
out += res.Notes + "\n\n"
|
||||
out += "Files:\n"
|
||||
for _, f := range res.Files {
|
||||
out += f.Path + ":\n```\n"
|
||||
out += f.Contents + "\n```\n"
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ParseCodeChangeResponse parses the LLM's response to CodeChangeRequest (string) into a CodeChangeResponse.
|
||||
func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse {
|
||||
sections := strings.Split(llmResponse, "Notes:")
|
||||
|
||||
filesSection := sections[0]
|
||||
notes := strings.TrimSpace(sections[1])
|
||||
|
||||
files := parseFiles(filesSection)
|
||||
|
||||
return CodeChangeResponse{
|
||||
Files: files,
|
||||
Notes: notes,
|
||||
}
|
||||
}
|
||||
|
||||
// parseFiles process the "files" subsection of the LLM's response. It is a helper for GetCodeChangeResponse.
|
||||
func parseFiles(filesSection string) []File {
|
||||
fileStringList := strings.Split(filesSection, "name:")
|
||||
// first item in the list is just gonna be "Files:"
|
||||
fileStringList = fileStringList[1:]
|
||||
|
||||
fileList := make([]File, len(fileStringList))
|
||||
for i, f := range fileStringList {
|
||||
fileParts := strings.Split(f, "contents:")
|
||||
path := strings.TrimSpace(fileParts[0])
|
||||
contents := strings.TrimSpace(fileParts[1])
|
||||
contents = strings.Trim(contents, "```")
|
||||
|
||||
fileList[i] = File{
|
||||
Path: path,
|
||||
Contents: contents,
|
||||
}
|
||||
}
|
||||
|
||||
return fileList
|
||||
vcClient: ghClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user