-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathellama-community-prompts.el
200 lines (178 loc) · 8.02 KB
/
ellama-community-prompts.el
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
196
197
198
199
200
;;; ellama-community-prompts.el --- Community prompt collection -*- lexical-binding: t; package-lint-main-file: "ellama.el"; -*-
;; Copyright (C) 2023-2025 Free Software Foundation, Inc.
;; Author: Sergey Kostyaev <[email protected]>
;; SPDX-License-Identifier: GPL-3.0-or-later
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Ellama is a tool for interacting with large language models from Emacs.
;; It allows you to ask questions and receive responses from the
;; LLMs. Ellama can perform various tasks such as translation, code
;; review, summarization, enhancing grammar/spelling or wording and
;; more through the Emacs interface. Ellama natively supports streaming
;; output, making it effortless to use with your preferred text editor.
;;
;;; Code:
(require 'plz)
(require 'ellama)
(defcustom ellama-community-prompts-url "https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv"
"The URL of the community prompts collection."
:type 'string
:group 'ellama)
(defcustom ellama-community-prompts-file (expand-file-name
"community-prompts.csv"
(file-name-concat
user-emacs-directory
"ellama"))
"Path to the CSV file containing community prompts.
This file is expected to be located inside an `ellama' subdirectory
within your `user-emacs-directory'."
:type 'file
:group 'ellama)
(defun ellama-community-prompts-ensure-file ()
"Ensure that the community prompt collection file is downloaded.
Downloads the file from `ellama-community-prompts-url` if it does
not already exist."
(unless (file-exists-p ellama-community-prompts-file)
(let* ((directory (file-name-directory ellama-community-prompts-file))
(response (plz 'get ellama-community-prompts-url
:as 'file
:then (lambda (filename)
(rename-file filename ellama-community-prompts-file t))
:else (lambda (error)
(message "Failed to download community prompts: %s" error)))))
(when (and response (not (file-directory-p directory)))
(make-directory directory t))
(when response
(message "Community prompts file downloaded successfully.")))))
(defun ellama-community-prompts-parse-csv-line (line)
"Parse a single CSV LINE into a list of fields, handling quotes.
LINE is the string to be parsed."
(let ((i 0)
(len (length line)))
(cl-loop
with fields = '()
with current-field = ""
with inside-quotes = nil
while (< i len)
do (let ((char (aref line i)))
(cond
;; Opening quote (start of field)
((and (eq char ?\") (not inside-quotes))
(setq inside-quotes t)
(cl-incf i))
;; Closing quote (end of field or escaped quote)
((and (eq char ?\") inside-quotes)
(if (and (< (1+ i) len) (eq (aref line (1+ i)) ?\"))
(progn ; Escaped quote: add single quote, skip next character
(setq current-field (concat current-field "\""))
(cl-incf i 2))
(setq inside-quotes nil) ; End of quoted field
(cl-incf i)))
;; Comma separator (outside quotes)
((and (eq char ?,) (not inside-quotes))
(push current-field fields)
(setq current-field "")
(cl-incf i))
;; Regular character
(t
(setq current-field (concat current-field (string char)))
(cl-incf i))))
;; Add the last field after loop ends
finally return (nreverse (cons current-field fields)))))
(defun ellama-community-prompts-convert-to-plist (parsed-line)
"Convert PARSED-LINE to plist.
PARSED-LINE is expected to be a list with three elements: :act,
:prompt, and :for-devs."
(let ((act (cl-first parsed-line))
(prompt (cl-second parsed-line))
(for-devs (string= "TRUE" (cl-third parsed-line))))
`(:act ,act :prompt ,prompt :for-devs ,for-devs)))
(defvar ellama-community-prompts-collection nil
"Community prompts collection.")
(defun ellama-community-prompts-ensure ()
"Ensure that the community prompt collection are loaded and available.
This function ensures that the file specified by `ellama-community-prompts-file'
is read and parsed, and the resulting collection of prompts is stored in
`ellama-community-prompts-collection'. If the collection is already populated,
this function does nothing.
Returns the collection of community prompts."
(ellama-community-prompts-ensure-file)
(unless ellama-community-prompts-collection
(setq ellama-community-prompts-collection
(let ((buf (find-file-noselect ellama-community-prompts-file)))
(with-current-buffer buf
(mapcar (lambda (line)
(ellama-community-prompts-convert-to-plist
(ellama-community-prompts-parse-csv-line
line)))
(cdr (string-lines
(buffer-substring-no-properties
(point-min) (point-max)))))))))
ellama-community-prompts-collection)
(defvar ellama-community-prompts-blurpint-buffer " *ellama-community-prompts-blueprint-buffer*"
"Buffer for community prompt blueprint.")
;;;###autoload
(defun ellama-community-prompts-select-blueprint (&optional for-devs)
"Select a prompt from the community prompt collection.
The user is prompted to choose a role, and then a
corresponding prompt is inserted into a blueprint buffer.
Optional argument FOR-DEVS filters prompts for developers."
(interactive "P")
(let ((acts '())
selected-act selected-prompt)
;; Collect unique acts from the filtered collection
(dolist (prompt (ellama-community-prompts-ensure))
(when (or (not for-devs) (eq for-devs (plist-get prompt :for-devs)))
(cl-pushnew (plist-get prompt :act) acts)))
;; Prompt user to select an act
(setq selected-act (completing-read "Select Act: " acts))
;; Find the corresponding prompt
(catch 'found-prompt
(dolist (prompt ellama-community-prompts-collection)
(when (and (string= selected-act (plist-get prompt :act))
(or (not for-devs) (eq for-devs (plist-get prompt :for-devs))))
(setq selected-prompt (plist-get prompt :prompt))
(throw 'found-prompt nil))))
;; Create a new buffer and insert the selected prompt
(with-current-buffer (get-buffer-create ellama-community-prompts-blurpint-buffer)
(erase-buffer)
(let ((hard-newline t))
(insert selected-prompt)
(fill-region (point-min) (point-max))
(ellama-blueprint-mode))
(switch-to-buffer (current-buffer))
(ellama-community-prompts-update-variables))))
(defun ellama-community-prompts-get-variable-list ()
"Return a deduplicated list of variables found in the current buffer."
(save-excursion
(let ((vars '()))
(goto-char (point-min))
(while (re-search-forward "\{\\([^}]+\\)}" nil t)
(push (match-string 1) vars))
(seq-uniq vars))))
(defun ellama-community-prompts-set-variable (var value)
"Replace VAR with VALUE in blueprint buffer."
(save-excursion
(goto-char (point-min))
(while (search-forward (format "{%s}" var) nil t)
(replace-match value))))
;;;###autoload
(defun ellama-community-prompts-update-variables ()
"Prompt user for values of variables found in current buffer and update them."
(interactive)
(let ((vars (ellama-community-prompts-get-variable-list)))
(dolist (var vars)
(let ((value (read-string (format "Enter value for {%s}: " var))))
(ellama-community-prompts-set-variable var value)))))
(provide 'ellama-community-prompts)
;;; ellama-community-prompts.el ends here.