225 lines
8.9 KiB
EmacsLisp
225 lines
8.9 KiB
EmacsLisp
(require 'forge)
|
|
(require 'llm)
|
|
|
|
;;;###autoload
|
|
(defun forge-llm-hello ()
|
|
"Display pull request branch information and show git diff in a Forge PR buffer."
|
|
(interactive)
|
|
(if (not (derived-mode-p 'forge-post-mode))
|
|
(message "Not in a Forge pull request buffer")
|
|
(let ((head (and (boundp 'forge--buffer-head-branch) forge--buffer-head-branch))
|
|
(base (and (boundp 'forge--buffer-base-branch) forge--buffer-base-branch)))
|
|
(if (and head base)
|
|
(let* ((default-directory (file-name-directory
|
|
(directory-file-name
|
|
(file-name-directory
|
|
(or buffer-file-name default-directory)))))
|
|
(pr-desc (format "Pull Request: %s → %s" head base))
|
|
(repo-root (locate-dominating-file default-directory ".git")))
|
|
;; First show the PR branches in the message area
|
|
(message "%s" pr-desc)
|
|
|
|
;; Now create a buffer with the git diff
|
|
(when repo-root
|
|
(let ((diff-command (format "git diff %s..%s" base head))
|
|
(buffer (get-buffer-create "*forge-llm-diff*")))
|
|
(with-current-buffer buffer
|
|
(setq buffer-read-only nil)
|
|
(erase-buffer)
|
|
(insert (format "Diff for %s\n\n" pr-desc))
|
|
(let ((default-directory repo-root))
|
|
(call-process-shell-command diff-command nil buffer))
|
|
(diff-mode)
|
|
(setq buffer-read-only t)
|
|
(goto-char (point-min)))
|
|
(display-buffer buffer))))
|
|
(message "Branch information not available")))))
|
|
|
|
;;;###autoload
|
|
(defun forge-llm-debug ()
|
|
"Debug function to show all relevant forge and magit variables."
|
|
(interactive)
|
|
(with-output-to-temp-buffer "*forge-llm-debug*"
|
|
(let ((vars '()))
|
|
;; Collect global variables with forge or magit in name
|
|
(mapatoms
|
|
(lambda (sym)
|
|
(when (and (boundp sym)
|
|
(not (keywordp sym))
|
|
(symbolp sym)
|
|
(or (string-match-p "forge" (symbol-name sym))
|
|
(string-match-p "magit" (symbol-name sym))))
|
|
(push sym vars))))
|
|
|
|
;; Sort and print global variables
|
|
(setq vars (sort vars (lambda (a b) (string< (symbol-name a) (symbol-name b)))))
|
|
(princ "=== Global Variables ===\n\n")
|
|
(dolist (var vars)
|
|
(princ (format "%s: %S\n\n" var (symbol-value var))))
|
|
|
|
;; Print local variables
|
|
(princ "\n\n=== Buffer-Local Variables ===\n\n")
|
|
(dolist (var (buffer-local-variables))
|
|
(when (and (symbolp (car var))
|
|
(or (string-match-p "forge" (symbol-name (car var)))
|
|
(string-match-p "magit" (symbol-name (car var)))))
|
|
(princ (format "%s: %S\n\n" (car var) (cdr var))))))))
|
|
|
|
;;;###autoload
|
|
(defun forge-llm-setup ()
|
|
"Set up forge-llm integration with Forge's new-pullreq buffer."
|
|
(interactive)
|
|
(add-hook 'forge-post-mode-hook #'forge-llm-setup-pullreq-hook)
|
|
(message "forge-llm has been set up successfully"))
|
|
|
|
(defun forge-llm-setup-pullreq-hook ()
|
|
"Hook function to set up forge-llm in a new-pullreq buffer."
|
|
;; Only add our keybinding if this is a pull request post
|
|
(when (and buffer-file-name
|
|
(string-match-p "new-pullreq" buffer-file-name))
|
|
(local-set-key (kbd "C-c C-l") #'forge-llm-hello)
|
|
(local-set-key (kbd "C-c C-d") #'forge-llm-debug)))
|
|
|
|
;;; LLM Integration
|
|
|
|
(defcustom forge-llm-llm-provider nil
|
|
"LLM provider to use.
|
|
Can be a provider object or a function that returns a provider object."
|
|
:type '(choice
|
|
(sexp :tag "LLM provider")
|
|
(function :tag "Function that returns an LLM provider"))
|
|
:group 'forge-llm)
|
|
|
|
(defcustom forge-llm-temperature nil
|
|
"Temperature for LLM responses.
|
|
If nil, the default temperature of the LLM provider will be used."
|
|
:type '(choice (const :tag "Use provider default" nil)
|
|
(float :tag "Custom temperature"))
|
|
:group 'forge-llm)
|
|
|
|
(defcustom forge-llm-max-tokens nil
|
|
"Maximum number of tokens for LLM responses.
|
|
If nil, the default max tokens of the LLM provider will be used."
|
|
:type '(choice (const :tag "Use provider default" nil)
|
|
(integer :tag "Custom max tokens"))
|
|
:group 'forge-llm)
|
|
|
|
(defcustom forge-llm-short-story-prompt
|
|
"Write a short story (250-300 words) about an open source developer who discovers something unexpected while working on a pull request.
|
|
|
|
The story should be professional, concise, and have a clear beginning, middle, and conclusion."
|
|
"Prompt used to generate a short story with the LLM."
|
|
:type 'string
|
|
:group 'forge-llm)
|
|
|
|
(defun forge-llm--get-provider ()
|
|
"Return the LLM provider to use.
|
|
If `forge-llm-llm-provider' is a function, call it to get the provider.
|
|
Otherwise, return the value directly."
|
|
(if (functionp forge-llm-llm-provider)
|
|
(funcall forge-llm-llm-provider)
|
|
forge-llm-llm-provider))
|
|
|
|
;;;###autoload
|
|
;;; Stream handling
|
|
|
|
(defvar forge-llm--active-request nil
|
|
"The active LLM request, if any.")
|
|
|
|
(defun forge-llm--stream-insert-response (msg buffer)
|
|
"Insert streaming LLM response.
|
|
MSG is the response text.
|
|
BUFFER is the target buffer."
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(let ((inhibit-read-only t))
|
|
(erase-buffer)
|
|
(insert "# Short Story Generated by LLM\n\n")
|
|
(insert msg)))))
|
|
|
|
(defun forge-llm--stream-update-status (status buffer &optional error-msg)
|
|
"Update status of the streaming response.
|
|
STATUS is one of 'success', 'error'.
|
|
BUFFER is the target buffer.
|
|
ERROR-MSG is the error message, if any."
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(let ((inhibit-read-only t))
|
|
(goto-char (point-max))
|
|
(insert "\n\n")
|
|
(pcase status
|
|
('success (insert "--- Generation complete ---"))
|
|
('error (insert (format "Error: %s" error-msg))))
|
|
(text-mode)))))
|
|
|
|
(defun forge-llm-cancel-request ()
|
|
"Cancel the active LLM request, if any."
|
|
(interactive)
|
|
(when forge-llm--active-request
|
|
(llm-cancel-request forge-llm--active-request)
|
|
(setq forge-llm--active-request nil)
|
|
(message "LLM request canceled")))
|
|
|
|
(defun forge-llm-generate-story ()
|
|
"Generate a short story using LLM and display it in a buffer.
|
|
Only works in Forge pull request buffers."
|
|
(interactive)
|
|
(if (not (derived-mode-p 'forge-post-mode))
|
|
(message "Not in a Forge pull request buffer")
|
|
(if-let ((provider (forge-llm--get-provider)))
|
|
(progn
|
|
(message "Generating story with LLM...")
|
|
(let ((buffer (get-buffer-create "*forge-llm-output*")))
|
|
;; Initialize buffer
|
|
(with-current-buffer buffer
|
|
(let ((inhibit-read-only t))
|
|
(erase-buffer)
|
|
(insert "# Short Story Generated by LLM\n\n")
|
|
(insert "Generating...")
|
|
(display-buffer buffer)))
|
|
|
|
;; Create a proper chat prompt
|
|
(let ((prompt (llm-make-simple-chat-prompt forge-llm-short-story-prompt)))
|
|
;; Set temperature and max tokens if supported
|
|
(when forge-llm-temperature
|
|
(setf (llm-chat-prompt-temperature prompt) forge-llm-temperature))
|
|
(when forge-llm-max-tokens
|
|
(setf (llm-chat-prompt-max-tokens prompt) forge-llm-max-tokens))
|
|
|
|
;; Cancel any existing request
|
|
(when forge-llm--active-request
|
|
(llm-cancel-request forge-llm--active-request)
|
|
(setq forge-llm--active-request nil))
|
|
|
|
;; Start new streaming request
|
|
(setq forge-llm--active-request
|
|
(llm-chat-streaming
|
|
provider prompt
|
|
;; Partial callback - called for each chunk
|
|
(lambda (partial-response)
|
|
(forge-llm--stream-insert-response partial-response buffer))
|
|
;; Complete callback - called when done
|
|
(lambda (_full-response)
|
|
(forge-llm--stream-update-status 'success buffer)
|
|
(setq forge-llm--active-request nil))
|
|
;; Error callback
|
|
(lambda (err-msg)
|
|
(forge-llm--stream-update-status 'error buffer err-msg)
|
|
(setq forge-llm--active-request nil)))))))
|
|
(user-error "No LLM provider configured. Set `forge-llm-llm-provider' first"))))
|
|
|
|
;; Add a key binding to the forge-llm keybinding
|
|
(defun forge-llm-setup-story-key ()
|
|
"Add key binding for generating stories with LLM."
|
|
(interactive)
|
|
(define-key forge-post-mode-map (kbd "C-c C-g") 'forge-llm-generate-story))
|
|
|
|
;;;###autoload
|
|
(defun forge-llm-setup-all ()
|
|
"Set up all forge-llm integrations."
|
|
(interactive)
|
|
(forge-llm-setup) ; Set up the basic PR branch info
|
|
(forge-llm-setup-story-key)) ; Set up the story generation key
|
|
|
|
(provide 'forge-llm)
|