Initial commit
This commit is contained in:
commit
07e5e799ea
2 changed files with 588 additions and 0 deletions
189
AGENTS.md
Normal file
189
AGENTS.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# Agent Guidelines for nano-gpt
|
||||
|
||||
This is an Emacs Lisp project for a dashboard application. The codebase consists of:
|
||||
- `dashboard.el` - Main dashboard implementation (180 lines)
|
||||
|
||||
## Build/Load Commands
|
||||
|
||||
```elisp
|
||||
;; Load the dashboard in Emacs
|
||||
(load-file "dashboard.el")
|
||||
|
||||
;; Byte-compile for faster loading
|
||||
(byte-compile-file "dashboard.el")
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
This project uses Emacs's built-in ERT testing framework. No external test framework is configured.
|
||||
|
||||
To run a single test in Emacs:
|
||||
```elisp
|
||||
;; Evaluate the test definition, then run:
|
||||
(ert-run-tests-interactively "^test-name$")
|
||||
```
|
||||
|
||||
Or from command line:
|
||||
```bash
|
||||
emacs --batch --eval "(progn (load-file \"dashboard.el\") (ert \"^test-name$\"))"
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
```bash
|
||||
emacs --batch --eval "(progn (load-file \"dashboard.el\") (ert t))"
|
||||
```
|
||||
|
||||
## Linting Commands
|
||||
|
||||
Emacs has built-in linting tools available:
|
||||
|
||||
```elisp
|
||||
;; Check documentation strings
|
||||
(checkdoc "dashboard.el")
|
||||
|
||||
;; Check for undefined variables (elint)
|
||||
(elint-file "dashboard.el")
|
||||
|
||||
;; Byte-compile to catch errors
|
||||
(byte-compile-file "dashboard.el")
|
||||
```
|
||||
|
||||
Run linting from command line:
|
||||
```bash
|
||||
emacs --batch --eval "(progn (load-file \"dashboard.el\") (checkdoc \"dashboard.el\"))"
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### General Principles
|
||||
- Use CamelCase for function and variable names (Emacs Lisp convention)
|
||||
- Prefix all public functions/variables with a project-specific namespace: `my-dashboard-`
|
||||
- Use kebab-case for keymap names: `my-dashboard-mode-map`
|
||||
|
||||
### Naming Conventions
|
||||
- Functions: `my-dashboard-function-name`
|
||||
- Variables: `my-dashboard-variable-name`
|
||||
- Private functions/variables: prefix with `--` (e.g., `my-dashboard--internal`)
|
||||
- Modes: `my-dashboard-foo-mode`
|
||||
- Keymaps: `my-dashboard-foo-mode-map`
|
||||
- Buffer names: `*Dashboard:Name*`
|
||||
|
||||
### Formatting
|
||||
- Indent using 2 spaces (Emacs default for Elisp)
|
||||
- Maximum line length: 80 characters
|
||||
- Use `setq` for setting variables, `defvar` for defining variables with docstrings
|
||||
- Place docstrings on the line after `defun`/`defvar` opening paren
|
||||
- Use consistent spacing around operators: `(+ x y)` not `(+x y)`
|
||||
|
||||
### Imports and Dependencies
|
||||
- Use `(require 'module)` at the top of files
|
||||
- Common dependencies: `url`, `json`, `tabulated-list`, `org`
|
||||
- Use `use-package` for declarative package management if needed
|
||||
|
||||
### Types
|
||||
- Use `:type` property in `defcustom` for customization types
|
||||
- Use type predicates: `numberp`, `stringp`, `listp`, `functionp`, `bufferp`
|
||||
- Check for nil explicitly: `(when var ...)` not `(if var ...)`
|
||||
- Use `eq` for symbol comparison, `equal` for lists/strings
|
||||
|
||||
### Error Handling
|
||||
- Use `condition-case` for error catching
|
||||
- Check API error status with `plist-get status :error`
|
||||
- Provide meaningful error messages in docstrings
|
||||
- Handle `:null` values from JSON parsing explicitly
|
||||
|
||||
### Keymap Conventions
|
||||
- Use `make-sparse-keymap` for mode keymaps
|
||||
- Set keymap parent to appropriate mode map (e.g., `tabulated-list-mode-map`)
|
||||
- Define keys using `define-key` with key sequences as strings (e.g., "q", "C-x C-f")
|
||||
- Use `setq` with `let` for local keymap creation
|
||||
|
||||
### Mode Definition
|
||||
- Use `define-derived-mode` for major modes
|
||||
- Set `tabulated-list-format` and `tabulated-list-entries`
|
||||
- Call `tabulated-list-init-header` and `tabulated-list-print`
|
||||
- Set `inhibit-read-only` before modifying read-only buffers
|
||||
|
||||
### Async Operations
|
||||
- Use `url-retrieve` for async HTTP requests
|
||||
- Define callback functions with `status` parameter
|
||||
- Check for errors with `plist-get status :error`
|
||||
- Use `goto-char (point-min)` before parsing buffer content
|
||||
- Use `re-search-forward` to skip HTTP headers in response
|
||||
|
||||
### Documentation
|
||||
- Every `defun` and `defvar` should have a docstring
|
||||
- Docstrings start with descriptive verb ("Show", "Fetch", "Copy")
|
||||
- Document interactive commands with `(interactive)` and "Calling from program..."
|
||||
|
||||
### Package Management
|
||||
- Use `defcustom` for user-configurable variables with `:type`, `:group`, `:default`
|
||||
- Use `defvar` for internal variables with docstrings
|
||||
- Prefix custom group with project namespace
|
||||
|
||||
## Project-Specific Patterns
|
||||
|
||||
### Safe List Access
|
||||
```elisp
|
||||
(defun my-dashboard--aget (alist key)
|
||||
"Safely get value from ALIST for KEY, returns nil if null."
|
||||
(let ((val (cdr (assq key alist))))
|
||||
(unless (eq val :null) val)))
|
||||
```
|
||||
|
||||
### Buffer Management
|
||||
```elisp
|
||||
;; Get or create buffer
|
||||
(get-buffer-create "*Buffer-Name*")
|
||||
|
||||
;; Modify buffer safely
|
||||
(with-current-buffer buf
|
||||
...)
|
||||
|
||||
;; For read-only buffers
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
...)
|
||||
```
|
||||
|
||||
### Window Configuration
|
||||
```elisp
|
||||
;; Save/restore window config
|
||||
(setq my-dashboard--window-config (current-window-configuration))
|
||||
(set-window-configuration my-dashboard--window-config)
|
||||
|
||||
;; Split windows
|
||||
(split-window-right) ; vertical split
|
||||
(split-window-below) ; horizontal split
|
||||
(delete-other-windows) ; maximize current window
|
||||
```
|
||||
|
||||
### JSON Parsing
|
||||
```elisp
|
||||
;; Parse JSON with alist object type
|
||||
(let* ((json (json-parse-string (buffer-substring (point) (point-max))
|
||||
:object-type 'alist))
|
||||
(data (cdr (assq 'data json))))
|
||||
...)
|
||||
```
|
||||
|
||||
### Tabulated List Mode
|
||||
```elisp
|
||||
;; Format specification: [("Column" width sort-fn) ...]
|
||||
(setq tabulated-list-format
|
||||
[("Name" 30 t)
|
||||
("ID" 40 t)
|
||||
("Created" 18 t)
|
||||
("Ctx" 8 t :right-align t :pad-right 2)])
|
||||
|
||||
;; Entries: (id [col1 col2 col3] ...)
|
||||
(setq tabulated-list-entries
|
||||
'((1 ["Apple" "Red" "Sweet"])
|
||||
(2 ["Banana" "Yellow" "Sweet"])))
|
||||
```
|
||||
|
||||
### Properize Text with Faces
|
||||
```elisp
|
||||
(propertize text 'font-lock-face face)
|
||||
;; face can be: '(:foreground "green"), 'bold, etc.
|
||||
```
|
||||
399
nano-gpt-status.el
Normal file
399
nano-gpt-status.el
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
;;; nano-gpt-status.el --- Dashboard for Nano-GPT API -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2024 Nano-GPT Contributors
|
||||
|
||||
;; Author: Nano-GPT Contributors
|
||||
;; Maintainer: Nano-GPT Contributors
|
||||
;; URL: https://github.com/nano-gpt/emacs
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "28.1") (qrencode "0.1"))
|
||||
;; Keywords: convenience, api, dashboard
|
||||
|
||||
;; 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; A dashboard for the Nano-GPT API showing models, usage, balance and deposits.
|
||||
|
||||
;; Setup:
|
||||
;; (require 'nano-gpt-status)
|
||||
;; M-x nano-gpt-status
|
||||
;;
|
||||
;; Configure API key in auth-sources:
|
||||
;; (add-to-list 'auth-sources '("nano-gpt.gpg" :host "nano-gpt.com"))
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'url)
|
||||
(require 'json)
|
||||
(require 'auth-source)
|
||||
(require 'org)
|
||||
(require 'tabulated-list)
|
||||
(require 'hl-line)
|
||||
|
||||
(declare-function qrencode-string "qrencode")
|
||||
|
||||
(defgroup nano-gpt nil
|
||||
"Dashboard for Nano-GPT API."
|
||||
:group 'convenience
|
||||
:prefix "nano-gpt-"
|
||||
:link '(url-link :tag "GitHub" "https://github.com/nano-gpt/emacs"))
|
||||
|
||||
(defcustom nano-gpt-api-base-url "https://nano-gpt.com/api"
|
||||
"Base URL for Nano-GPT API."
|
||||
:type 'string
|
||||
:group 'nano-gpt)
|
||||
|
||||
(defcustom nano-gpt-models-endpoint "/v1/models?detailed=true"
|
||||
"Endpoint for fetching models."
|
||||
:type 'string
|
||||
:group 'nano-gpt)
|
||||
|
||||
(defvar nano-gpt--window-config nil
|
||||
"Saved window configuration before dashboard was shown.")
|
||||
|
||||
(defun nano-gpt--get-api-key ()
|
||||
"Get API key from auth-source for nano-gpt.com."
|
||||
(let ((secret (auth-source-search :host "nano-gpt.com" :secret t)))
|
||||
(when secret
|
||||
(funcall (plist-get (car secret) :secret)))))
|
||||
|
||||
;; --- Table 1: Fruits ---
|
||||
|
||||
(defvar nano-gpt-fruits-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(set-keymap-parent map tabulated-list-mode-map)
|
||||
(define-key map "q" #'nano-gpt-quit)
|
||||
map))
|
||||
|
||||
(define-derived-mode nano-gpt-fruits-mode tabulated-list-mode "Fruits"
|
||||
"A table of fruits."
|
||||
(setq tabulated-list-format [("Fruit" 15 t) ("Color" 10 t) ("Taste" 10 t)])
|
||||
(setq tabulated-list-entries
|
||||
'((1 ["Apple" "Red" "Sweet"])
|
||||
(2 ["Banana" "Yellow" "Sweet"])
|
||||
(3 ["Lemon" "Yellow" "Sour"])
|
||||
(4 ["Grape" "Purple" "Sweet"])))
|
||||
(tabulated-list-init-header)
|
||||
(tabulated-list-print))
|
||||
|
||||
;; --- Table 2: Models ---
|
||||
|
||||
(defvar nano-gpt--models-data nil
|
||||
"Cached models data from API.")
|
||||
|
||||
(defun nano-gpt--aget (alist key)
|
||||
"Safely get value from ALIST for KEY, returns nil if null."
|
||||
(let ((val (cdr (assq key alist))))
|
||||
(unless (eq val :null) val)))
|
||||
|
||||
(defun nano-gpt--format-context-length (num)
|
||||
"Format NUM as human-readable context length."
|
||||
(if (numberp num)
|
||||
(cond
|
||||
((>= num 1000000) (format "%.1fM" (/ num 1000000.0)))
|
||||
((>= num 1000) (format "%.0fK" (/ num 1000.0)))
|
||||
(t (number-to-string num)))
|
||||
"N/A"))
|
||||
|
||||
(defun nano-gpt--format-pricing (prompt completion)
|
||||
"Format pricing from PROMPT and COMPLETION values."
|
||||
(if (and (numberp prompt) (numberp completion)
|
||||
(eq prompt 0) (eq completion 0))
|
||||
"Free"
|
||||
(format "$%.2f/$%.2f"
|
||||
(or prompt 0)
|
||||
(or completion 0))))
|
||||
|
||||
(defun nano-gpt--fetch-models-callback (status)
|
||||
"Callback for async model fetching. STATUS is the url-retrieve status."
|
||||
(unless (plist-get status :error)
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$" nil t)
|
||||
(let* ((json (json-parse-string (buffer-substring (point) (point-max)) :object-type 'alist))
|
||||
(models (cdr (assq 'data json))))
|
||||
(setq nano-gpt--models-data models)
|
||||
(when (get-buffer "*Dashboard:Models*")
|
||||
(with-current-buffer "*Dashboard:Models*"
|
||||
(nano-gpt-models-mode)))
|
||||
(message "Models loaded: %d entries" (length models)))))
|
||||
|
||||
(defun nano-gpt--fetch-models-async ()
|
||||
"Fetch models from Nano-GPT API asynchronously."
|
||||
(url-retrieve
|
||||
(concat nano-gpt-api-base-url nano-gpt-models-endpoint)
|
||||
#'nano-gpt--fetch-models-callback
|
||||
nil t t))
|
||||
|
||||
;; --- Usage ---
|
||||
|
||||
(defun nano-gpt--format-usage-time (epoch-ms)
|
||||
"Format EPOCH-MS as human-readable local time."
|
||||
(when epoch-ms
|
||||
(format-time-string "%Y-%m-%d %H:%M" (/ epoch-ms 1000))))
|
||||
|
||||
(defun nano-gpt--fetch-usage-callback (status)
|
||||
"Callback for async usage fetching. STATUS is the url-retrieve status."
|
||||
(unless (plist-get status :error)
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$" nil t)
|
||||
(let* ((json-str (buffer-substring (point) (point-max)))
|
||||
(json (json-parse-string json-str :object-type 'alist))
|
||||
(usage json)
|
||||
(state (nano-gpt--aget usage 'state))
|
||||
(active (nano-gpt--aget usage 'active))
|
||||
(limits (nano-gpt--aget usage 'limits))
|
||||
(weekly (nano-gpt--aget usage 'weeklyInputTokens))
|
||||
(period (nano-gpt--aget usage 'period)))
|
||||
(when (get-buffer "*Dashboard:Info*")
|
||||
(with-current-buffer "*Dashboard:Info*"
|
||||
(let ((inhibit-read-only t))
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^\\*\\* Usage" nil t)
|
||||
(delete-region (match-beginning 0) (progn (forward-line) (point)))
|
||||
(insert "** Usage\n")
|
||||
(insert (format "- State: %s\n" (or state "N/A")))
|
||||
(insert (format "- Active: %s\n\n" (if active "Yes" "No")))
|
||||
(insert "*** Weekly Input Tokens\n")
|
||||
(insert (format "- Used: %s / %s\n"
|
||||
(nano-gpt--aget weekly 'used)
|
||||
(nano-gpt--aget limits 'weeklyInputTokens)))
|
||||
(insert (format "- Remaining: %s\n" (nano-gpt--aget weekly 'remaining)))
|
||||
(insert (format "- Percent: %s%%\n"
|
||||
(let ((pct (nano-gpt--aget weekly 'percentUsed)))
|
||||
(when pct (format "%.2f" (* pct 100))))))
|
||||
(insert (format "- Resets at: %s\n\n"
|
||||
(nano-gpt--format-usage-time (nano-gpt--aget weekly 'resetAt))))
|
||||
(insert "*** Billing Period\n")
|
||||
(insert (format "- Ends: %s\n"
|
||||
(let ((end (nano-gpt--aget period 'currentPeriodEnd)))
|
||||
(when end
|
||||
(format-time-string "%Y-%m-%d %H:%M"
|
||||
(date-to-time end))))))))))
|
||||
(message "Usage loaded"))))
|
||||
|
||||
(defun nano-gpt--fetch-usage-async ()
|
||||
"Fetch usage from Nano-GPT API asynchronously."
|
||||
(let ((api-key (nano-gpt--get-api-key)))
|
||||
(when api-key
|
||||
(let ((url-request-extra-headers (list (cons "Authorization" (format "Bearer %s" api-key)))))
|
||||
(url-retrieve
|
||||
(concat nano-gpt-api-base-url "/subscription/v1/usage")
|
||||
#'nano-gpt--fetch-usage-callback
|
||||
nil nil t)))))
|
||||
|
||||
;; --- Balance ---
|
||||
|
||||
(defun nano-gpt--fetch-balance-callback (status)
|
||||
"Callback for async balance fetching. STATUS is the url-retrieve status."
|
||||
(unless (plist-get status :error)
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$" nil t)
|
||||
(let* ((json (json-parse-string (buffer-substring (point) (point-max)) :object-type 'alist))
|
||||
(usd (nano-gpt--aget json 'usd_balance)))
|
||||
(when (get-buffer "*Dashboard:Info*")
|
||||
(with-current-buffer "*Dashboard:Info*"
|
||||
(let ((inhibit-read-only t))
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^\\*\\* Balance" nil t)
|
||||
(delete-region (match-beginning 0) (progn (forward-line) (point)))
|
||||
(insert "** Balance\n")
|
||||
(insert (format "- USD: $%s\n" (or usd "N/A")))))))
|
||||
(message "Balance loaded"))))
|
||||
|
||||
(defun nano-gpt--fetch-balance-async ()
|
||||
"Fetch balance from Nano-GPT API asynchronously."
|
||||
(let ((api-key (nano-gpt--get-api-key)))
|
||||
(when api-key
|
||||
(let ((url-request-method "POST")
|
||||
(url-request-extra-headers (list (cons "x-api-key" api-key))))
|
||||
(url-retrieve
|
||||
(concat nano-gpt-api-base-url "/check-balance")
|
||||
#'nano-gpt--fetch-balance-callback
|
||||
nil nil t)))))
|
||||
|
||||
;; --- Deposit ---
|
||||
|
||||
(defun nano-gpt--create-deposit-callback (status)
|
||||
"Callback for async deposit creation. STATUS is the url-retrieve status."
|
||||
(unless (plist-get status :error)
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$" nil t)
|
||||
(let* ((json-str (buffer-substring (point) (point-max)))
|
||||
(json (json-parse-string json-str :object-type 'alist))
|
||||
(tx-id (nano-gpt--aget json 'txId))
|
||||
(amount (nano-gpt--aget json 'amount))
|
||||
(address (nano-gpt--aget json 'address)))
|
||||
(when (get-buffer "*Dashboard:Info*")
|
||||
(with-current-buffer "*Dashboard:Info*"
|
||||
(let ((inhibit-read-only t))
|
||||
(goto-char (point-max))
|
||||
(insert (format "\n*** Deposit Created\n"))
|
||||
(insert (format "- txId: %s\n" (or tx-id "N/A")))
|
||||
(insert (format "- Amount: %s BTC\n" (or amount "N/A")))
|
||||
(when address
|
||||
(insert (format "- Invoice (raw):\n%s\n" address))
|
||||
(insert (format "- Invoice (BOLT-11):\n"))
|
||||
(insert (qrencode-string address))
|
||||
(insert "\n"))
|
||||
(insert "\n"))
|
||||
(goto-char (point-min)))
|
||||
(message "Deposit created: %s" tx-id)))))
|
||||
|
||||
(defun nano-gpt--create-deposit-async (amount)
|
||||
"Create BTC-LN deposit for AMOUNT."
|
||||
(let ((api-key (nano-gpt--get-api-key)))
|
||||
(when api-key
|
||||
(let ((url-request-method "POST")
|
||||
(url-request-extra-headers (list (cons "Authorization" (format "Bearer %s" api-key))
|
||||
(cons "Content-Type" "application/json")))
|
||||
(url-request-data (json-encode (list (cons 'amount amount)))))
|
||||
(url-retrieve
|
||||
(concat nano-gpt-api-base-url "/transaction/create/btc-ln")
|
||||
#'nano-gpt--create-deposit-callback
|
||||
nil nil t)))))
|
||||
|
||||
(defun nano-gpt--deposit-button-action (_)
|
||||
"Action for deposit button."
|
||||
(let* ((satoshis (read-number "Enter amount in satoshis: "))
|
||||
(amount (/ satoshis 100000000.0)))
|
||||
(message "Creating BTC-LN deposit for %s satoshis (%s BTC)..." satoshis amount)
|
||||
(nano-gpt--create-deposit-async amount)))
|
||||
|
||||
(defun nano-gpt--insert-deposit-button ()
|
||||
"Insert a deposit button in the current buffer."
|
||||
(insert-text-button "[Deposit BTC-LN]"
|
||||
'action (lambda (_) (nano-gpt--deposit-button-action 0.00001))
|
||||
'follow-link t))
|
||||
|
||||
;; --- Models Mode ---
|
||||
|
||||
(defvar nano-gpt-models-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(set-keymap-parent map tabulated-list-mode-map)
|
||||
(define-key map "q" #'nano-gpt-quit)
|
||||
(define-key map "w" #'nano-gpt-models-copy-id)
|
||||
map))
|
||||
|
||||
(defun nano-gpt-models-copy-id ()
|
||||
"Copy the ID of the current model to clipboard."
|
||||
(interactive)
|
||||
(when tabulated-list-entries
|
||||
(let* ((entry (tabulated-list-get-entry))
|
||||
(id (aref entry 1)))
|
||||
(kill-new id)
|
||||
(message "Copied: %s" id))))
|
||||
|
||||
(define-derived-mode nano-gpt-models-mode tabulated-list-mode "Models"
|
||||
"A table of Nano-GPT models."
|
||||
(hl-line-mode 1)
|
||||
(setq tabulated-list-format
|
||||
[("Name" 30 t)
|
||||
("ID" 40 t)
|
||||
("Created" 10 t)
|
||||
("Ctx" 6 t :right-align t :pad-right 3)
|
||||
("Pricing" 18 t)])
|
||||
(setq tabulated-list-sort-key (cons "Created" t))
|
||||
(setq tabulated-list-entries
|
||||
(mapcar
|
||||
(lambda (model)
|
||||
(let* ((pricing (nano-gpt--aget model 'pricing))
|
||||
(subscription (nano-gpt--aget model 'subscription))
|
||||
(has-sub (eq (nano-gpt--aget subscription 'included) t))
|
||||
(face (if has-sub '(:foreground "green") nil)))
|
||||
(list (nano-gpt--aget model 'id)
|
||||
(vector
|
||||
(propertize (or (nano-gpt--aget model 'name) "N/A") 'font-lock-face face)
|
||||
(propertize (or (nano-gpt--aget model 'id) "N/A") 'font-lock-face face)
|
||||
(propertize (format-time-string "%Y-%m-%d" (nano-gpt--aget model 'created)) 'font-lock-face face)
|
||||
(propertize (nano-gpt--format-context-length (nano-gpt--aget model 'context_length)) 'font-lock-face face)
|
||||
(propertize (nano-gpt--format-pricing
|
||||
(nano-gpt--aget pricing 'prompt)
|
||||
(nano-gpt--aget pricing 'completion)) 'font-lock-face face)))))
|
||||
nano-gpt--models-data))
|
||||
(tabulated-list-init-header)
|
||||
(tabulated-list-print))
|
||||
|
||||
;; --- Quit function ---
|
||||
|
||||
(defun nano-gpt-quit ()
|
||||
"Quit the dashboard, restore previous window configuration, and kill buffers."
|
||||
(interactive)
|
||||
(dolist (buf (list (get-buffer "*Dashboard:Fruits*")
|
||||
(get-buffer "*Dashboard:Models*")
|
||||
(get-buffer "*Dashboard:Info*")))
|
||||
(when buf (kill-buffer buf)))
|
||||
(setq nano-gpt--models-data nil)
|
||||
(when nano-gpt--window-config
|
||||
(set-window-configuration nano-gpt--window-config)
|
||||
(setq nano-gpt--window-config nil)))
|
||||
|
||||
;; --- Main entry point ---
|
||||
|
||||
;;;###autoload
|
||||
(defun nano-gpt-status ()
|
||||
"Show a simple dashboard with two tables and an org buffer."
|
||||
(interactive)
|
||||
(setq nano-gpt--window-config (current-window-configuration))
|
||||
|
||||
(let ((fruits-buf (get-buffer-create "*Dashboard:Fruits*"))
|
||||
(models-buf (get-buffer-create "*Dashboard:Models*"))
|
||||
(org-buf (get-buffer-create "*Dashboard:Info*")))
|
||||
|
||||
(with-current-buffer fruits-buf
|
||||
(nano-gpt-fruits-mode))
|
||||
|
||||
(with-current-buffer models-buf
|
||||
(nano-gpt-models-mode))
|
||||
|
||||
(with-current-buffer org-buf
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(org-mode)
|
||||
(org-indent-mode 1)
|
||||
(insert "* Dashboard\n\n")
|
||||
(insert "Welcome to the *dashboard*!\n\n")
|
||||
(insert "** Notes\n")
|
||||
(insert "- Press ~q~ in any table to quit\n")
|
||||
(insert "- Table 1 :: Fruits of the world\n")
|
||||
(insert "- Table 2 :: Nano-GPT Models (loading...)\n\n")
|
||||
(insert "** Usage\n\n")
|
||||
(insert "** Balance\n\n")
|
||||
(insert "** Deposit (BTC-LN)\n")
|
||||
(nano-gpt--insert-deposit-button)
|
||||
(insert "\n"))
|
||||
(goto-char (point-min))
|
||||
(use-local-map (let ((map (copy-keymap org-mode-map)))
|
||||
(define-key map "q" #'nano-gpt-quit)
|
||||
map)))
|
||||
|
||||
(delete-other-windows)
|
||||
(let* ((top-left (selected-window))
|
||||
(top-right (split-window-right))
|
||||
(bottom (split-window-below (/ (window-height) 2))))
|
||||
(set-window-buffer top-left fruits-buf)
|
||||
(set-window-buffer top-right models-buf)
|
||||
(set-window-buffer bottom org-buf))
|
||||
|
||||
(select-window (get-buffer-window fruits-buf)))
|
||||
|
||||
(nano-gpt--fetch-models-async)
|
||||
(nano-gpt--fetch-usage-async)
|
||||
(nano-gpt--fetch-balance-async))
|
||||
|
||||
(provide 'nano-gpt-status)
|
||||
|
||||
;;; nano-gpt-status.el ends here
|
||||
Loading…
Add table
Add a link
Reference in a new issue