Skip to content

Commit 1bb43d1

Browse files
committed
Refactor AskQuery function to accept image bytes; implement command history storage in SQLite; add Co Pilot feature for enhanced user assistance
1 parent eff4c37 commit 1bb43d1

File tree

12 files changed

+527
-26
lines changed

12 files changed

+527
-26
lines changed

Readme.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ Or customize the prompt:
8787
gema commit --prompt "Write a detailed commit message explaining the following changes:"
8888
```
8989

90+
### Co Pilot
91+
92+
Get assistance with anything on your screen:
93+
94+
```bash
95+
gema assist "explain this error message"
96+
gema a "explain this error message" # alias
97+
gema copilot "suggest improvements for this code" # alias
98+
gema assistant "what does this output mean?" # alias
99+
```
100+
101+
The Co Pilot feature analyzes text from your terminal and provides helpful explanations, suggestions, or guidance based on the content.
102+
103+
Example:
104+
```bash
105+
gema assist "What's wrong with this command: grep -l 'function' | xargs sed 's/old/new/g'"
106+
```
107+
90108
## Contributing
91109

92110
Contributions are welcome! Please feel free to submit a Pull Request.

cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func waitForResponse(m model) model {
5757
}()
5858

5959
time.Sleep(3 * time.Second)
60-
genaiResponse := AskQuery(m.query)
60+
genaiResponse := AskQuery(m.query, nil)
6161
done <- true
6262
fmt.Print("\r") // Clear the loading line
6363

copilot.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/fatih/color"
8+
"github.com/getlantern/systray"
9+
"github.com/getlantern/systray/example/icon"
10+
"github.com/ncruces/zenity"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// WriterCmd represents the writer command
15+
var CoPilotCmd = &cobra.Command{
16+
Use: "assist [text]",
17+
Aliases: []string{"a", "copilot", "assistant"},
18+
Short: "Help user with anything on his/her screen.",
19+
Long: `Help user with anything on his/her screen.`,
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
22+
// Indicate processing
23+
processingMsg := color.New(color.FgYellow).PrintFunc()
24+
processingMsg("Starting Gema tray application...\n")
25+
26+
systray.Run(onReady, onExit)
27+
28+
return nil
29+
},
30+
}
31+
32+
func onReady() {
33+
fmt.Println("Initializing system tray...")
34+
systray.SetIcon(icon.Data)
35+
systray.SetTitle("Gema")
36+
mHelp := systray.AddMenuItem("Help", "Take Screensot and and audio.")
37+
fmt.Println("Gema is now running in your system tray")
38+
39+
// Listen for clicks on the help button
40+
go func() {
41+
for {
42+
<-mHelp.ClickedCh
43+
fmt.Println("Help button clicked, launching Copilot...")
44+
RunCopilot()
45+
}
46+
}()
47+
}
48+
49+
func onExit() {
50+
fmt.Println("Exiting Gema...")
51+
// clean up here
52+
}
53+
54+
func RunCopilot() {
55+
fmt.Println("--------------------------------------------------")
56+
fmt.Printf("[%s] Starting Gema Assistant\n", time.Now().Format("15:04:05"))
57+
58+
fmt.Println("[INFO] Displaying input dialog...")
59+
// Ask the user for their question with a centered dialog
60+
userQuery, err := zenity.Entry(
61+
"What would you like help with?",
62+
zenity.Title("Gema Assistant"),
63+
zenity.Width(400),
64+
)
65+
if err != nil {
66+
// User canceled or there was an error
67+
fmt.Printf("[ERROR] Dialog error: %v\n", err)
68+
return
69+
}
70+
71+
// If the user didn't enter anything, return early
72+
if userQuery == "" {
73+
fmt.Println("[INFO] No query provided, canceling request")
74+
return
75+
}
76+
77+
fmt.Println("[INFO] Taking screenshot...")
78+
imgBytes, err := Screenshot()
79+
if err != nil {
80+
fmt.Printf("[ERROR] Failed to capture screenshot: %v\n", err)
81+
} else {
82+
fmt.Printf("[INFO] Screenshot captured successfully (%d bytes)\n", len(imgBytes))
83+
}
84+
85+
fmt.Println("[INFO] Sending query to AI service...")
86+
aiResp := AskQuery(userQuery, [][]byte{imgBytes})
87+
88+
fmt.Println("====================================")
89+
fmt.Printf("[%s] QUERY: %s\n", time.Now().Format("15:04:05"), userQuery)
90+
fmt.Println("====================================")
91+
fmt.Println("[RESPONSE]")
92+
fmt.Println(aiResp.Response)
93+
fmt.Println("====================================")
94+
95+
fmt.Println("[INFO] Copying response to clipboard...")
96+
err = PutTextOnClipboard(aiResp.Response)
97+
if err != nil {
98+
fmt.Printf("[ERROR] Failed to copy to clipboard: %v\n", err)
99+
} else {
100+
fmt.Println("[INFO] Response copied to clipboard")
101+
}
102+
103+
fmt.Println("[INFO] Converting response to speech...")
104+
// Create a channel to signal speech to stop
105+
stopSpeech := make(chan struct{})
106+
107+
// Start speech in a goroutine
108+
go func() {
109+
SpeakMessage(aiResp.Response, stopSpeech)
110+
}()
111+
112+
// Monitor CLI input in parallel
113+
go func() {
114+
fmt.Println("[INFO] Press any key to stop speech...")
115+
var input string
116+
fmt.Scanln(&input) // Wait for Enter, but any input will work
117+
close(stopSpeech) // Signal to stop the speech
118+
fmt.Println("[INFO] Speech stopped by user input")
119+
}()
120+
121+
// Wait for speech to complete or be stopped
122+
<-stopSpeech
123+
124+
fmt.Printf("[%s] Gema Assistant completed\n", time.Now().Format("15:04:05"))
125+
fmt.Println("--------------------------------------------------")
126+
}

gemini.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type AiResponse struct {
1717
Command string `json:"command"`
1818
}
1919

20-
func AskQuery(query string) AiResponse {
20+
func AskQuery(query string, imageBytes [][]byte) AiResponse {
2121

2222
apiKey := os.Getenv("GENAI_API_KEY")
2323
if apiKey == "" {
@@ -47,7 +47,23 @@ func AskQuery(query string) AiResponse {
4747

4848
finalPrompt := sysInfo + SystemInstruction
4949

50-
model.SystemInstruction = &genai.Content{Parts: []genai.Part{genai.Text(finalPrompt)}}
50+
SystemInstruction := []genai.Part{
51+
genai.Text(finalPrompt),
52+
}
53+
54+
attachMents := []genai.Part{}
55+
56+
attachMents = append(attachMents, genai.Text(query))
57+
58+
for _, imgBytes := range imageBytes {
59+
if imageBytes != nil {
60+
61+
attachMents = append(attachMents, genai.ImageData("image/jpeg", imgBytes))
62+
}
63+
}
64+
65+
model.SystemInstruction = &genai.Content{Parts: SystemInstruction}
66+
5167
model.ResponseMIMEType = "application/json"
5268
model.ResponseSchema = &genai.Schema{
5369
Type: genai.TypeObject,
@@ -62,7 +78,7 @@ func AskQuery(query string) AiResponse {
6278
Required: []string{"response"},
6379
}
6480

65-
resp, err := model.GenerateContent(ctx, genai.Text(query))
81+
resp, err := model.GenerateContent(ctx, attachMents...)
6682
if err != nil {
6783
log.Fatal(err)
6884
}
@@ -82,6 +98,8 @@ func AskQuery(query string) AiResponse {
8298
log.Fatal("Response field is missing or not a string")
8399
}
84100

101+
StoreCommandHistory(query, result.Response)
102+
85103
return result
86104
}
87105

git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func GenerateCommitMessage(path, systemPrompt string) (string, []string) {
128128
limitDiffSize(string(diffOutput), 4000)) // Limit diff size to avoid token limits
129129

130130
// Use AskQuery from gemini.go
131-
result := AskQuery(query)
131+
result := AskQuery(query, nil)
132132
return result.Response, changedFiles
133133
}
134134

go.mod

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ go 1.23.4
55
require (
66
github.com/elastic/go-sysinfo v1.15.0
77
github.com/fatih/color v1.18.0
8+
github.com/getlantern/systray v1.2.2
89
github.com/google/generative-ai-go v0.19.0
10+
github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c
11+
github.com/mattn/go-sqlite3 v1.14.24
12+
github.com/ncruces/zenity v0.10.14
913
github.com/spf13/cobra v1.8.1
1014
google.golang.org/api v0.186.0
1115
)
@@ -17,34 +21,54 @@ require (
1721
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
1822
cloud.google.com/go/compute/metadata v0.3.0 // indirect
1923
cloud.google.com/go/longrunning v0.5.7 // indirect
24+
github.com/akavel/rsrc v0.10.2 // indirect
25+
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect
2026
github.com/elastic/go-windows v1.0.0 // indirect
2127
github.com/felixge/httpsnoop v1.0.4 // indirect
22-
github.com/go-logr/logr v1.4.1 // indirect
28+
github.com/gen2brain/shm v0.1.0 // indirect
29+
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
30+
github.com/getlantern/errors v1.0.4 // indirect
31+
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
32+
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
33+
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
34+
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 // indirect
35+
github.com/go-logr/logr v1.4.2 // indirect
2336
github.com/go-logr/stdr v1.2.2 // indirect
37+
github.com/go-stack/stack v1.8.1 // indirect
38+
github.com/godbus/dbus/v5 v5.1.0 // indirect
2439
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
2540
github.com/golang/protobuf v1.5.4 // indirect
2641
github.com/google/s2a-go v0.1.7 // indirect
2742
github.com/google/uuid v1.6.0 // indirect
2843
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
2944
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
3045
github.com/inconshreveable/mousetrap v1.1.0 // indirect
46+
github.com/jezek/xgb v1.1.1 // indirect
47+
github.com/josephspurrier/goversioninfo v1.4.1 // indirect
48+
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
3149
github.com/mattn/go-colorable v0.1.13 // indirect
3250
github.com/mattn/go-isatty v0.0.20 // indirect
51+
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
3352
github.com/pkg/errors v0.9.1 // indirect
3453
github.com/prometheus/procfs v0.15.1 // indirect
54+
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect
3555
github.com/spf13/pflag v1.0.5 // indirect
3656
go.opencensus.io v0.24.0 // indirect
57+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
3758
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
3859
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
39-
go.opentelemetry.io/otel v1.26.0 // indirect
40-
go.opentelemetry.io/otel/metric v1.26.0 // indirect
41-
go.opentelemetry.io/otel/trace v1.26.0 // indirect
60+
go.opentelemetry.io/otel v1.34.0 // indirect
61+
go.opentelemetry.io/otel/metric v1.34.0 // indirect
62+
go.opentelemetry.io/otel/trace v1.34.0 // indirect
63+
go.uber.org/multierr v1.11.0 // indirect
64+
go.uber.org/zap v1.27.0 // indirect
4265
golang.org/x/crypto v0.24.0 // indirect
66+
golang.org/x/image v0.20.0 // indirect
4367
golang.org/x/net v0.26.0 // indirect
4468
golang.org/x/oauth2 v0.21.0 // indirect
4569
golang.org/x/sync v0.9.0 // indirect
46-
golang.org/x/sys v0.27.0 // indirect
47-
golang.org/x/text v0.16.0 // indirect
70+
golang.org/x/sys v0.30.0 // indirect
71+
golang.org/x/text v0.18.0 // indirect
4872
golang.org/x/time v0.5.0 // indirect
4973
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
5074
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect

0 commit comments

Comments
 (0)