如何实现一个不断接受其他进程输入内容的只读缓冲区, 不干扰当前光标的正常操作

大家好, 在 @carlos-wong 的启发下, 改写了一个chatgpt.el的插件, GitHub - gfgkmn/ChatGPT.el: ChatGPT in Emacs 实现了一些流式输入的功能, 和对日志的保存, 本来只是自己用, 所以没有考虑和他的代码的一致性和对齐的问题. 先感谢并致歉.

现在功能算是还不错, 平时用起来也非常顺手,

但是有一个问题, 目前这个库支持流式输出(carlos-wrong的想法)和并行的一次性输入(最初原作者的实现), 我基本都喜欢流式输出, 有时不需要一次回复完成, 就可以进入下一步操作了.

但是流式输出有一个问题, 就是它会占用光标, 在模型输出的时候, 我就算看到答案已经出来了, 不需要再等了, 也必须等gpt buffer的光标出完, 才能到主buffer中进行处理.

有没有什么办法, 可以让这两个进程完全并行化, 就是一边流式的在gpt buffer产生内容, 一边在主buffer中, 我仍然可以控制光标进行各种操作.

下面是相关的函数.

(defun chatgpt--query-stream (query use-model &optional recursive)
  (unless chatgpt-process
    (chatgpt-init))
  (chatgpt-display)
  (lexical-let ((saved-id (if recursive
                              chatgpt-id
                            (cl-incf chatgpt-id)))
                (query (if recursive
                           (string-join (nthcdr 1 (split-string query "-")) "-")
                         query))
                (query_with_id (if recursive
                                   query
                                 (format "%s-%s" (org-id-uuid) query)))
                (recursive-model use-model))

    (if recursive
        (setq next-recursive recursive)
      (progn
        (setq next-recursive nil)
        (chatgpt--insert-query query saved-id)))

    (deferred:$
      (deferred:$
        (epc:call-deferred chatgpt-process 'querystream (list query_with_id recursive-model))
        (deferred:nextc it
          #'(lambda (response)
              (with-current-buffer (chatgpt-get-output-buffer-name)
                (save-excursion
                  (if (numberp next-recursive)
                      (goto-char next-recursive)
                    (chatgpt--goto-identifier chatgpt-id))
                  (if (and (stringp response))
                      (progn
                        (insert response)
                        (chatgpt--query-stream query_with_id recursive-model (point)))
                    (progn
                      (insert (format "\n\n%s"
                                      (make-string (chatgpt-get-buffer-width-by-char ?=) ?=))))))
                (let ((output-window (get-buffer-window (current-buffer))))
                  (when output-window
                    (with-selected-window output-window
                      (goto-char (point-max))
                      (recenter -1))))))))
      (deferred:error it
        `(lambda (err)
           (message "err is:%s" err))))))

欢迎各位大佬不吝赐教

telega 的聊天界面就可以一边回复一边显示新消息,所以应该是可以做到的。不过我不知道怎么整🙈

1 个赞

感谢这个信息, 我去看看去

本站中的bing Chat插件可能实现了你想要的功能,我一直在使用的,体验很不错,但是对于OpenAI 的流式支持存在问题。加油,你描述的功能,我也很需要,期待。

就是用 with-current-buffersave-mark-and-excursion 实现的,看你上面的代码也是这样,是不是你的 epc 不是异步的

(with-current-buffer buffer 
  (save-mark-and-excursion
  ...
 ))
1 个赞

aichat-openai-chat-demo 就是流式返回数据。我平时不用 OpenAI ,所以关于 OpenAI 的功能没有实现,只有两个 demo ,展示 API 的使用。

1 个赞

我现在猜测可能是递归的问题, chatgpt–query-stream这个函数里调用了它自己, 而且这个递归调用发生在save_excursion块内, 可能是因为这个原因, 这个save块一直没有返回, 所以光标就一直被这个堆栈给占了.

试试用 (run-at-time 0 ...) 调用 , 这样应该能让 save 块结束后再调用 chatgpt-query-stream

1 个赞

好, 我试一试. 非常感谢

搞定了, 改动很小, 最主要的就是把递归调用从这两行中拿出来就可以了. (with-current-buffer (chatgpt-get-output-buffer-name) (save-excursion 哎, 前一段时间没有整这个东西. 非常感谢@xhcoding.