;;; ai/gptel.el -*- lexical-binding: t; -*- ;;; ============================================================================ ;;; gptel Configuration ;;; ============================================================================ ;; Core gptel setup (use-package! gptel :defer t :config ;; Default backend: OpenRouter (setq gptel-model 'google/gemini-3-flash-preview gptel-backend (gptel-make-openai "OpenRouter" :host "openrouter.ai" :endpoint "/api/v1/chat/completions" :stream t :key "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" :models '(google/gemini-3-flash-preview x-ai/grok-4.1-fast x-ai/grok-code-fast-1 google/gemini-3-pro-preview anthropic/claude-sonnet-4.5))) ;; Use org-mode by default for better note-taking (setq gptel-default-mode 'org-mode) ;; Move cursor to end of response automatically (add-hook 'gptel-post-response-functions 'gptel-end-of-response) ;; Keybindings - using SPC A for AI (uppercase to avoid conflicts) (map! :leader (:prefix ("A" . "AI/gptel") :desc "gptel chat" "g" #'gptel :desc "gptel send" "s" #'gptel-send :desc "gptel menu" "m" #'gptel-menu :desc "gptel rewrite" "r" #'gptel-rewrite :desc "Add context" "c" #'gptel-add :desc "Add file to context" "f" #'gptel-add-file)) ;; Additional keybindings in gptel buffer (map! :map gptel-mode-map "C-c RET" #'gptel-send "C-c C-k" #'gptel-abort "C-c C-c" #'gptel-menu)) ;; gptel presets for different tasks (after! gptel ;; Coding assistant preset (gptel-make-preset 'coder :description "Expert coding assistant" :backend "OpenRouter" :model 'anthropic/claude-sonnet-4.5 :system "You are an expert coding assistant. Provide high-quality code solutions, refactorings, and explanations. Be concise but thorough. Include error handling and follow best practices.") ;; Code explainer preset (gptel-make-preset 'explain :description "Explain code to beginners" :system "Explain this code to a novice programmer. Use simple language and break down complex concepts.") ;; Refactoring preset (gptel-make-preset 'refactor :description "Code refactoring expert" :model 'anthropic/claude-sonnet-4.5 :system "You are a code refactoring expert. Improve code quality, readability, and performance while maintaining functionality. Explain your changes.") ;; Quick answers preset (gptel-make-preset 'quick :description "Quick, concise answers" :model 'google/gemini-3-flash-preview :system "Provide quick, concise answers. Be direct and to the point.") ;; Documentation writer preset (gptel-make-preset 'docs :description "Documentation writer" :system "Write clear, comprehensive documentation. Include examples and explain edge cases.")) ;;; ============================================================================ ;;; gptel-quick - Quick lookups and explanations ;;; ============================================================================ (use-package! gptel-quick :after gptel :defer t :config ;; Include context from gptel-add if available (setq gptel-quick-use-context t) (map! :leader (:prefix "A" :desc "Quick lookup" "q" #'gptel-quick))) ;;; ============================================================================ ;;; gptel-extensions - Extra utility functions ;;; ============================================================================ (use-package! gptel-extensions :after gptel :defer t :config (map! :leader (:prefix "A" :desc "Load buffer into session" "l" #'gptel-extensions-load-buffer :desc "Send whole buffer" "b" #'gptel-extensions-send-whole-buffer :desc "Refactor region" "R" #'gptel-extensions-refactor))) ;;; ============================================================================ ;;; gptel-autocomplete - Inline code completion ;;; ============================================================================ (use-package! gptel-autocomplete :after gptel :defer t :commands (gptel-complete gptel-accept-completion) :config ;; Configure context size (setq gptel-autocomplete-before-context-lines 100 gptel-autocomplete-after-context-lines 20 gptel-autocomplete-temperature 0.1) ;; Keybindings for autocomplete (map! :leader (:prefix "A" :desc "Complete at point" "C" #'gptel-complete :desc "Accept completion" "a" #'gptel-accept-completion)) ;; Also add convenient keybindings in prog-mode (map! :map prog-mode-map "M-TAB" #'gptel-complete "C-c TAB" #'gptel-complete "C-c C-a" #'gptel-accept-completion)) (defun my/gptel-quick-explain-region () "Quickly explain selected code region in the gptel buffer." (interactive) (when (use-region-p) (let* ((code (buffer-substring-no-properties (region-beginning) (region-end))) (buf (get-buffer-create "*gptel*"))) ;; Prepare the buffer and insert the query immediately (with-current-buffer buf (goto-char (point-max)) (unless (derived-mode-p 'gptel-mode) (funcall gptel-default-mode) (gptel-mode 1)) (insert "\n\n--- Code to Explain ---\n\n" code "\n\n### Explanation Request Sent...\n")) (display-buffer buf) ;; Send the request (gptel-request (format "Explain this code concisely in 2-3 sentences:\n\n%s" code) :system "You are an expert programmer explaining code." :callback (lambda (response info) (with-current-buffer (get-buffer "*gptel*") (goto-char (point-max)) (insert "\n### Code Explanation\n\n" response "\n\n")) (message "Explanation received in *gptel* buffer.")))))) (defun my/gptel-proofread-region () "Proofread and improve selected text." (interactive) (when (use-region-p) (gptel-rewrite-region (region-beginning) (region-end) "Fix spelling, grammar, and improve clarity. Keep the same tone and style."))) (defun my--get-region-as-path-tag (path-fn) "Helper to get path tag using PATH-FN to determine the file path." (let* ((filename (funcall path-fn)) (start-line (line-number-at-pos (if (use-region-p) (region-beginning) (point)))) (tag (if (use-region-p) (let* ((end-pos (1- (region-end))) (end-line (line-number-at-pos (max (region-beginning) end-pos)))) (format "@%s:%d-%d" filename start-line end-line)) (format "@%s:%d" filename start-line)))) tag)) (defun my/copy-region-as-path-tag () "Copy the current region as a relative path tag: @path/to/file.ext:start-end" (interactive) (let ((tag (my--get-region-as-path-tag (lambda () (file-relative-name (buffer-file-name) (doom-project-root)))))) (kill-new tag) (message "Copied: %s" tag))) (defun my/copy-region-as-path-tag-absolute () "Copy the current region as an absolute path tag: @/abs/path/to/file.ext:start-end" (interactive) (let ((tag (my--get-region-as-path-tag #'buffer-file-name))) (kill-new tag) (message "Copied: %s" tag))) ;; Add keybindings for helper functions (defvar my-ai-helper-map (make-sparse-keymap) "Keymap for my AI helper functions.") (global-set-key (kbd "C-c a") my-ai-helper-map) (define-key my-ai-helper-map (kbd "e") #'my/gptel-quick-explain-region) (define-key my-ai-helper-map (kbd "p") #'my/gptel-proofread-region) (define-key my-ai-helper-map (kbd "t") #'my/copy-region-as-path-tag) (define-key my-ai-helper-map (kbd "T") #'my/copy-region-as-path-tag-absolute) (map! :leader (:prefix "A" :desc "Quick explain" "e" #'my/gptel-quick-explain-region :desc "Proofread text" "p" #'my/gptel-proofread-region :desc "Copy path tag (relative)" "t" #'my/copy-region-as-path-tag :desc "Copy path tag (absolute)" "T" #'my/copy-region-as-path-tag-absolute))