diff --git a/README.org b/README.org index 32b9150..07bebc5 100644 --- a/README.org +++ b/README.org @@ -23,7 +23,10 @@ Both functions are accessed through an [[https://help.kagi.com/kagi/api/overview ** FastGPT -The FastGPT functionality has only one command: =kagi-fastgpt-shell=. This opens a shell buffer in a new window where prompts can be typed. Kagi FastGPT typically returns output based on actual search results. When point is on one of the listed URLs, press =C-c RET= to open it. +- =kagi-fastgpt-shell= :: Opens a shell buffer in a new window where prompts can be typed. This Kagi FastGPT typically returns output based on actual search results. When point is on one of the listed URLs, press =C-c RET= to open it. +- =kagi-fastgpt-prompt= :: Enter a prompt in the minibuffer and show the result in a separate buffer. With a universal prefix (=C-u=), the result is inserted at point. +- =kagi-fastgpt= :: Function to retrieve a FastGPT response, to be used from Lisp code. +- =kagi-translate= :: This command translates strings or complete buffers to another language (including programming languages). ** Universal Summarizer diff --git a/kagi.el b/kagi.el index 9c641a4..3034432 100644 --- a/kagi.el +++ b/kagi.el @@ -101,42 +101,54 @@ https://help.kagi.com/kagi/api/summarizer.html." kagi--summarizer-engines)) :group 'kagi) -(defvar kagi--summarizer-languages '(("Document language" . nil) - ("Bulgarian [BG]" . "BG") - ("Czech [CZ]" . "CZ") - ("Danish [DA]" . "DA") - ("German [DE]" . "DE") - ("Greek [EL]" . "EL") - ("English [EN]" . "EN") - ("Spanish [ES]" . "ES") - ("Estonian [ET]" . "ET") - ("Finnish [FI]" . "FI") - ("French [FR]" . "FR") - ("Hungarian [HU]" . "HU") - ("Indonesian [ID]" . "ID") - ("Italian [IT]" . "IT") - ("Japanese [JA]" . "JA") - ("Korean [KO]" . "KO") - ("Lithuanian [LT]" . "LT") - ("Latvian [LV]" . "LV") - ("Norwegian [NB]" . "NB") - ("Dutch [NL]" . "NL") - ("Polish [PL]" . "PL") - ("Portuguese [PT]" . "PT") - ("Romanian [RO]" . "RO") - ("Russian [RU]" . "RU") - ("Slovak [SK]" . "SK") - ("Slovenian [SL]" . "SL") - ("Swedish [SV]" . "SV") - ("Turkish [TR]" . "TR") - ("Ukrainian [UK]" . "UK") - ("Chinese (simplified) [ZH]" . "ZH")) +(defvar kagi--languages '(("Bulgarian" . "BG") + ("Czech" . "CZ") + ("Danish" . "DA") + ("German" . "DE") + ("Greek" . "EL") + ("English" . "EN") + ("Spanish" . "ES") + ("Estonian" . "ET") + ("Finnish" . "FI") + ("French" . "FR") + ("Hungarian" . "HU") + ("Indonesian" . "ID") + ("Italian" . "IT") + ("Japanese" . "JA") + ("Korean" . "KO") + ("Lithuanian" . "LT") + ("Latvian" . "LV") + ("Norwegian" . "NB") + ("Dutch" . "NL") + ("Polish" . "PL") + ("Portuguese" . "PT") + ("Romanian" . "RO") + ("Russian" . "RU") + ("Slovak" . "SK") + ("Slovenian" . "SL") + ("Swedish" . "SV") + ("Turkish" . "TR") + ("Ukrainian" . "UK") + ("Chinese (simplified)" . "ZH")) + "Supported languages by the Kagi LLM.") + +(defvar kagi--summarizer-languages (append + '(("Document language" . nil) + kagi--languages)) "Supported languages by the Kagi Universal Summarizer.") +(defvar kagi--language-history nil) + (defcustom kagi-summarizer-default-language nil - "Default target language of the summary." + "Default target language of the summary. + +The value should be a string of two characters representing the + language. See variable `kagi--summarizer-languages' for the list + of language codes." :type (append '(choice) - (mapcar (lambda (lang) `(const :tag ,(car lang) ,(cdr lang))) + (mapcar (lambda (lang) + `(const :tag ,(format "%s [%s]" (car lang) (cdr lang)) + ,(cdr lang))) kagi--summarizer-languages)) :group 'kagi) @@ -343,8 +355,7 @@ list of conses." (defun kagi--display-summary (summary buffer-name) "Display the SUMMARY in a buffer called BUFFER-NAME." - (with-current-buffer (get-buffer-create buffer-name) - (erase-buffer) + (with-current-buffer (generate-new-buffer-name buffer-name) (insert summary) (goto-char 0) (text-mode) @@ -355,16 +366,23 @@ list of conses." (save-excursion (insert (substring-no-properties summary)))) -(defun kagi--process-prompt (prompt) - "Submit a PROMPT to FastGPT and process the API response. - -Returns a formatted string to be displayed by the shell." +(defun kagi-fastgpt (prompt) + "Submit a PROMPT to FastGPT and return a formatted response string." (let* ((response (kagi--call-fastgpt prompt)) (parsed-response (json-parse-string response)) (output (kagi--gethash parsed-response "data" "output")) (references (kagi--gethash parsed-response "data" "references"))) (format "%s\n\n%s" (kagi--format-output output) (kagi--format-references references)))) +(defun kagi--fastgpt-display-result (result) + "Display the RESULT of a FastGPT prompt in a new buffer." + (let ((buffer-name (generate-new-buffer-name "*fastgpt-result*"))) + (with-current-buffer (get-buffer-create buffer-name) + (save-excursion + (insert result)) + (text-mode) + (display-buffer buffer-name)))) + (defvar kagi-fastgpt--config (make-shell-maker-config :name "FastGPT" @@ -372,7 +390,7 @@ Returns a formatted string to be displayed by the shell." :execute-command (lambda (command _history callback error-callback) (condition-case err - (funcall callback (kagi--process-prompt command) nil) + (funcall callback (kagi-fastgpt-prompt command) nil) (json-parse-error (funcall error-callback (format "Could not parse the server response %s" (cdr err)))) (error (funcall error-callback (format "An error occurred during the request %s" (cdr err))))))) @@ -386,6 +404,67 @@ Returns a formatted string to be displayed by the shell." (interactive) (shell-maker-start kagi-fastgpt--config)) +;;;###autoload +(defun kagi-fastgpt-prompt (prompt &optional insert) + "Feed the given PROMPT to FastGPT. + +If INSERT is non-nil, the response is inserted at point. +Otherwise, show the result in a separate buffer." + (interactive "sfastgpt> \nP") + (let ((result (kagi-fastgpt prompt))) + (if (and insert (not buffer-read-only)) + (save-excursion + (insert result)) + (kagi--fastgpt-display-result result)))) + +(defun kagi--read-language (prompt) + "Read a language from the minibuffer interactively. + +PROMPT is passed to the corresponding parameters of +`completing-read', refer to its documentation for more info." + (completing-read prompt kagi--languages + nil + nil + nil + kagi--language-history + "English")) + +(defun kagi-translate (text target-language &optional source-language interactive-p) + "Translate the TEXT to TARGET-LANGUAGE using FastGPT. + +With a single universal prefix, also prompt for the SOURCE-LANGUAGE. + +When INTERACTIVE-P is nil, the translation is returned as a string. + +When non-nil, the translation is shown in the echo area when the +result is short, otherwise it is displayed in a new buffer." + (interactive + (list (if (use-region-p) + (buffer-substring-no-properties (region-beginning) (region-end)) + (let ((buffer-or-text (read-buffer (format-prompt "Buffer name or text" nil)))) + (cond ((get-buffer buffer-or-text) + (with-current-buffer buffer-or-text + (buffer-string))) + ((< 0 (length buffer-or-text)) buffer-or-text) + (t (error "No buffer or text entered"))))) + (kagi--read-language (format-prompt "Target language" nil)) + (when (equal current-prefix-arg '(4)) + (kagi--read-language (format-prompt "Source language" nil))) + t)) + (let* ((prompt (format "Translate the following text %sto %s: + +%s" + (if source-language + (format "from %s " source-language) + "") + target-language + text)) + (result (string-trim (kagi-fastgpt prompt))) + (result-lines (length (string-lines result)))) + (cond ((and interactive-p (eql result-lines 1)) (message result)) + ((and interactive-p (> result-lines 1)) (kagi--fastgpt-display-result result)) + (t result)))) + ;;; Summarizer (defun kagi--get-domain-name (url) @@ -456,13 +535,18 @@ this when PROMPT-INSERT-P is non-nil." (y-or-n-p "Insert summary at point?"))) (list (when (equal current-prefix-arg '(4)) - (alist-get - (completing-read (format-prompt "Output language" "") - kagi--summarizer-languages nil t) - kagi--summarizer-languages - (or kagi-summarizer-default-language "EN") - nil - #'string=))) + (let ((language-table (mapcar (lambda (lang) + (cons + (format "%s [%s]" (car lang) (cdr lang)) + (cdr lang))) + kagi--summarizer-languages))) + (alist-get + (completing-read (format-prompt "Output language" "") + language-table nil t nil kagi--language-history) + language-table + (or kagi-summarizer-default-language "EN") + nil + #'string=)))) (list (when (equal current-prefix-arg '(4)) (completing-read (format-prompt "Engine" "")