diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-23 20:12:58 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-23 20:12:58 -0400 |
| commit | 10fa6f4e2e7150ad99827721ada1ae4badcc5e90 (patch) | |
| tree | 960065c8e69f1e7a4150ecf522e2f813c239b5ee /archive/gptel/modules/ai-config.el | |
| parent | f4cc70c69e7707dd4a686637e14885f5443fcca6 (diff) | |
| download | dotemacs-10fa6f4e2e7150ad99827721ada1ae4badcc5e90.tar.gz dotemacs-10fa6f4e2e7150ad99827721ada1ae4badcc5e90.zip | |
chore(ai): archive gptel and remove it from the live config
I archived gptel to archive/gptel/ since I rarely use it. Moved there: the six gptel modules (ai-config, ai-conversations, ai-conversations-browser, ai-mcp, ai-quick-ask, ai-rewrite), the gptel-tools/ directory, custom/gptel-prompts.el, their test files and utilities, and the four gptel-only specs.
Scrubbed from the live config: the ai-config require in init.el, which also drops the whole C-; a keymap; the gptel-mode emojify hook in font-config.el; the gptel-tools entries in the Makefile clean target and the coverage runner; and the gptel feature notes in README. Cancelled the open gptel tasks in todo.org (the AI Open Work issues, the feature-extension brainstorm, the velox gptel-magit bug).
ai-term stays. It is the ghostel Claude launcher, independent of gptel.
Verified: every module loads, a batch init launch reaches completion clean, and the full test suite shows only pre-existing coverage failures unrelated to this change.
Diffstat (limited to 'archive/gptel/modules/ai-config.el')
| -rw-r--r-- | archive/gptel/modules/ai-config.el | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/archive/gptel/modules/ai-config.el b/archive/gptel/modules/ai-config.el new file mode 100644 index 00000000..97af1296 --- /dev/null +++ b/archive/gptel/modules/ai-config.el @@ -0,0 +1,585 @@ +;;; ai-config.el --- Configuration for AI Integrations -*- lexical-binding: t; coding: utf-8; -*- +;; author Craig Jennings <c@cjennings.net> +;; +;;; Commentary: +;; +;; Layer: 3 (Domain Workflow). +;; Category: D/P. +;; Load shape: eager. +;; Eager reason: registers the cj/ai-keymap (C-; a); GPTel itself should load on +;; command, a Phase 5 deferral candidate. +;; Top-level side effects: defines cj/ai-keymap, registers it under cj/custom-keymap. +;; Runtime requires: keybindings, system-lib. +;; Direct test load: yes (requires keybindings explicitly). +;; +;; Configuration for AI integrations in Emacs, focused on GPTel. +;; +;; Main Features: +;; - Quick toggle for AI assistant window (C-; a t) +;; - Custom keymap (C-; a prefix) for AI-related commands. +;; - Enhanced org-mode conversation formatting with timestamps +;; allows switching models and easily compare and track responses. +;; - Various specialized AI directives (coder, reviewer, etc.) +;; - Context management for adding files/buffers to conversations +;; - Conversation persistence with save/load functionality +;; - Integration with Magit for code review +;; +;; Basic Workflow +;; +;; Using a side-chat window: +;; - Launch GPTel via C-; a t, and chat in the AI-Assistant side window (C-<return> to send) +;; - Change system prompt (expertise, personalities) with C-; a p +;; - Add context from files (C-; a f) or current buffer (C-; a .) +;; - Save conversations with C-; a s, load previous ones with C-; a l +;; - Clear the conversation and start over with C-; a x +;; Or in any buffer: +;; - Add directive as above, and select a region to rewrite with C-; a r. +;; + +;;; Code: + +(require 'keybindings) ;; provides cj/custom-keymap +(require 'system-lib) ;; provides cj/auth-source-secret-value +(require 'cj-window-toggle-lib) ;; side-window size memory for the panel + +(autoload 'cj/gptel-save-conversation "ai-conversations" "Save the AI conversation to a file." t) +(autoload 'cj/gptel-load-conversation "ai-conversations" "Load a saved AI conversation." t) +(autoload 'cj/gptel-delete-conversation "ai-conversations" "Delete a saved AI conversation." t) +(autoload 'cj/gptel-autosave-toggle "ai-conversations" "Toggle autosave in the current GPTel buffer." t) +(autoload 'cj/gptel-quick-ask "ai-quick-ask" "One-shot quick-ask in a transient buffer." t) +(autoload 'cj/gptel-rewrite-with-directive "ai-rewrite" "Pick a directive and run gptel-rewrite on the region." t) +(autoload 'cj/gptel-rewrite-redo-with-different-directive "ai-rewrite" "Re-run the previous rewrite with a different directive." t) +(autoload 'cj/gptel-browse-conversations "ai-conversations-browser" "Browse saved GPTel conversations." t) + +;;; ------------------------- AI Config Helper Functions ------------------------ + +;; Define variables upfront +(defvar cj/anthropic-api-key-cached nil "Cached Anthropic API key.") +(defvar cj/openai-api-key-cached nil "Cached OpenAI API key.") +(defvar gptel-claude-backend nil "Claude backend, lazy-initialized.") +(defvar gptel-chatgpt-backend nil "ChatGPT backend, lazy-initialized.") + +(defcustom cj/gptel-tools-directory + (expand-file-name "gptel-tools/" user-emacs-directory) + "Directory containing optional local GPTel tool modules." + :type 'directory + :group 'cj) + +(defcustom cj/gptel-local-tool-features + '(read_buffer + read_text_file + write_text_file + update_text_file + list_directory_files + move_to_trash + git_status + git_log + git_diff + web_fetch) + "Feature symbols for optional local GPTel tool modules." + :type '(repeat symbol) + :group 'cj) + +(defun cj/gptel-load-local-tools + (&optional tools-directory tool-features) + "Load optional GPTel tools from TOOLS-DIRECTORY. +TOOL-FEATURES defaults to `cj/gptel-local-tool-features'. Return a list +of loaded feature symbols. Missing directories or individual optional +tools are reported with `message' and do not signal." + (let ((dir (file-name-as-directory + (expand-file-name (or tools-directory cj/gptel-tools-directory)))) + (features (or tool-features cj/gptel-local-tool-features)) + (loaded nil)) + (cond + ((not (file-directory-p dir)) + (message "GPTel tools directory not found: %s" dir) + nil) + (t + (add-to-list 'load-path dir) + (dolist (feature features) + (condition-case err + (if (require feature nil 'noerror) + (push feature loaded) + (message "Optional GPTel tool not found: %s" feature)) + (error + (message "Failed to load GPTel tool %s: %s" + feature + (error-message-string err))))) + (nreverse loaded))))) + +(with-eval-after-load 'gptel + (require 'ai-conversations) + (cj/gptel-load-local-tools)) + +(defun cj/auth-source-secret (host user) + "Fetch a required secret from auth-source for HOST and USER. + +HOST and USER must be strings that identify the credential to return. +Errors when no secret is found." + (or (cj/auth-source-secret-value host user) + (error "No usable secret found for host %s and user %s" host user))) + +(defun cj/anthropic-api-key () + "Return the Anthropic API key, caching the result after first retrieval." + (or cj/anthropic-api-key-cached + (setq cj/anthropic-api-key-cached + (cj/auth-source-secret "api.anthropic.com" "apikey")))) + +(defun cj/openai-api-key () + "Return the OpenAI API key, caching the result after first retrieval." + (or cj/openai-api-key-cached + (setq cj/openai-api-key-cached + (cj/auth-source-secret "api.openai.com" "apikey")))) + +(defun cj/--gptel-load-backend-libs () + "Require the gptel backend libraries so their `gptel-make-*' constructors exist. +The local fork (`:load-path \"~/code/gptel\"', `:ensure nil') ships no generated +autoloads, so requiring `gptel' alone never loads `gptel-anthropic' / +`gptel-openai', where the constructors are defined." + (require 'gptel-anthropic) + (require 'gptel-openai)) + +(defun cj/ensure-gptel-backends () + "Initialize GPTel backends if they are not already available. +Loads the backend libraries first so the `gptel-make-*' constructors are +defined even when gptel is the local fork without generated autoloads." + (cj/--gptel-load-backend-libs) + (unless gptel-claude-backend + (setq gptel-claude-backend + (gptel-make-anthropic + "Claude" + :key (cj/anthropic-api-key) + :models '( + "claude-opus-4-7" + "claude-sonnet-4-6" + "claude-haiku-4-5-20251001" + ) + :stream t))) + (unless gptel-chatgpt-backend + (setq gptel-chatgpt-backend + (gptel-make-openai + "ChatGPT" + :key (cj/openai-api-key) + :models '( + "gpt-5.5" + "gpt-5.4-mini" + "o3" + ) + :stream t))) + ;; Set default backend and model + (unless gptel-backend + (setq gptel-backend (or gptel-chatgpt-backend gptel-claude-backend)) + (setq gptel-model 'gpt-5.5))) + +;; ------------------ GPTel Conversation And Utility Commands ------------------ + +(defun cj/gptel--available-backends () + "Return an alist of (NAME . BACKEND). +Ensures gptel and backends are initialized." + (unless (featurep 'gptel) + (require 'gptel)) + (cj/ensure-gptel-backends) + (delq nil + (list (and (bound-and-true-p gptel-claude-backend) + (cons "Anthropic - Claude" gptel-claude-backend)) + (and (bound-and-true-p gptel-chatgpt-backend) + (cons "OpenAI - ChatGPT" gptel-chatgpt-backend))))) + +(defun cj/gptel--model-to-string (m) + "Return model M as a string regardless of its type." + (cond + ((stringp m) m) + ((symbolp m) (symbol-name m)) + (t (format "%s" m)))) + +(defun cj/gptel--model-to-symbol (m) + "Return model M as a symbol regardless of its type. +`gptel-model' must be a symbol: gptel's modeline code calls `symbolp' +on it and signals `wrong-type-argument' on a string, which surfaces as a +redisplay hang. Coerce any model value through this before assigning it." + (cond + ((symbolp m) m) + ((stringp m) (intern m)) + (t (intern (format "%s" m))))) + +;; Backend/model switching helpers (pure logic, extracted for testability) + +(defun cj/gptel--build-model-list (backends model-fn) + "Build a flat list of all models across BACKENDS. +BACKENDS is an alist of (NAME . BACKEND-OBJECT). MODEL-FN is called +with each backend object and should return a list of model identifiers. +Returns a list of entries: (DISPLAY-STRING BACKEND MODEL-STRING BACKEND-NAME) +where DISPLAY-STRING is \"Backend: model\" for use in completing-read." + (mapcan + (lambda (pair) + (let* ((backend-name (car pair)) + (backend (cdr pair)) + (models (funcall model-fn backend))) + (mapcar (lambda (m) + (list (format "%s: %s" backend-name (cj/gptel--model-to-string m)) + backend + (cj/gptel--model-to-string m) + backend-name)) + models))) + backends)) + +(defun cj/gptel--current-model-selection (backends current-backend current-model) + "Format the current backend/model as a display string. +BACKENDS is the alist from `cj/gptel--available-backends'. +CURRENT-BACKEND and CURRENT-MODEL are the active gptel settings. +Returns a string like \"Anthropic - Claude: claude-opus-4-7\"." + (let ((backend-name (car (rassoc current-backend backends)))) + (format "%s: %s" + (or backend-name "AI") + (cj/gptel--model-to-string current-model)))) + +(defun cj/--gptel-apply-model-selection (scope backend model backend-name) + "Set gptel BACKEND and MODEL, globally or buffer-locally per SCOPE. +SCOPE is \"global\" or \"buffer\"; any non-\"global\" value is buffer-local. +MODEL is a symbol. BACKEND-NAME is the display name for the confirmation. +Returns the confirmation message string." + (if (string= scope "global") + (progn + (setq gptel-backend backend) + (setq gptel-model model) + (format "Changed to %s model: %s (global)" backend-name model)) + (setq-local gptel-backend backend) + (setq-local gptel-model model) + (format "Changed to %s model: %s (buffer-local)" backend-name model))) + +;; Backend/model switching commands +(defun cj/gptel-change-model () + "Change the GPTel backend and select a model from that backend. +Present all available models from every backend, switching backends when +necessary. Prompt for whether to apply the selection globally or buffer-locally." + (interactive) + (let* ((backends (cj/gptel--available-backends)) + (all-models (cj/gptel--build-model-list + backends + (lambda (b) + (when (fboundp 'gptel-backend-models) + (gptel-backend-models b))))) + (current-selection (cj/gptel--current-model-selection + backends + (bound-and-true-p gptel-backend) + (bound-and-true-p gptel-model))) + (scope (completing-read "Set model for: " '("buffer" "global") nil t)) + (selected (completing-read + (format "Select model (current: %s): " current-selection) + (mapcar #'car all-models) nil t nil nil current-selection))) + (let* ((model-info (assoc selected all-models)) + (backend (nth 1 model-info)) + (model (intern (nth 2 model-info))) + (backend-name (nth 3 model-info))) + (message "%s" (cj/--gptel-apply-model-selection + scope backend model backend-name))))) + +(defun cj/gptel-switch-backend () + "Switch the GPTel backend and then choose one of its models." + (interactive) + (let* ((backends (cj/gptel--available-backends)) + (choice (completing-read "Select GPTel backend: " (mapcar #'car backends) nil t)) + (backend (cdr (assoc choice backends)))) + (unless backend + (user-error "Invalid GPTel backend: %s" choice)) + (let* ((models (when (fboundp 'gptel-backend-models) + (gptel-backend-models backend))) + (model (completing-read (format "Select %s model: " choice) + (mapcar #'cj/gptel--model-to-string models) + nil t nil nil (cj/gptel--model-to-string (bound-and-true-p gptel-model))))) + (setq gptel-backend backend + gptel-model (cj/gptel--model-to-symbol model)) + (message "Switched to %s with model: %s" choice model)))) + +;; Clear assistant buffer (moved out so it's always available) +(defun cj/gptel-clear-buffer () + "Erase the current GPTel buffer while preserving the initial Org heading. +Operate only when `gptel-mode' is active in an Org buffer so the heading +can be reinserted." + (interactive) + (let ((is-gptel (bound-and-true-p gptel-mode)) + (is-org (derived-mode-p 'org-mode))) + (if (and is-gptel is-org) + (progn + (erase-buffer) + (when (fboundp 'cj/gptel--fresh-org-prefix) + (insert (cj/gptel--fresh-org-prefix))) + (message "GPTel buffer cleared and heading reset")) + (message "Not a GPTel buffer in org-mode. Nothing cleared.")))) + +;; ----------------------------- Context Management ---------------------------- + +(defun cj/gptel--add-file-to-context (file-path) + "Add FILE-PATH to the GPTel context. +Returns t on success, nil on failure. +Provides consistent user feedback about the context state." + (when (and file-path (file-exists-p file-path)) + (gptel-add-file file-path) + (let ((context-count (if (boundp 'gptel-context--alist) + (length gptel-context--alist) + 0))) + (message "Added %s to GPTel context (%d sources total)" + (file-name-nondirectory file-path) + context-count)) + t)) + +(defun cj/gptel-add-file () + "Add a file to the GPTel context. +If inside a Projectile project, prompt from that project's file list. +Otherwise, prompt with `read-file-name'." + (interactive) + (let* ((in-proj (and (featurep 'projectile) + (fboundp 'projectile-project-p) + (projectile-project-p))) + (file-name (if in-proj + (let ((cands (projectile-current-project-files))) + (if (fboundp 'projectile-completing-read) + (projectile-completing-read "GPTel add file: " cands) + (completing-read "GPTel add file: " cands nil t))) + (read-file-name "GPTel add file: "))) + (file-path (if in-proj + (expand-file-name file-name (projectile-project-root)) + file-name))) + (unless (cj/gptel--add-file-to-context file-path) + (error "Failed to add file: %s" file-path)))) + +(defun cj/gptel-add-buffer-file () + "Select a buffer and add its associated file to the GPTel context. +Lists all open buffers for selection. If the selected buffer is visiting +a file, that file is added to the GPTel context. Otherwise, an error +message is displayed." + (interactive) + (let* ((buffers (mapcar #'buffer-name (buffer-list))) + (selected-buffer-name (completing-read "Add file from buffer: " buffers nil t)) + (selected-buffer (get-buffer selected-buffer-name)) + (file-path (and selected-buffer + (buffer-file-name selected-buffer)))) + (if file-path + (cj/gptel--add-file-to-context file-path) + (message "Buffer '%s' is not visiting a file" selected-buffer-name)))) + +(defun cj/gptel-add-this-buffer () + "Add the current buffer to the GPTel context. +Works for any buffer, whether it's visiting a file or not." + (interactive) + ;; Load gptel-context if needed + (unless (featurep 'gptel-context) + (require 'gptel-context)) + ;; Use gptel-add with prefix arg '(4) to add current buffer + (gptel-add '(4)) + (message "Added buffer '%s' to GPTel context" (buffer-name))) + +;;; -------------------------- Org Header Construction -------------------------- + +(defun cj/gptel--fresh-org-prefix () + "Generate a fresh org-mode header with current timestamp for user messages." + (concat "* " user-login-name " " (format-time-string "[%Y-%m-%d %H:%M:%S]") "\n")) + +(defun cj/gptel--refresh-org-prefix (&rest _) + "Update the org-mode prefix with fresh timestamp before sending message." + (setf (alist-get 'org-mode gptel-prompt-prefix-alist) + (cj/gptel--fresh-org-prefix))) + +(defun cj/gptel-backend-and-model () + "Return backend, model, and timestamp as a single string." + (let* ((backend (pcase (bound-and-true-p gptel-backend) + ((and v (pred vectorp)) (aref v 1)) + (_ "AI"))) + (model (format "%s" (or (bound-and-true-p gptel-model) ""))) + (ts (format-time-string "[%Y-%m-%d %H:%M:%S]"))) + (format "%s: %s %s" backend model ts))) + +(defun cj/gptel-insert-model-heading (response-begin-pos _response-end-pos) + "Insert an Org heading for the AI reply at RESPONSE-BEGIN-POS." + (save-excursion + (goto-char response-begin-pos) + (insert (format "* %s\n" (cj/gptel-backend-and-model))))) + +;;; ---------------------------- GPTel Configuration ---------------------------- + +(use-package gptel + :load-path "~/code/gptel" + :ensure nil + :defer t + :commands (gptel gptel-send gptel-menu) + :bind + (:map gptel-mode-map + ("C-<return>" . gptel-send)) + :custom + (gptel-default-mode 'org-mode) + (gptel-expert-commands t) + (gptel-track-media t) + ;; Options: t (include + resend), 'ignore (show but don't resend), + ;; nil (discard), or a buffer name to redirect reasoning to + (gptel-include-reasoning "*AI-Reasoning*") + (gptel-log-level 'info) + (gptel--debug nil) + :config + (cj/ensure-gptel-backends) + ;; Set ChatGPT (gpt-5.5) as default after initialization. Model + ;; must be a symbol -- gptel's modeline-display code calls `symbolp' + ;; on it and signals `wrong-type-argument' otherwise. + (setq gptel-backend gptel-chatgpt-backend) + (setq gptel-model 'gpt-5.5) + + (setq gptel-confirm-tool-calls nil) ;; allow tool access by default + + ;; Initialize org-mode user prefix and wire up hooks + (setf (alist-get 'org-mode gptel-prompt-prefix-alist) + (cj/gptel--fresh-org-prefix)) + (advice-add 'gptel-send :before #'cj/gptel--refresh-org-prefix) + (add-hook 'gptel-post-response-functions #'cj/gptel-insert-model-heading)) + +;;; ---------------------------- Toggle GPTel Window ---------------------------- + +(defvar cj/ai-assistant-window-width 0.4 + "Default fraction of frame width for the *AI-Assistant* side window. +Used until the panel is resized and toggled off this session; after that, +the toggled-off width is remembered in `cj/--ai-assistant-width'.") + +(defvar cj/--ai-assistant-width nil + "Last width fraction the *AI-Assistant* side window was toggled off at. +nil falls back to `cj/ai-assistant-window-width'. Shared by the panel's +entry points (toggle, load-conversation, quick-ask escalation) so the +panel reopens at one consistent width. In-memory only -- resets each +Emacs session.") + +(defun cj/toggle-gptel () + "Toggle the visibility of the AI-Assistant buffer, and place point at its end. +The panel opens at `cj/ai-assistant-window-width'; once it has been resized +and toggled off this session, it reopens at that remembered width." + (interactive) + (let* ((buf-name "*AI-Assistant*") + (buffer (get-buffer buf-name)) + (win (and buffer (get-buffer-window buffer)))) + (if win + (progn + (cj/side-window-capture-size win 'right 'cj/--ai-assistant-width) + (delete-window win)) + ;; Ensure GPTel and our backends are initialized before creating the buffer + (unless (featurep 'gptel) + (require 'gptel)) + (cj/ensure-gptel-backends) + (unless buffer + ;; Pass backend, not model + (gptel buf-name gptel-backend)) + (setq buffer (get-buffer buf-name)) + (setq win + (cj/side-window-display + buffer 'right 'cj/--ai-assistant-width + cj/ai-assistant-window-width)) + (select-window win) + (with-current-buffer buffer + (goto-char (point-max)))))) + +;; ------------------------------- Clear Context ------------------------------- + +(defun cj/gptel-context-clear () + "Clear all GPTel context sources, with compatibility across GPTel versions." + (interactive) + (cond + ((fboundp 'gptel-context-remove-all) + (call-interactively 'gptel-context-remove-all) + (message "GPTel context cleared")) + ((fboundp 'gptel-context-clear) + (call-interactively 'gptel-context-clear) + (message "GPTel context cleared")) + ((boundp 'gptel-context--alist) + (setq gptel-context--alist nil) + (message "GPTel context cleared")) + (t + (message "No known GPTel context clearing function available")))) + +;;; -------------------------------- GPTel-Magit -------------------------------- + +;; Each integration point waits on its actual dependency, not on `magit' +;; broadly. `magit.el' calls `(provide 'magit)' BEFORE its +;; `cl-eval-when (load eval) ...' block requires `magit-commit' and +;; `magit-stash', so a single `with-eval-after-load 'magit' fires while +;; the transient prefixes the wiring references are still undefined. +;; `transient-append-suffix' silently no-ops on missing prefixes (it +;; calls `message' unless `transient-error-on-insert-failure' is set), +;; which is how the failure stayed invisible. +;; +;; Keys: +;; M-g — generate commit message (in commit message buffer) +;; g — generate commit (in magit-commit transient) +;; x — explain diff (in magit-diff transient) + +(use-package gptel-magit + :defer t + :commands (gptel-magit-generate-message + gptel-magit-commit-generate + gptel-magit-diff-explain) + :init + (with-eval-after-load 'git-commit + (define-key git-commit-mode-map (kbd "M-g") #'gptel-magit-generate-message)) + (with-eval-after-load 'magit-commit + (transient-append-suffix 'magit-commit #'magit-commit-create + '("g" "Generate commit" gptel-magit-commit-generate))) + (with-eval-after-load 'magit-diff + (transient-append-suffix 'magit-diff #'magit-stash-show + '("x" "Explain" gptel-magit-diff-explain)))) + +;; ------------------------------ GPTel Directives ----------------------------- + +(use-package gptel-prompts + :load-path (lambda () (expand-file-name "custom/" user-emacs-directory)) + :after gptel + :if (file-exists-p (expand-file-name "custom/gptel-prompts.el" user-emacs-directory)) + :custom + (gptel-prompts-directory (concat user-emacs-directory "ai-prompts")) + :config + (gptel-prompts-update) + (gptel-prompts-add-update-watchers) + ;; gptel--system-message is set at gptel load time, before gptel-prompts + ;; replaces the default directive. Re-apply it now. + (when-let* ((dir (alist-get 'default gptel-directives))) + (setq gptel--system-message dir))) + +;;; --------------------------------- AI Keymap --------------------------------- + +(defvar-keymap cj/ai-keymap + :doc "Keymap for gptel and other AI operations." + "A" #'cj/gptel-autosave-toggle ;; toggle autosave on the current GPTel buffer + "B" #'cj/gptel-switch-backend ;; change the backend (OpenAI, Anthropic, etc. + "M" #'gptel-menu ;; gptel's transient menu + "d" #'cj/gptel-delete-conversation ;; delete conversation + "." #'cj/gptel-add-this-buffer ;; add buffer to context + "f" #'cj/gptel-add-file ;; add a file to context + "b" #'cj/gptel-browse-conversations ;; browse saved conversations + "l" #'cj/gptel-load-conversation ;; load and continue conversation + "m" #'cj/gptel-change-model ;; change the LLM model + "p" #'gptel-system-prompt ;; change prompt + "q" #'cj/gptel-quick-ask ;; one-shot quick ask + "r" #'cj/gptel-rewrite-with-directive ;; rewrite region with a chosen directive + "R" #'cj/gptel-rewrite-redo-with-different-directive ;; redo last rewrite, new directive + "c" #'cj/gptel-context-clear ;; clear all context + "s" #'cj/gptel-save-conversation ;; save conversation + "t" #'cj/toggle-gptel ;; toggles the ai-assistant window + "x" #'cj/gptel-clear-buffer) ;; clears the assistant buffer +(cj/register-prefix-map "a" cj/ai-keymap) + +(with-eval-after-load 'which-key + (which-key-add-key-based-replacements + "C-; a" "AI assistant menu" + "C-; a A" "toggle autosave" + "C-; a B" "switch backend" + "C-; a M" "gptel menu" + "C-; a b" "browse conversations" + "C-; a d" "delete conversation" + "C-; a ." "add buffer" + "C-; a f" "add file" + "C-; a l" "load conversation" + "C-; a m" "change model" + "C-; a p" "change prompt" + "C-; a q" "quick ask" + "C-; a r" "rewrite region (directive)" + "C-; a R" "redo rewrite, new directive" + "C-; a c" "clear context" + "C-; a s" "save conversation" + "C-; a t" "toggle window" + "C-; a x" "clear buffer")) + +(provide 'ai-config) +;;; ai-config.el ends here. |
