forked from vihangd/emacs-copilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemacs-copilot.el
More file actions
195 lines (170 loc) · 8.88 KB
/
emacs-copilot.el
File metadata and controls
195 lines (170 loc) · 8.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
;;; emacs-copilot.el --- Emacs Copilot: AI-driven code generation and editing -*- lexical-binding: t -*-
;; Author: Vihang D
;; URL: https://github.com/vihangd/emacs-copilot
;; Version: 0.2.0
;; Package-Requires: ((emacs "25.1"))
;;; Commentary:
;; Emacs Copilot is a package that provides AI-driven code generation
;; and editing capabilities using OpenAI's API. It includes
;; functions to generate Emacs Lisp code, rewrite the content of a buffer
;; or region, and insert code based on a given instruction.
;;; Code:
(require 'cl-lib)
(require 'request)
(require 'request-deferred)
(require 'magit)
(defcustom emacs-copilot-openapi-url "https://api.openai.com"
"Your OpenAI API key."
:type 'string
:group 'emacs-copilot)
(defcustom emacs-copilot-api-key ""
"Your OpenAI API key."
:type 'string
:group 'emacs-copilot)
(defcustom emacs-copilot-api-provider 'openai
"API provider to use for generating content. Either 'openai' or 'google-gemini'."
:type '(choice (const :tag "OpenAI" openai)
(const :tag "Google Gemini" google-gemini))
:group 'emacs-copilot)
(defcustom emacs-copilot-openai-api-key ""
"Your OpenAI API key."
:type 'string
:group 'emacs-copilot)
(defcustom emacs-copilot-google-gemini-api-key ""
"Your Google Gemini API key."
:type 'string
:group 'emacs-copilot)
(defun emacs-copilot-make-api-request (provider instruction callback)
"Make an API request to the selected PROVIDER with the given INSTRUCTION and CALLBACK."
(cl-case provider
(openai
(let ((headers `(("Content-Type" . "application/json")
("Authorization" . ,(concat "Bearer " emacs-copilot-openai-api-key))))
(data (json-encode `(("model" . "gpt-4-turbo")
("prompt" . ,instruction)
("max_tokens" . 2000)
("n" . 1)
("temperature" . 0.3)))))
(request-deferred (contact emacs-copilot-openapi-url "/v1/completions")
:type "POST"
:headers headers
:data data
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(let* ((choices (cdr (assoc 'choices data)))
(choice (elt choices 0))
(text (cdr (assoc 'text choice))))
(funcall callback text))))
:error (cl-function
(lambda (&key error-thrown &allow-other-keys)
(message "Error: %S" error-thrown))))))
(google-gemini
(let ((headers `(("Content-Type" . "application/json")
))
(data (json-encode `(("contents" . [((("parts" . [((("text" . ,instruction)))])))])))))
;;
(request-deferred (concat "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" emacs-copilot-google-gemini-api-key)
:type "POST"
:headers headers
:data data
:parser 'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
(let* ((candidates (cdr (assoc 'candidates data)))
(candidate (elt candidates 0))
(content (cdr (assoc 'content candidate)))
(parts (cdr (assoc 'parts content)))
(part (elt parts 0))
(text (cdr (assoc 'text part))))
(funcall callback text))))
:error (cl-function
(lambda (&key error-thrown &allow-other-keys)
(message "Error: %S" error-thrown))))))))
(defun emacs-copilot-api-request (instruction callback)
"A wrapper function to make an API request with the given INSTRUCTION and CALLBACK."
(emacs-copilot-make-api-request emacs-copilot-api-provider instruction callback))
(defun emacs-copilot-process (instruction &optional no-input callback)
"Process the entire buffer or the selected region based on the INSTRUCTION provided.
If NO-INPUT is non-nil, do not use the buffer or selection content as input.
When CALLBACK is provided, call it with the resulting text."
(interactive "sEnter an instruction (or leave empty for default): ")
(let* ((input (if no-input "" (if (use-region-p) (buffer-substring (region-beginning) (region-end)) (buffer-string))))
(default-instruction "Fix the grammar and rewrite the following text:")
(final-instruction (if (string-empty-p instruction) (concat default-instruction "\n" input) (concat instruction "\n" input))))
(emacs-copilot-api-request final-instruction
(lambda (text)
(if callback
(funcall callback text)
(with-current-buffer (get-buffer-create "*GPT-Rewritten*")
(erase-buffer)
(insert text)
(pop-to-buffer (current-buffer))))))))
(defun emacs-copilot-confirm-and-eval (code)
"Display the generated CODE and ask for confirmation before evaluating it."
(with-current-buffer (get-buffer-create "*Emacs Copilot Code*")
(erase-buffer)
(insert code)
(pop-to-buffer (current-buffer))
(when (y-or-n-p "Evaluate the generated code? ")
(eval-buffer))
(kill-buffer)))
(defun emacs-copilot-generate-code (task)
"Generate Emacs Lisp code using OpenAI API with the given TASK."
(let ((prompt (concat "Generate emacslisp code which can be evaluated as it is for the following task (Note: do not add any quotes): " task)))
(emacs-copilot-api-request
prompt
(lambda (data)
(emacs-copilot-confirm-and-eval data)))))
(defun emacs-copilot ()
"Ask the user for a task and generate Emacs Lisp code using OpenAI API."
(interactive)
(let ((task (read-string "Enter the task you want to generate code for: ")))
(emacs-copilot-generate-code task)))
(defun emacs-copilot-generate (instruction)
"Generate content based on given instruction"
(interactive "sEnter an instruction (or leave empty for default): ")
(emacs-copilot-process instruction t))
(defun emacs-copilot-commit ()
"Generate a commit message for the staged changes in the current project using emacs-copilot-process."
(interactive)
(let* (
(staged-diff (shell-command-to-string "git --no-pager diff --staged"))
(prompt (concat "Generate a git commit message with first line being a succinct summary of all the changes with no more than 50 characters then separated by 2 new lines give the summary of all the changes in lines where each line is no longer than 72 characters in the following commit :\n\n" staged-diff)))
(emacs-copilot-process
prompt
nil
(lambda (commit-message)
(if (stringp commit-message)
(progn
(magit-commit-create)
(let ((counter 0))
(while (and (not (get-buffer "COMMIT_EDITMSG")) (< counter 100))
(sleep-for 0.1)
(setq counter (1+ counter))))
(if (get-buffer "COMMIT_EDITMSG")
(with-current-buffer (get-buffer "COMMIT_EDITMSG")
(goto-char (point-min))
(insert commit-message))
(error "Error: COMMIT_EDITMSG buffer not available after 10 seconds.")))
(message "Error: Invalid commit message received from API."))))))
(defun emacs-copilot-inline-assist (instruction)
"Replace the selected region in the current buffer with processed content.
If no region is selected, append the processed content to the current line.
The content is processed based on the given INSTRUCTION."
(interactive "sEnter an instruction (or leave empty for default): ")
(let* ((start (if (use-region-p) (region-beginning) (line-end-position)))
(end (if (use-region-p) (region-end) (line-end-position)))
(region-content (buffer-substring start end)))
(emacs-copilot-process
(concat "Generate code following instruction, ensure that the output only has code and comments.\n" instruction "\n" region-content)
nil
(lambda (processed-content)
(if (stringp processed-content)
(progn
(delete-region start end)
(goto-char start)
(insert processed-content)
(insert "\n"))
(message "Error: Invalid processed content received from API."))))))
(provide 'emacs-copilot)