From e78a5032dc7a1433de8d16feb66f9824c82b343a Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Thu, 2 Mar 2023 20:37:32 -0500 Subject: [PATCH] xe-chatgpt Signed-off-by: Xe Iaso --- common/home-manager/emacs/default.nix | 20 ++++- common/home-manager/emacs/packages/tclip.el | 81 +++++++++++++++++++ .../home-manager/emacs/packages/xe-chatgpt.el | 73 +++++++++++++++++ .../emacs/{ => packages}/xe-tools.el | 0 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 common/home-manager/emacs/packages/tclip.el create mode 100644 common/home-manager/emacs/packages/xe-chatgpt.el rename common/home-manager/emacs/{ => packages}/xe-tools.el (100%) diff --git a/common/home-manager/emacs/default.nix b/common/home-manager/emacs/default.nix index fc837a3..1d974a0 100644 --- a/common/home-manager/emacs/default.nix +++ b/common/home-manager/emacs/default.nix @@ -588,12 +588,30 @@ in { src = ./packages/change-case.el; }); }; + + tclip = { + enable = true; + after = [ "request" ]; + package = (epkgs: epkgs.trivialBuild { + pname = "tclip"; + src = ./packages/tclip.el; + }); + }; + + xe-chatgpt = { + enable = true; + after = [ "request" ]; + package = (epkgs: epkgs.trivialBuild { + pname = "xe-chatgpt"; + src = ./packages/xe-chatgpt.el; + }); + }; xe-tools = { enable = true; package = (epkgs: epkgs.trivialBuild { pname = "xe-tools"; - src = ./xe-tools.el; + src = ./packages/xe-tools.el; }); config = '' diff --git a/common/home-manager/emacs/packages/tclip.el b/common/home-manager/emacs/packages/tclip.el new file mode 100644 index 0000000..2a501b4 --- /dev/null +++ b/common/home-manager/emacs/packages/tclip.el @@ -0,0 +1,81 @@ +;;; tclip.el --- tclip client for Emacs -*- lexical-binding: t; -*- + +;; Copyright (C) +;; 2023 Tailscale, Inc. +;; Author: Xe Iaso +;; Maintainer: Xe Iaso +;; Created: 2023-01-13 +;; Version: 0.1 +;; Keywords: tailscale, pastebin, sharing +;; Homepage: https://github.com/tailscale-dev/tclip + +;;; Commentary: +;; +;; This uses request-el to make requests to your tailnet's tclip server. You +;; can install request-el with M-x package-install. +;; +;; This package requires that you have a tclip server set up. This package +;; reaches out to a tclip server over either plain HTTP, or HTTPS should you +;; configure the variable `tclip-server'. +;; +;; Usage: +;; +;; To submit the contents of the current buffer to tclip: +;; M-x tclip-submit-buffer +;; To submit the contents of the currently highlighted region to tclip: +;; M-x tclip-submit-region +;; +;; Customization: +;; +;; To customize the tclip server this package reaches out to: +;; M-x customize-group tclip +;; +;; You can customize the tclip server URL by changing the value of `tclip-server': +;; (setq tclip-server "https://paste.shark-harmonic.ts.net") + +;;; Code: + +(require 'request) + +(defgroup tclip nil + "Tclip server configuration." + :prefix "tclip-" + :group 'tclip) + +(defcustom tclip-server "http://paste" + "The server that is running tclip or a service with a compatible API to tclip. This should NOT end with a trailing slash." + :group 'tclip + :type 'string) + +(defun tclip--send-paste (fname content) + "Internal function that actually fires off the paste with name FNAME and content CONTENT to the tclip server." + (request (format "%s/api/post" tclip-server) + :type "POST" + :data `(("filename" . ,fname) + ("content" . ,content)) + :headers '(("Accept" . "text/plain")) + :timeout 60 + :success (cl-function + (lambda (&key response &allow-other-keys) + (message "%s" (request-response-data response)))))) + +(defun tclip-submit-buffer () + "Submits the entire current buffer to tclip." + (interactive) + (let ((fname (format "%s.%s" + (file-name-base (buffer-file-name)) + (file-name-extension (buffer-file-name)))) + (content (buffer-string))) + (tclip--send-paste fname content))) + +(defun tclip-submit-region () + "Submits the highlighted region to tclip." + (interactive) + (let ((fname (format "%s.%s" + (file-name-base (buffer-file-name)) + (file-name-extension (buffer-file-name)))) + (content (buffer-substring-no-properties (region-beginning) (region-end)))) + (tclip--send-paste fname content))) + +(provide 'tclip) +;;; tclip.el ends here diff --git a/common/home-manager/emacs/packages/xe-chatgpt.el b/common/home-manager/emacs/packages/xe-chatgpt.el new file mode 100644 index 0000000..04940ea --- /dev/null +++ b/common/home-manager/emacs/packages/xe-chatgpt.el @@ -0,0 +1,73 @@ +;;; xe-tools --- Xe's chatgpt bindings + +;;; Commentary: + +;;; I guess we're gonna have to deal with this shit, +;;; so I might as well try and learn how to use it. + +;;; Code: + +(setf lexical-binding t) +(eval-when-compile '(require 'cl)) + +(require 'request) + +(defcustom xe/chatgpt-base-prompt + "You are an assistant that helps Xe Iaso with programming. You will return answers and code that helps Xe program things." + "The default system message for ChatGPT." + :type 'string) + +(defun xe/chatgpt--chomp (str) + "Chomp leading and tailing whitespace from STR." + (while (string-match "\\`\n+\\|^\\s-+\\|\\s-+$\\|\n+\\'" + str) + (setq str (replace-match "" t t str))) + str) + +(defun xe/chatgpt--read-file (fname) + "Reads FNAME and returns its contents as a string." + (with-temp-buffer + (insert-file-contents fname) + (xe/chatgpt--chomp (buffer-string)))) + +(defun xe/chatgpt--make-request (question) + "Internal function to ask ChatGPT a QUESTION and insert the result text of the first response to the current buffer." + (let* ((req `(("model" . "gpt-3.5-turbo") + ("messages" . ((("role" . "system") ("content" . ,xe/chatgpt-base-prompt)) + (("role" . "user") ("content" . ,question)))))) + (auth-key (xe/chatgpt--read-file + (format "%s/.openai-token" (getenv "HOME")))) + (headers `(("Content-Type" . "application/json") + ("Authorization" . ,(format "Bearer %s" auth-key))))) + (request + "https://api.openai.com/v1/chat/completions" + :type "POST" + :data (json-encode req) + :headers headers + :parser 'json-read + :encoding 'utf-8 + :success (cl-function + (lambda (&key data &allow-other-keys) + (message "%S" data) + (let* ((choice (aref (alist-get 'choices data) 0)) + (message (alist-get 'message choice)) + (content (alist-get 'content message))) + (message "ChatGPT reply: %s" content) + (insert content))))))) + +(defun xe/chatgpt-ask (question) + "Ask ChatGPT a QUESTION and get the response put into your current buffer." + (interactive "squestion> ") + (message "ChatGPT ask: %s" question) + (xe/chatgpt--make-request prompt)) + +(defun xe/chatgpt-ask-with-mode (question) + "Ask ChatGPT a QUESTION and get the response put into your current buffer. This will add the context of what editor major mode you are in." + (interactive "squestion> ") + (message "ChatGPT ask: %s" question) + (let* ((editor-mode (string-join (split-string (symbol-name major-mode) "-") " ")) + (prompt (format "%s\nUser is in %s. Only include the code." question editor-mode))) + (xe/chatgpt--make-request prompt))) + +(provide 'xe-chatgpt) +;;; xe-chatgpt.el ends here diff --git a/common/home-manager/emacs/xe-tools.el b/common/home-manager/emacs/packages/xe-tools.el similarity index 100% rename from common/home-manager/emacs/xe-tools.el rename to common/home-manager/emacs/packages/xe-tools.el