14. Timestamp Files
In our ongoing development of the package, an essential feature we need is the capability to navigate the prompt history. Specifically, we want to enable users to retrieve previous prompts sent to OpenAI by using keyboard shortcuts like M-p for previous prompts and M-n for the next. In the upcoming lessons, we will implement this feature, starting with our discussion on using timestamp files.
Purpose of Timestamp Files¶
To facilitate prompt history navigation, we must organize previous prompts chronologically. For this, we will utilize timestamp files, which we will elaborate on shortly. First, let's explore the reason for this approach.
Examining the JSON response in the response.json file located at /home/tony/chatgpt-emacs/requests/zsewR8/, we note that it contains a created field with a timestamp:
{
"id": "chatcmpl-B8UAwZapHTpsUexLppYeadC0sRG2v",
"object": "chat.completion",
"created": 1741362318,
"model": "gpt-4o-2024-08-06",
"choices": [...],
...
}
While we could theoretically use this timestamp to sort requests, this method would result in reading numerous files if the request directory contains hundreds or thousands of them, leading to considerable inefficiency. Moreover, requests that lead to API or processing errors do not generate a response.json file, further complicating sorting.
Instead, we will create a timestamp file for each request sent to OpenAI. This file will have its timestamp embedded in the filename as follows:
This approach allows us to sort the requests in the chatgpt-dir directory efficiently using the directory-files-recursively function, without needing to read individual files. We will still need to read each request file to retrieve the prompts.
Writing Timestamp Files¶
We modify the chatgpt-send-request function to create a timestamp file alongside each request sent to OpenAI. We define the path for the timestamp file using format and time-to-seconds, which gives the current time as a float representing seconds since the epoch. We then create the file with the write-region function:
(defun chatgpt-send-request (prompt)
"Send the request with PROMPT to OpenAI."
(let* (...
(timestamp-path
(format "%stimestamp-%s" req-dir (time-to-seconds)))
(command (chatgpt-command req-path)))
(message "chatgpt: %s" req-dir)
(write-region (chatgpt-json-encode req) nil req-path)
(write-region "" nil timestamp-path)
(make-process ...)))
By invoking chatgpt-send-request in the prompt buffer with the input "Hello!", a timestamp file is created alongside the request.json and response.json files:
/home/tony/chatgpt-emacs/requests/JVjcXV:
drwx------ 2 tony tony 4.0K Mar 10 10:50 .
drwxrwxr-x 5 tony tony 4.0K Mar 10 10:50 ..
-rw-rw-r-- 1 tony tony 101 Mar 10 10:50 request.json
-rw-rw-r-- 1 tony tony 807 Mar 10 10:50 response.json
-rw-rw-r-- 1 tony tony 0 Mar 10 10:50 timestamp-1741600223.2159884
Calling chatgpt-send in the prompt buffer, we send the request with the prompt "Hello!" to OpenAI and a timestamp file is created alongside the request.json and response.json files:
/home/tony/chatgpt-emacs/requests/JVjcXV:
drwx------ 2 tony tony 4.0K Mar 10 10:50 .
drwxrwxr-x 5 tony tony 4.0K Mar 10 10:50 ..
-rw-rw-r-- 1 tony tony 101 Mar 10 10:50 request.json
-rw-rw-r-- 1 tony tony 807 Mar 10 10:50 response.json
-rw-rw-r-- 1 tony tony 0 Mar 10 10:50 timestamp-1741600223.2159884
Defining the chatgpt-timestamp Function¶
Next, we implement the chatgpt-timestamp function, which retrieves the timestamp number from a specified timestamp file. This will assist in organizing prompts chronologically.
(defun chatgpt-timestamp (file)
"Return the timestamp number from FILE."
(string-to-number (nth 1 (string-split file "timestamp-"))))
For example: \begingroup \hyphenpenalty=10000 \exhyphenpenalty=10000
(let ((file "/home/tony/chatgpt-emacs/requests/JVjcXV/timestamp-1741600223.2159884"))
(chatgpt-timestamp file))
;; => 1741600223.2159884
For Emacs versions prior to 29.1, utilize split-string in place of string-split.
Defining the chatgpt-requests Function¶
Finally, we create the chatgpt-requests function, which returns a sorted list of requests in the chatgpt-dir directory, prioritizing the most recent entries.
First, we list the timestamp files with directory-files-recursively:
(directory-files-recursively chatgpt-dir "timestamp.*")
;; ("/home/tony/chatgpt-emacs/requests/JVjcXV/timestamp-1741600223.2159884"
;; "/home/tony/chatgpt-emacs/requests/wVcThg/timestamp-1741610135.553578"
;; "/home/tony/chatgpt-emacs/requests/wavggx/timestamp-1741610117.0591946")
Next, we sort these files using seq-sort in conjunction with our chatgpt-timestamp function, ensuring the most recent timestamps come first:
(let ((files (directory-files-recursively chatgpt-dir "timestamp.*")))
(seq-sort
(lambda (f1 f2) (> (chatgpt-timestamp f1) (chatgpt-timestamp f2)))
files))
;; ("/home/tony/chatgpt-emacs/requests/wVcThg/timestamp-1741610135.553578"
;; "/home/tony/chatgpt-emacs/requests/wavggx/timestamp-1741610117.0591946"
;; "/home/tony/chatgpt-emacs/requests/JVjcXV/timestamp-1741600223.2159884")
After that, we extract the request directories by trimming the file paths:
(let ((files (directory-files-recursively chatgpt-dir "timestamp.*")))
(mapcar (lambda (f) (string-trim-right f "timestamp.*"))
(seq-sort
(lambda (f1 f2) (> (chatgpt-timestamp f1) (chatgpt-timestamp f2)))
files)))
;; ("/home/tony/chatgpt-emacs/requests/wVcThg/"
;; "/home/tony/chatgpt-emacs/requests/wavggx/"
;; "/home/tony/chatgpt-emacs/requests/JVjcXV/")
We can then consolidate the above logic into the chatgpt-requests function:
(defun chatgpt-requests ()
"Return a sorted list of the requests in `chatgpt-dir'.
The most recent requests are listed first."
(let ((files (directory-files-recursively chatgpt-dir "timestamp.*")))
(mapcar (lambda (f) (string-trim-right f "timestamp.*"))
(seq-sort
(lambda (f1 f2)
(> (chatgpt-timestamp f1) (chatgpt-timestamp f2)))
files))))
We can test the chatgpt-requests function like so: