扩展 org-roam-capture 模板支持ChatGPT
文章目录
【注意】最后更新于 May 20, 2023,文中内容可能已过时,请谨慎使用。
通过扩展org-roam-capture 模板完善 emacs 工作流:
收集灵感 –> 配置 chatGPT prompt –> 发送到 ChatGPT –> 获得建议和处理内容 –> 根据反馈,分析灵感内容,是否进行下一步安排。
本文主要包括以下功能:
- 定制 AI capture 模板,支持角色属性和 AI 模板标签,添加 AI 分组。
- 利用 org-capture-template 的特性动态加载预设的 ChatGPT prompt 模板。
- 添加 hook 函数,在保存 capture 之后,和 ChatGPT 交互,并将处理的内容追加到结果中。
先定义一个 capture 模板
|
|
- 动态添加 role prompt : 使用
%(方法)
从 json 中获取 prompt 添加到 entry 中。 :ai t
: 设定为 ai 模板,主要在 hook 方法中使用,可以针对这类模板,做特殊处理。:role "写作"
: 添加 prompt 角色属性,主要在动态添加 prompt 时,用来匹配 json 数据。以下是一个示例 elisp 函数,可以使用它向 org-capture-templates 列表中添加多个模板:
开启 AI 的模板分组,在使用 capture 便于管理
|
|
从加载 json 中预设的 prompt
|
|
- 解析 json 文件中的对象列表
- 匹配
:role
指定的角色,返回 角色的 prompt 。
问题:这种方式无法在 capture 过程中使用 ^%{主题}
占位符模板,职能使用确定的 prompt 模板内容。
解决办法文件模板设置 capture 模板,这样需要为每个 AI 模板编写文件模板。
下面举例说明:
先设置 AI capture 模板,这样就不需要 :role "写作"
属性
|
|
写作助手.org
文件模板样例:
|
|
借助 gptel 和 openAI 通信
在 capture 完成时,执行 AI 方法
自定义 hook 方法:获取 capture 相关属性 hook 方法对所有 capture 模板都生效,即当 caputre 完成时,都会执行该方法,需要在 hook 方法中添加过滤条件,实现自定义需求。之前已经在 capture 模板自定义属性:
:ai t
, 当ai
属性为 t 时,执行下面的操作。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
(defun gptel-capture-hook () (if org-note-abort (message "带有键 %s 和描述 %s 的模板终止" key desc) ;;调用 gptel 发送给 AI (if (org-capture-get :ai) (let ((key (plist-get org-capture-plist :key)) (desc (plist-get org-capture-plist :description)) (org-str (plist-get org-capture-plist :template)) (role (plist-get org-capture-plist :role)) ) ;; 通过解析字符串获取用户模板内容 ;; (setq content (content-from-org-element org-str)) ;; 从json文件中获取system prompt ;; (setq system (add-system-prompt-from-json)) ;; 第一步获取 capture 字符串中的AI tag ;; (setq aitag (get-element-tag-by-ql org-str "AI")) ;; 在capture 时,竟然拿不到 tag 值 (setq aitag role) ;; 第二步获取 使用org-ql 从 org 文件中获取system ;; aitag 作为 title 获取 prompt (setq promptfile "~/hsg/iNotes/content-org/prompt/prompt.org") (setq system (get-element-prompt-by-ql promptfile aitag 1)) (setq user (get-element-prompt-by-ql promptfile aitag 0)) (message "结果=-%s \n -%s" system user) (gptel-capture-request system org-str) ) ))) (defun get-element-tag-by-ql (org-str tagregex) (let (result) (with-current-buffer (get-buffer-create "*gpteltagtemp*") (erase-buffer) (insert org-str) (org-mode) (setq tags (org-ql-query :select '(org-entry-get (point) "TAGS") :from (current-buffer) :where '(and (tags* (format "^%s.*$" tagregex)) (not (parent)) ) )) (message "数组:%s" tags) (setq result (catch 'tag-found (dolist (tag tags) (setq tag (substring tag 1 -1)) ; 去除首尾: (setq tagarr (split-string tag ":")) (dolist (item tagarr) (message "匹配中:%s" item) (setq reg (format "^%s.*$" tagregex)) (message "正则:%s" reg) (when (string-match reg item) (setq item (substring item 2)) ;;去除AI前缀 (throw 'tag-found item))) ))) (message "结果:%s" result) ))) ;; ptype 1 :system 其他:user (defun get-element-prompt-by-ql (file title ptype) (let (prompt) (if (= ptype 1) (setq prompt (org-ql-query :select '(org-get-entry) :from file :where '(and (ancestors (heading title)) (heading "system") ))) (setq prompt (org-ql-select file '(and (ancestors (heading title)) (heading "user") ) :action '(org-get-entry) )) ) )) ;; 解析org-str,获取指定部分内容 (defun content-from-org-element (org-str &optional headline) (with-temp-buffer (erase-buffer) (insert org-str) (org-mode) (setq content "") (org-element-map (org-element-parse-buffer) 'paragraph (lambda (x) ;;把获取到的段落,格式为text (setq item (org-element-interpret-data x)) ;;拼接循环到的每个段落,用\n分割 (setq content (format "%s\n%s" content item)) )) content))
监听 hook 方法:
org-capture-after-finalize-hook
1
(add-hook 'org-capture-after-finalize-hook 'gptel-capture-hook)
在包 AI cupture 事项保存到文件中之后,将自动调用
gptel-capture-hook
函数。参考:
Template elements (The Org Manual)
org mode - creating an org-capture template - Emacs Stack Exchange
- 通过 gptel 和 openAI 交互参考:GitHub - karthink/gptel: A no-frills ChatGPT client for Emacs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
(defun gptel-capture-request (sytem prompt) (when (string= prompt "") (user-error "A prompt is required.")) (message "开始网络 %s \n %s" system prompt) (setq gptel--system-message sytem) (gptel-request prompt :callback (lambda (response info) (if (not response) (message "gptel-capture 错误: %s" (plist-get info :status)) (with-current-buffer (plist-get org-capture-plist :buffer) (goto-char (point-max)) (unless (bolp) (insert "\n")) (insert "** 测试 \n" response "\n") ) ))))
文章作者 iTBoyer
上次更新 2023-05-20