From 973e6b19e6ef3a88a7b00a6ef760766b7b3647cf Mon Sep 17 00:00:00 2001 From: JenChieh Date: Sun, 11 Feb 2024 04:40:18 -0800 Subject: [PATCH] Add src --- .gitignore | 3 ++ google-gemini-content.el | 58 +++++++++++++++++++++++++ google-gemini.el | 92 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 google-gemini-content.el diff --git a/.gitignore b/.gitignore index d67dbfa..825d564 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ dist/ # packaging *-autoloads.el *-pkg.el + +# ignore internal test +_test/ diff --git a/google-gemini-content.el b/google-gemini-content.el new file mode 100644 index 0000000..3960697 --- /dev/null +++ b/google-gemini-content.el @@ -0,0 +1,58 @@ +;;; google-gemini-content.el --- Create generate content with Google Gemini API -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Shen, Jen-Chieh + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; Create generate content with Google Gemini API. +;; + +;;; Code: + +(require 'google-gemini) + +;; +;;; API + +;;;###autoload +(cl-defun google-gemini-generate-content ( contents callback + &key + (content-type "application/json")) + "Send generate content request." + (request (format "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=%s" + google-gemini-key) + :type "POST" + :headers (google-gemini--headers content-type) + :data (google-gemini--json-encode + `(("contents" . ,contents))) + :parser 'json-read + :complete (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data))))) + +;; +;;; Application + +;;;###autoload +(defun google-gemini-say () + "Start making a conversation to Google Gemini." + (interactive) + ) + +(provide 'google-gemini-content) +;;; google-gemini-content.el ends here diff --git a/google-gemini.el b/google-gemini.el index 3e17ff7..309b3f3 100644 --- a/google-gemini.el +++ b/google-gemini.el @@ -6,7 +6,7 @@ ;; Maintainer: JenChieh ;; URL: https://github.com/emacs-openai/google-gemini ;; Version: 0.1.0 -;; Package-Requires: ((emacs "26.1")) +;; Package-Requires: ((emacs "26.1") (request "0.3.0")) ;; Keywords: comm google gemini ;; This file is not part of GNU Emacs. @@ -31,7 +31,95 @@ ;;; Code: -;; Happy coding! ;) +(require 'auth-source) +(require 'cl-lib) +(require 'let-alist) +(require 'pcase) +(require 'pp) +(require 'json) + +(require 'request) + +(defgroup google-gemini nil + "Elisp library for the Google Gemini API." + :prefix "google-gemini-" + :group 'comm + :link '(url-link :tag "Repository" "https://github.com/emacs-openai/google-gemini")) + +;; +;;; Logger + +(defvar google-gemini--show-log nil + "Get more information from the program.") + +(defun google-gemini--log (fmt &rest args) + "Debug message like function `message' with same argument FMT and ARGS." + (when google-gemini--show-log + (apply 'message fmt args))) + +;; +;;; Request + +(defvar google-gemini-key "" + "Variable storing the gemini key or a function name to retrieve it. + +The function should take no arguments and return a string containing the key. + +A function, `google-gemini-key-auth-source', that retrieves the key from +auth-source is provided for convenience.") + +(defcustom google-gemini-base-url "googleapis.com" + "The base URL for Google Gemini API requests." + :type 'string + :group 'google-gemini) + +;;;###autoload +(defun google-gemini-key-auth-source (&optional base-url) + "Retrieve the Google Gemini API key from auth-source given a BASE-URL. +If BASE-URL is not specified, it defaults to `google-gemini-base-url'." + (if-let ((auth-info + (auth-source-search :max 1 + :host (or (url-host (url-generic-parse-url (or base-url google-gemini-base-url))) + google-gemini-base-url) + :require '(:user :secret)))) + (funcall (plist-get (car auth-info) :secret)) + (error "Google Gemini API key not found in auth-source"))) + +(defun google-gemini--alist-omit-null (alist) + "Omit null value or empty string in ALIST." + (cl-remove-if (lambda (pair) + (let ((value (cdr pair))) + (or (null value) ; ignore null + (and (stringp value) ; ignore empty string + (string-empty-p value))))) + alist)) + +(defun google-gemini--headers (content-type) + "Construct request headers. + +Arguments CONTENT-TYPE are common request headers." + (google-gemini--alist-omit-null + `(("Content-Type" . ,content-type) + ,(if (or (null key) + (string-empty-p key)) + "" + (pcase openai-key-type + (:bearer `("Authorization" . ,(concat "Bearer " key))) + (:azure-api `("api-key" . ,key)) + (_ (user-error "Invalid key type: %s" + openai-key-type)))) + ))) + +(defun google-gemini--json-encode (object) + "Wrapper for function `json-encode' but it remove nil value before +constructing JSON data. + +The argument OBJECT is an alist that can be construct to JSON data; see function +`json-encode' for the detials." + (let* ((object (google-gemini--alist-omit-null object)) + (encoded (json-encode object))) + (google-gemini--log "[ENCODED]: %s" encoded) + encoded)) (provide 'google-gemini) ;;; google-gemini.el ends here