Skip to content

Commit fbd3d1a

Browse files
committed
Add ask, add proper logger
1 parent a85b89b commit fbd3d1a

File tree

16 files changed

+422
-43
lines changed

16 files changed

+422
-43
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
ottodocs
22
bin/**
33
dist/**
4+
*.bleve

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ build:
88
go build -v -o bin/otto
99

1010
clean:
11-
rm -f otto
11+
rm -rf bin dist
1212

1313
run *commands:
1414
go run main.go {{commands}}

document/markdown.go renamed to ai/markdown.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package document
1+
package ai
22

33
import (
44
"errors"

ai/question.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ai
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
gopenai "github.com/CasualCodersProjects/gopenai"
8+
ai_types "github.com/CasualCodersProjects/gopenai/types"
9+
"github.com/chand1012/ottodocs/constants"
10+
"github.com/pandodao/tokenizer-go"
11+
)
12+
13+
func Question(filePath, fileContent, chatPrompt, APIKey string) (string, error) {
14+
openai := gopenai.NewOpenAI(&gopenai.OpenAIOpts{
15+
APIKey: APIKey,
16+
})
17+
18+
question := "File Name: " + filePath + "\nQuestion: " + chatPrompt + "\n\n" + strings.TrimRight(string(fileContent), " \n") + "\nAnswer:"
19+
20+
messages := []ai_types.ChatMessage{
21+
{
22+
Content: constants.QUESTION_PROMPT,
23+
Role: "system",
24+
},
25+
{
26+
Content: question,
27+
Role: "user",
28+
},
29+
}
30+
31+
tokens := tokenizer.MustCalToken(messages[0].Content) + tokenizer.MustCalToken(messages[1].Content)
32+
33+
maxTokens := constants.OPENAI_MAX_TOKENS - tokens
34+
35+
if maxTokens < 0 {
36+
return "", fmt.Errorf("the prompt is too long. max length is %d. Got %d", constants.OPENAI_MAX_TOKENS, tokens)
37+
}
38+
39+
req := ai_types.NewDefaultChatRequest("")
40+
req.Messages = messages
41+
req.MaxTokens = maxTokens
42+
// lower the temperature to make the model more deterministic
43+
// req.Temperature = 0.3
44+
45+
resp, err := openai.CreateChat(req)
46+
if err != nil {
47+
fmt.Printf("Error: %s", err)
48+
return "", err
49+
}
50+
51+
message := resp.Choices[0].Message.Content
52+
53+
return message, nil
54+
}

document/single_file.go renamed to ai/single_file.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package document
1+
package ai
22

33
import (
44
"errors"
@@ -92,7 +92,7 @@ func SingleFile(filePath, chatPrompt, APIKey string) (string, error) {
9292
resp, err := openai.CreateChat(req)
9393
if err != nil {
9494
fmt.Printf("Error: %s", err)
95-
os.Exit(1)
95+
return "", err
9696
}
9797

9898
message := resp.Choices[0].Message.Content

cmd/ask.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/blevesearch/bleve/v2"
13+
"github.com/chand1012/git2gpt/prompt"
14+
"github.com/spf13/cobra"
15+
16+
"github.com/chand1012/ottodocs/ai"
17+
"github.com/chand1012/ottodocs/config"
18+
"github.com/chand1012/ottodocs/utils"
19+
)
20+
21+
// askCmd represents the ask command
22+
var askCmd = &cobra.Command{
23+
Use: "ask",
24+
Short: "Ask a question about a file or repo",
25+
Long: `Uses a vector search to find the most similar questions and answers`,
26+
Args: cobra.PositionalArgs(func(cmd *cobra.Command, args []string) error {
27+
if len(args) < 1 {
28+
return fmt.Errorf("requires a path to a repository or file")
29+
}
30+
return nil
31+
}),
32+
Run: func(cmd *cobra.Command, args []string) {
33+
repoPath := args[0]
34+
var fileName string
35+
conf, err := config.Load()
36+
if err != nil || conf.APIKey == "" {
37+
// if the API key is not set, prompt the user to login
38+
log.Error("Please login first.")
39+
log.Error("Run `ottodocs login` to login.")
40+
os.Exit(1)
41+
}
42+
43+
if chatPrompt == "" {
44+
fmt.Println("Please enter a question: ")
45+
fmt.Scanln(&chatPrompt)
46+
// strip the newline character
47+
chatPrompt = strings.TrimRight(chatPrompt, " \n")
48+
}
49+
50+
// if .index.bleve exists, delete it
51+
if _, err := os.Stat(filepath.Join(args[0], ".index.bleve")); err == nil {
52+
err = os.RemoveAll(filepath.Join(args[0], ".index.bleve"))
53+
if err != nil {
54+
log.Errorf("Error deleting index: %s", err)
55+
os.Exit(1)
56+
}
57+
}
58+
59+
info, err := os.Stat(repoPath)
60+
if err != nil {
61+
log.Errorf("Error getting file info: %s", err)
62+
os.Exit(1)
63+
}
64+
// check if the first arg is a file or a directory
65+
// if it's a file, ask a question about that file directly
66+
if info.IsDir() {
67+
var index bleve.Index
68+
var err error
69+
mapping := bleve.NewIndexMapping()
70+
71+
// check if .index.bleve exists
72+
// if it does, load it
73+
// if it doesn't, create it
74+
// fmt.Println("Indexing repo...")
75+
index, err = bleve.New(filepath.Join(args[0], ".index.bleve"), mapping)
76+
if err != nil {
77+
log.Errorf("Error creating index: %s", err)
78+
os.Exit(1)
79+
}
80+
// index the repo
81+
ignoreList := prompt.GenerateIgnoreList(repoPath, ignoreFilePath, !ignoreGitignore)
82+
ignoreList = append(ignoreList, filepath.Join(repoPath, ".gptignore"))
83+
repo, err := prompt.ProcessGitRepo(repoPath, ignoreList)
84+
if err != nil {
85+
log.Errorf("Error processing repo: %s", err)
86+
os.Exit(1)
87+
}
88+
// index the files
89+
for _, file := range repo.Files {
90+
err = index.Index(file.Path, file)
91+
if err != nil {
92+
log.Errorf("Error indexing file: %s", err)
93+
os.Exit(1)
94+
}
95+
}
96+
97+
// ask a question about the repo
98+
query := bleve.NewQueryStringQuery(chatPrompt)
99+
search := bleve.NewSearchRequest(query)
100+
searchResults, err := index.Search(search)
101+
if err != nil {
102+
log.Errorf("Error searching index: %s", err)
103+
os.Exit(1)
104+
}
105+
106+
// tokenize the question
107+
tokens := utils.SimpleTokenize(chatPrompt)
108+
for _, token := range tokens {
109+
query := bleve.NewQueryStringQuery(token)
110+
search := bleve.NewSearchRequest(query)
111+
r, err := index.Search(search)
112+
if err != nil {
113+
log.Errorf("Error searching index: %s", err)
114+
os.Exit(1)
115+
}
116+
// combines the searches, but doesn't weight by ID
117+
searchResults.Merge(r)
118+
}
119+
hits := make(map[string]float64)
120+
121+
for _, results := range searchResults.Hits {
122+
currentScore := hits[results.ID]
123+
hits[results.ID] = currentScore + results.Score
124+
}
125+
126+
var bestHit string
127+
var bestScore float64
128+
for hit, score := range hits {
129+
if score > bestScore {
130+
bestScore = score
131+
bestHit = hit
132+
}
133+
}
134+
135+
fileName = bestHit
136+
} else {
137+
fileName = repoPath
138+
}
139+
140+
fmt.Println("Asking question about " + fileName + "...")
141+
142+
// load the file
143+
content, err := os.ReadFile(fileName)
144+
if err != nil {
145+
log.Errorf("Error reading file: %s", err)
146+
os.Exit(1)
147+
}
148+
149+
resp, err := ai.Question(fileName, string(content), chatPrompt, conf.APIKey)
150+
151+
if err != nil {
152+
log.Errorf("Error asking question: %s", err)
153+
os.Exit(1)
154+
}
155+
156+
fmt.Println(resp)
157+
},
158+
}
159+
160+
func init() {
161+
RootCmd.AddCommand(askCmd)
162+
askCmd.Flags().StringVarP(&chatPrompt, "question", "q", "", "The question to ask")
163+
askCmd.Flags().BoolVarP(&ignoreGitignore, "ignore-gitignore", "g", false, "ignore .gitignore file")
164+
askCmd.Flags().StringVarP(&ignoreFilePath, "ignore", "n", "", "path to .gptignore file")
165+
}

cmd/chat.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ If '-q' is not specified, the user will be prompted to enter a question.
2626
conf, err := config.Load()
2727
if err != nil || conf.APIKey == "" {
2828
// if the API key is not set, prompt the user to login
29-
fmt.Println("Please login first.")
30-
fmt.Println("Run `ottodocs login` to login.")
29+
log.Error("Please login first.")
30+
log.Error("Run `ottodocs login` to login.")
3131
os.Exit(1)
3232
}
3333

@@ -44,7 +44,7 @@ If '-q' is not specified, the user will be prompted to enter a question.
4444
// chat ChatGPT the question
4545
resp, err := openai.CreateChatSimple(question, 0) // 0 sets the max tokens to 1024
4646
if err != nil {
47-
fmt.Printf("Error: %s", err)
47+
log.Errorf("Error: %s", err)
4848
os.Exit(1)
4949
}
5050

cmd/doc.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99

1010
"github.com/spf13/cobra"
1111

12+
"github.com/chand1012/ottodocs/ai"
1213
"github.com/chand1012/ottodocs/config"
13-
"github.com/chand1012/ottodocs/document"
1414
)
1515

1616
// docCmd represents the doc command
@@ -20,8 +20,8 @@ var docCmd = &cobra.Command{
2020
Long: `Document a file using the OpenAI ChatGPT API.`,
2121
Run: func(cmd *cobra.Command, args []string) {
2222
if filePath == "" {
23-
fmt.Println("Please provide a path to a file to document.")
24-
fmt.Println("Run `ottodocs doc -h` for more information.")
23+
log.Error("Please provide a path to a file to document.")
24+
log.Error("Run `ottodocs doc -h` for more information.")
2525
os.Exit(1)
2626
}
2727

@@ -32,44 +32,44 @@ var docCmd = &cobra.Command{
3232
conf, err := config.Load()
3333
if err != nil || conf.APIKey == "" {
3434
// if the API key is not set, prompt the user to login
35-
fmt.Println("Please login first.")
36-
fmt.Println("Run `ottodocs login` to login.")
35+
log.Error("Please login first.")
36+
log.Error("Run `ottodocs login` to login.")
3737
os.Exit(1)
3838
}
3939

4040
var contents string
4141

4242
if inlineMode || !markdownMode {
43-
contents, err = document.SingleFile(filePath, chatPrompt, conf.APIKey)
43+
contents, err = ai.SingleFile(filePath, chatPrompt, conf.APIKey)
4444
} else {
45-
contents, err = document.Markdown(filePath, chatPrompt, conf.APIKey)
45+
contents, err = ai.Markdown(filePath, chatPrompt, conf.APIKey)
4646
}
4747

4848
if err != nil {
49-
fmt.Printf("Error: %s", err)
49+
log.Errorf("Error: %s", err)
5050
os.Exit(1)
5151
}
5252

5353
if outputFile != "" {
5454
// write the string to the output file
5555
err = os.WriteFile(outputFile, []byte(contents), 0644)
5656
if err != nil {
57-
fmt.Printf("Error: %s", err)
57+
log.Errorf("Error: %s", err)
5858
os.Exit(1)
5959
}
6060
} else if overwriteOriginal {
6161
// overwrite the original file
6262
// clear the contents of the file
6363
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
6464
if err != nil {
65-
fmt.Printf("Error: %s", err)
65+
log.Errorf("Error: %s", err)
6666
os.Exit(1)
6767
}
6868

6969
// write the new contents to the file
7070
_, err = file.WriteString(contents)
7171
if err != nil {
72-
fmt.Printf("Error: %s", err)
72+
log.Errorf("Error: %s", err)
7373
os.Exit(1)
7474
}
7575
} else {

0 commit comments

Comments
 (0)