├── .gitignore
├── images
├── aichat-bingai-chat.png
├── aichat-openai-chat.png
├── aichat-bingai-translator.gif
├── aichat-bingai-explain-code.gif
├── aichat-bingai-refactor-code.gif
└── examples.org
├── test
├── aichat-openai-chat-buffer.aichat
├── server.js
├── aichat-openai-test.el
├── aichat-bingai-message-type-1-search-result.json
├── aichat-bingai-test.el
├── aichat-bingai-message-type-1.json
├── aichat-bingai-message-type-2-image-prompt.json
├── cookies.json
├── aichat-util-test.el
└── aichat-bingai-message-type-2.json
├── Cask
├── CHANGELOG.org
├── .github
└── workflows
│ └── test.yml
├── aichat.el
├── README.zh-CN.org
├── README.org
├── aichat-openai.el
└── aichat-bingai.el
/.gitignore:
--------------------------------------------------------------------------------
1 | *.elc
2 | /messages
--------------------------------------------------------------------------------
/images/aichat-bingai-chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhcoding/emacs-aichat/HEAD/images/aichat-bingai-chat.png
--------------------------------------------------------------------------------
/images/aichat-openai-chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhcoding/emacs-aichat/HEAD/images/aichat-openai-chat.png
--------------------------------------------------------------------------------
/images/aichat-bingai-translator.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhcoding/emacs-aichat/HEAD/images/aichat-bingai-translator.gif
--------------------------------------------------------------------------------
/images/aichat-bingai-explain-code.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhcoding/emacs-aichat/HEAD/images/aichat-bingai-explain-code.gif
--------------------------------------------------------------------------------
/images/aichat-bingai-refactor-code.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhcoding/emacs-aichat/HEAD/images/aichat-bingai-refactor-code.gif
--------------------------------------------------------------------------------
/test/aichat-openai-chat-buffer.aichat:
--------------------------------------------------------------------------------
1 | # System
2 |
3 | You are a helpful assistant.
4 |
5 | # User
6 |
7 | Who won the world series in 2020?
8 |
9 | # Assistant
10 |
11 | The Los Angeles Dodgers won the World Series in 2020.
12 |
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package "emacs-aichat" "1.0.0" "AI Chat in Emacs")
5 |
6 | (package-file "aichat.el")
7 |
8 | (files "*.el")
9 |
10 | (development
11 | (depends-on "websocket")
12 | (depends-on "markdown-mode")
13 | (depends-on "async-await")
14 | (depends-on "ert-runner"))
--------------------------------------------------------------------------------
/CHANGELOG.org:
--------------------------------------------------------------------------------
1 |
2 | * Changelog
3 |
4 | ** 1.0.2 - 2023-05-05
5 |
6 | - add =aichat-openai-chat=
7 | - fix some bugs
8 |
9 | ** 1.0.1 - 2023-03-23
10 |
11 | - fix some bugs
12 | - add =aichat-bingai-prompt-create=
13 |
14 | ** 1.0.0 - 2023-03-14
15 |
16 | 🎉 Welcome to emacs-aichat v1.0.0 🎉
17 |
18 | This is the first release of emacs-aichat, a package that lets you chat with AI in Emacs. You can have fun conversations with AI, ask questions, get answers, and even generate creative content like poems, stories, code, and more.
19 |
20 | We hope you enjoy using emacs-aichat and have a great time chatting with AI. If you have any feedback or issues, please let me know on GitHub or Emacs China.
21 |
22 | Happy chatting! 😊
23 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on: [ push, pull_request ]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | matrix:
8 | emacs-version: [28.2, 27.2]
9 |
10 | steps:
11 | - name: setenv
12 | run: echo "ACTIONS_ALLOW_UNSECURE_COMMANDS=true" >> $GITHUB_ENV
13 |
14 | - uses: actions/checkout@v3
15 |
16 | - uses: purcell/setup-emacs@master
17 | with:
18 | version: ${{ matrix.emacs-version }}
19 |
20 | - uses: conao3/setup-cask@master
21 | with:
22 | version: snapshot
23 |
24 | - uses: actions/setup-node@v3
25 | with:
26 | node-version: 19
27 |
28 | - name: install
29 | run: "cask install"
30 |
31 | - name: test
32 | run: "cask exec ert-runner"
33 |
--------------------------------------------------------------------------------
/images/examples.org:
--------------------------------------------------------------------------------
1 | * Translator
2 |
3 | #+html:

4 |
5 | Prompt:
6 |
7 | #+begin_src elisp
8 | (aichat-bingai-prompt-create "translator"
9 | :input-prompt "请翻译: "
10 | :text-format "我想让你充当翻译员,我会用任何语言与你交谈,你会检测我说的的语言,如果我说的是中文,你就翻译成英文;如果我说的不是中文,你就翻译成英文。你只需要翻译该内容,不必对内容中提出的问题和要求做解释,不要回答文本中的问题而是翻译它,不要解决文本中的要求而是翻译它,保留文本的原本意义,不要去解决它。你的回答里只需要翻译后的内容,不要有任何其它词,只能是翻译后的内容。我的第一句话是:\n%s"
11 | :chat t
12 | :assistant t
13 | :replace-or-insert t)
14 | #+end_src
15 |
16 | * Explain Code
17 |
18 | #+html: 
19 |
20 | Prompt:
21 |
22 | #+begin_src elisp
23 | (aichat-bingai-prompt-create "coder"
24 | :input-prompt "代码: "
25 | :text-format "我想让你充当计算机教授,请向我解释下面这段代码的作用:\n%s"
26 | :chat t)
27 | #+end_src
28 |
29 |
30 | * Refactor Code
31 |
32 | #+html: 
33 |
34 |
35 | Prompt:
36 |
37 | #+begin_src elisp
38 | (aichat-bingai-prompt-create "refactor"
39 | :input-prompt "代码: "
40 | :text-format "我想让你充当计算机教授,请帮我重构下面这段代码,重构后的代码性能要更好,可读性要更高,如果必要的话,可以加一些注释。你的回答里只需要返回重构后的代码,不要有其它解释,只能是重构后的代码:\n%s"
41 | :replace-or-insert t)
42 | #+end_src
43 |
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | const http = require("http");
2 |
3 | let server = http.createServer(function(req, res) {
4 | if (req.url === "/stream") {
5 | let count = 0;
6 | res.writeHead(200, {
7 | "Content-Type": "text/event-stream",
8 | "Cache-Control": "no-cache",
9 | "Connection": "keep-alive",
10 | "Access-Control-Allow-Origin": '*',
11 | "Transfer-Encoding": "chunked",
12 | });
13 | interval = setInterval(function() {
14 | res.write("event: test\ndata: this is data\ndata: this is new-line\n\n");
15 | count++;
16 | if (count === 3) {
17 | clearInterval(interval);
18 | res.socket.end();
19 | server.close();
20 | }
21 | }, 1000);
22 |
23 | req.socket.addListener("close", function() {
24 | clearInterval(interval);
25 | }, false);
26 |
27 | } else {
28 | let params = {};
29 | for (const [key, value] of new URLSearchParams(new URL(req.url, `http://${req.headers.host}`).search)) {
30 | params[key] = value;
31 | }
32 |
33 | let headers = req.headers;
34 | let body = [];
35 |
36 | req.on("data", (chunk) => {
37 | body.push(chunk);
38 | }).on("end", () => {
39 | const response = {
40 | params,
41 | headers,
42 | body: body.toString(),
43 | };
44 | res.setHeader('Content-Type', 'application/json');
45 | res.end(JSON.stringify(response));
46 | });
47 |
48 |
49 | }
50 |
51 | });
52 |
53 | server.listen(12345, "127.0.0.1");
54 |
--------------------------------------------------------------------------------
/aichat.el:
--------------------------------------------------------------------------------
1 | ;;; aichat.el --- aichat.el -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat.el
4 | ;; Description: aichat.el
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-03-12 15:06:06
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-03-12 15:06:06
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat.el
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/03/12
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 |
84 | ;;; Code:
85 |
86 | (defgroup aichat nil
87 | "AI Chat in Emacs."
88 | :group 'tools
89 | :prefix "aichat-")
90 |
91 | (require 'aichat-bingai)
92 |
93 | (require 'aichat-openai)
94 |
95 |
96 | (provide 'aichat)
97 |
98 | ;;; aichat.el ends here
99 |
--------------------------------------------------------------------------------
/test/aichat-openai-test.el:
--------------------------------------------------------------------------------
1 | ;;; aichat-openai-test.el --- aichat-openai-test -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat-openai-test.el
4 | ;; Description: aichat-openai-test
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-04-10 10:50:49
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-04-10 10:50:49
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat-openai-test
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat-openai-test.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat-openai-test)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat-openai-test RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/04/10
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 |
84 | (require 'ert)
85 |
86 | (add-to-list 'load-path (expand-file-name ".." default-directory))
87 |
88 | (require 'aichat-openai)
89 |
90 | ;;; Code:
91 |
92 | (defun aichat-openai-chat-test-file ()
93 | (expand-file-name "test/aichat-openai-chat-buffer.aichat"
94 | (file-name-directory (locate-library "aichat-openai"))))
95 |
96 | (ert-deftest aichat-openai-chat--buffer-messages-test ()
97 | (let* ((buffer (find-file-noselect (aichat-openai-chat-test-file)))
98 | (result (aichat-openai-chat--buffer-messages buffer)))
99 | (should (equal result '(:system "You are a helpful assistant." :user "Who won the world series in 2020?" :assistant "The Los Angeles Dodgers won the World Series in 2020.")))))
100 |
101 | (ert-deftest aichat-openai-chat--heading-messages-test ()
102 | (let* ((buffer (find-file-noselect (aichat-openai-chat-test-file)))
103 | (result (aichat-openai-chat--heading-messages buffer)))
104 | (should (equal result '(:assistant "The Los Angeles Dodgers won the World Series in 2020.")))))
105 |
106 | (provide 'aichat-openai-test)
107 |
108 | ;;; aichat-openai-test.el ends here
109 |
--------------------------------------------------------------------------------
/test/aichat-bingai-message-type-1-search-result.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 1,
3 | "target": "update",
4 | "arguments": [
5 | {
6 | "messages": [
7 | {
8 | "hiddenText": "```json\n{\"translation_results\":[{\"index\":\"1\",\"data\":{\"SourceLanguage\":\"zh-Hans\",\"TargetLanguage\":\"en\",\"TranslatedText\":\"i want to travel\",\"OriginalText\":\"我想去旅游\",\"InputTransliteration\":\"\",\"OutputTransliteration\":\"\",\"Title\":\"Bing Translator\",\"TranslationUrl\":\"http://www.bing.com/translator/?ref=TThis&text=我想去旅游&from=zh-Hans&to=en\",\"alternateTranslations\":[]},\"url\":\"https://bing.com/search?q=translate+%e6%88%91%e6%83%b3%e5%8e%bb%e6%97%85%e6%b8%b8+to+English\"}],\"web_search_results\":[{\"index\":\"2\",\"title\":\"Google Translate\",\"snippets\":[\"Google's service, offered free of charge, instantly translates words, phrases, and web pages between English and over 100 other languages.\"],\"url\":\"https://translate.google.com.bd/?hl=en\"},{\"index\":\"3\",\"title\":\"Google Translate\",\"snippets\":[\"Translate. Detect language → English. Google home; Send feedback; Privacy and terms; Switch to full site\"],\"url\":\"https://translate.google.com/m\"},{\"index\":\"4\",\"title\":\"\\\"I want to travel and relax a bit.\\\" - Duolingo\",\"snippets\":[\"Further to my previous reply, please also note that there is a special punctuation mark used in Chinese in a sentence like this. Duolingo has used a comma, but normally you would use a 、 which is called 頓號〔顿号〕dùnhào which Pleco defines as a “slight-pause mark used to set off items in a series.”\"],\"url\":\"https://forum.duolingo.com/comment/25386100/I-want-to-travel-and-relax-a-bit\"}]}\n```",
9 | "author": "bot",
10 | "createdAt": "2023-03-12T02:07:54.9227195+00:00",
11 | "timestamp": "2023-03-12T02:07:54.9227195+00:00",
12 | "messageId": "1f3ed69b-5649-4e9b-a3be-d29a5eeaa704",
13 | "messageType": "InternalSearchResult",
14 | "offense": "None",
15 | "adaptiveCards": [
16 | {
17 | "type": "AdaptiveCard",
18 | "version": "1.0",
19 | "body": [
20 | {
21 | "type": "RichTextBlock",
22 | "inlines": [
23 | {
24 | "type": "TextRun",
25 | "isSubtle": true,
26 | "italic": true,
27 | "text": "```json\n{\"translation_results\":[{\"index\":\"1\",\"data\":{\"SourceLanguage\":\"zh-Hans\",\"TargetLanguage\":\"en\",\"TranslatedText\":\"i want to travel\",\"OriginalText\":\"我想去旅游\",\"InputTransliteration\":\"\",\"OutputTransliteration\":\"\",\"Title\":\"Bing Translator\",\"TranslationUrl\":\"http://www.bing.com/translator/?ref=TThis&text=我想去旅游&from=zh-Hans&to=en\",\"alternateTranslations\":[]},\"url\":\"https://bing.com/search?q=translate+%e6%88%91%e6%83%b3%e5%8e%bb%e6%97%85%e6%b8%b8+to+English\"}],\"web_search_results\":[{\"index\":\"2\",\"title\":\"Google Translate\",\"snippets\":[\"Google's service, offered free of charge, instantly translates words, phrases, and web pages between English and over 100 other languages.\"],\"url\":\"https://translate.google.com.bd/?hl=en\"},{\"index\":\"3\",\"title\":\"Google Translate\",\"snippets\":[\"Translate. Detect language → English. Google home; Send feedback; Privacy and terms; Switch to full site\"],\"url\":\"https://translate.google.com/m\"},{\"index\":\"4\",\"title\":\"\\\"I want to travel and relax a bit.\\\" - Duolingo\",\"snippets\":[\"Further to my previous reply, please also note that there is a special punctuation mark used in Chinese in a sentence like this. Duolingo has used a comma, but normally you would use a 、 which is called 頓號〔顿号〕dùnhào which Pleco defines as a “slight-pause mark used to set off items in a series.”\"],\"url\":\"https://forum.duolingo.com/comment/25386100/I-want-to-travel-and-relax-a-bit\"}]}\n```"
28 | }
29 | ]
30 | }
31 | ]
32 | }
33 | ],
34 | "feedback": {
35 | "tag": null,
36 | "updatedOn": null,
37 | "type": "None"
38 | },
39 | "contentOrigin": "DeepLeo",
40 | "privacy": null
41 | }
42 | ],
43 | "requestId": "72a19064-e445-491d-97d1-bd03122e5537"
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/test/aichat-bingai-test.el:
--------------------------------------------------------------------------------
1 | ;;; aichat-bingai-test.el --- aichat-bingai-test -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat-bingai-test.el
4 | ;; Description: aichat-bingai-test
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-03-12 10:23:01
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-03-12 10:23:01
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat-bingai-test
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat-bingai-test.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat-bingai-test)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat-bingai-test RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/03/12
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 | (require 'ert)
84 |
85 | (add-to-list 'load-path (expand-file-name ".." default-directory))
86 |
87 | (require 'aichat-bingai)
88 |
89 | ;;; Code:
90 | (defun aichat-bingai-test-message-type-1 ()
91 | (aichat-json-parse-file (expand-file-name "test/aichat-bingai-message-type-1.json" (file-name-directory (locate-library "aichat-util")))))
92 |
93 | (defun aichat-bingai-test-message-type-1-search-result ()
94 | (aichat-json-parse-file (expand-file-name "test/aichat-bingai-message-type-1-search-result.json" (file-name-directory (locate-library "aichat-util")))))
95 |
96 | (defun aichat-bingai-test-message-type-2 ()
97 | (aichat-json-parse-file (expand-file-name "test/aichat-bingai-message-type-2.json" (file-name-directory (locate-library "aichat-util")))))
98 |
99 | (defun aichat-bingai-test-message-type-2-image-prompt ()
100 | (aichat-json-parse-file (expand-file-name "test/aichat-bingai-message-type-2-image-prompt.json" (file-name-directory (locate-library "aichat-util")))))
101 |
102 |
103 | (ert-deftest aichat-bingai-message-type-1-text ()
104 | (let ((message-type-1 (aichat-bingai-test-message-type-1)))
105 | (should (string= (aichat-bingai-message-type-1-text message-type-1) "根据Bing翻译[^1^],这句话的英文是:I want to travel.😊\n"))))
106 |
107 | (ert-deftest aichat-bingai-message-type-1-suggestion ()
108 | (let ((message (aichat-bingai-test-message-type-1)))
109 | (should (= (length (aichat-bingai-message-type-1-suggestion message)) 3))))
110 |
111 | (ert-deftest aichat-bingai-message-type-2-text ()
112 | (let ((message-type-2 (aichat-bingai-test-message-type-2)))
113 | (should (string= (aichat-bingai-message-type-2-text message-type-2) "判断链表的长度是一个常见的数据结构和算法问题。有多种方法可以解决这个问题,我为你找到了一些相关的资料,你可以参考一下:\n\n- 一种方法是使用**循环**,即定义一个计数器和一个指针,从头节点开始遍历链表,每遍历一个节点,计数器加一,直到指针为空为止,此时计数器的值就是链表的长度[^3^] [^2^]。这种方法的时间复杂度是O (n),空间复杂度是O (1)。\n- 另一种方法是使用**递归**,即定义一个递归函数,如果节点为空,返回0,否则返回1加上对下一个节点的递归调用,最终返回的值就是链表的长度[^3^]。这种方法的时间复杂度也是O (n),但空间复杂度是O (n),因为需要栈空间来存储递归调用。\n\n以上是我能找到的一些判断链表长度的方法,你可以根据自己的需要选择合适的方法。如果你还有其他问题,欢迎继续和我聊天。😊"))))
114 |
115 | (ert-deftest aichat-bingai-message-type-2-suggestion ()
116 | (let ((message-type-2 (aichat-bingai-test-message-type-2)))
117 | (should (= (length (aichat-bingai-message-type-2-suggestion message-type-2)) 3))))
118 |
119 | (ert-deftest aichat-bingai-message-type-2-image-prompt ()
120 | (let ((message-type-2 (aichat-bingai-test-message-type-2-image-prompt)))
121 | (should (string= (aichat-bingai-message-type-2-image-prompt message-type-2) "一只红色的猫和一只绿色的狗"))))
122 |
123 | (ert-deftest aichat-bingai-message-type-2-source-attrs ()
124 | (let ((message-type-2 (aichat-bingai-test-message-type-2)))
125 | (should (= (length (aichat-bingai-message-type-2-source-attrs message-type-2)) 3))))
126 |
127 | (provide 'aichat-bingai-test)
128 | ;;; aichat-bingai-test.el ends here
129 |
--------------------------------------------------------------------------------
/test/aichat-bingai-message-type-1.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 1,
3 | "target": "update",
4 | "arguments": [
5 | {
6 | "messages": [
7 | {
8 | "text": "根据Bing翻译[^1^],这句话的英文是:I want to travel.😊\n",
9 | "author": "bot",
10 | "createdAt": "2023-03-12T02:08:00.2809296+00:00",
11 | "timestamp": "2023-03-12T02:08:00.2809296+00:00",
12 | "messageId": "222164bb-c205-4aa9-811a-c203df7d94e3",
13 | "offense": "Unknown",
14 | "adaptiveCards": [
15 | {
16 | "type": "AdaptiveCard",
17 | "version": "1.0",
18 | "body": [
19 | {
20 | "type": "TextBlock",
21 | "text": "[1]: https://bing.com/search?q=translate+%E6%88%91%E6%83%B3%E5%8E%BB%E6%97%85%E6%B8%B8+to+English \"\"\n[2]: https://translate.google.com.bd/?hl=en \"Google Translate\"\n[3]: https://translate.google.com/m \"Google Translate\"\n[4]: https://forum.duolingo.com/comment/25386100/I-want-to-travel-and-relax-a-bit \"\\\"I want to travel and relax a bit.\\\" - Duolingo\"\n\n根据Bing翻译[^1^][1],这句话的英文是:I want to travel.😊\n",
22 | "wrap": true
23 | },
24 | {
25 | "type": "TextBlock",
26 | "size": "small",
27 | "text": "Learn more: [1. bing.com](https://bing.com/search?q=translate+%E6%88%91%E6%83%B3%E5%8E%BB%E6%97%85%E6%B8%B8+to+English) [2. translate.google.com.bd](https://translate.google.com.bd/?hl=en) [3. translate.google.com](https://translate.google.com/m) [4. forum.duolingo.com](https://forum.duolingo.com/comment/25386100/I-want-to-travel-and-relax-a-bit)",
28 | "wrap": true
29 | }
30 | ]
31 | }
32 | ],
33 | "sourceAttributions": [
34 | {
35 | "providerDisplayName": "",
36 | "seeMoreUrl": "https://bing.com/search?q=translate+%e6%88%91%e6%83%b3%e5%8e%bb%e6%97%85%e6%b8%b8+to+English",
37 | "searchQuery": "translate 我想去旅游 to English"
38 | },
39 | {
40 | "providerDisplayName": "Google Translate",
41 | "seeMoreUrl": "https://translate.google.com.bd/?hl=en",
42 | "searchQuery": "translate 我想去旅游 to English"
43 | },
44 | {
45 | "providerDisplayName": "Google Translate",
46 | "seeMoreUrl": "https://translate.google.com/m",
47 | "searchQuery": "translate 我想去旅游 to English"
48 | },
49 | {
50 | "providerDisplayName": "\"I want to travel and relax a bit.\" - Duolingo",
51 | "seeMoreUrl": "https://forum.duolingo.com/comment/25386100/I-want-to-travel-and-relax-a-bit",
52 | "searchQuery": "translate 我想去旅游 to English"
53 | }
54 | ],
55 | "feedback": {
56 | "tag": null,
57 | "updatedOn": null,
58 | "type": "None"
59 | },
60 | "contentOrigin": "DeepLeo",
61 | "privacy": null,
62 | "suggestedResponses": [
63 | {
64 | "text": "谢谢你。",
65 | "author": "user",
66 | "createdAt": "2023-03-12T02:08:04.2222136+00:00",
67 | "timestamp": "2023-03-12T02:08:04.2222136+00:00",
68 | "messageId": "dfd27bfd-e3a0-41b4-b7b5-fc267eb9978f",
69 | "messageType": "Suggestion",
70 | "offense": "Unknown",
71 | "feedback": {
72 | "tag": null,
73 | "updatedOn": null,
74 | "type": "None"
75 | },
76 | "contentOrigin": "DeepLeo",
77 | "privacy": null
78 | },
79 | {
80 | "text": "你会说中文吗?",
81 | "author": "user",
82 | "createdAt": "2023-03-12T02:08:04.2222158+00:00",
83 | "timestamp": "2023-03-12T02:08:04.2222158+00:00",
84 | "messageId": "4eccdcb4-952b-403b-ba6a-9d073091345a",
85 | "messageType": "Suggestion",
86 | "offense": "Unknown",
87 | "feedback": {
88 | "tag": null,
89 | "updatedOn": null,
90 | "type": "None"
91 | },
92 | "contentOrigin": "DeepLeo",
93 | "privacy": null
94 | },
95 | {
96 | "text": "你能教我一些英语吗?",
97 | "author": "user",
98 | "createdAt": "2023-03-12T02:08:04.222218+00:00",
99 | "timestamp": "2023-03-12T02:08:04.222218+00:00",
100 | "messageId": "6dcee439-140f-4303-902b-39c7358bb7f3",
101 | "messageType": "Suggestion",
102 | "offense": "Unknown",
103 | "feedback": {
104 | "tag": null,
105 | "updatedOn": null,
106 | "type": "None"
107 | },
108 | "contentOrigin": "DeepLeo",
109 | "privacy": null
110 | }
111 | ]
112 | }
113 | ],
114 | "requestId": "72a19064-e445-491d-97d1-bd03122e5537"
115 | }
116 | ]
117 | }
118 |
--------------------------------------------------------------------------------
/test/aichat-bingai-message-type-2-image-prompt.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 2,
3 | "invocationId": "5",
4 | "item": {
5 | "messages": [
6 | {
7 | "text": "创建一幅图片,里面应该有一只猫和一直狗,猫的颜色为红色,狗的颜色为绿色",
8 | "author": "user",
9 | "from": {
10 | "id": "1055518581560278",
11 | "name": null
12 | },
13 | "createdAt": "2023-03-23T15:20:51.5583496+00:00",
14 | "timestamp": "2023-03-23T15:20:51.5558201+00:00",
15 | "locale": "en-us",
16 | "market": "en-us",
17 | "region": "us",
18 | "messageId": "df287ac4-3421-4b79-a7f2-082da08f8113",
19 | "requestId": "df287ac4-3421-4b79-a7f2-082da08f8113",
20 | "offense": "None",
21 | "feedback": {
22 | "tag": null,
23 | "updatedOn": null,
24 | "type": "None"
25 | },
26 | "contentOrigin": "cib",
27 | "privacy": null,
28 | "inputMethod": "Keyboard"
29 | },
30 | {
31 | "text": "我会尝试创建这样的图片。",
32 | "author": "bot",
33 | "createdAt": "2023-03-23T15:20:58.1950527+00:00",
34 | "timestamp": "2023-03-23T15:20:58.1950527+00:00",
35 | "messageId": "0160433c-e842-4a9e-9945-d6d6be48faa1",
36 | "requestId": "df287ac4-3421-4b79-a7f2-082da08f8113",
37 | "offense": "None",
38 | "adaptiveCards": [
39 | {
40 | "type": "AdaptiveCard",
41 | "version": "1.0",
42 | "body": [
43 | {
44 | "type": "TextBlock",
45 | "text": "我会尝试创建这样的图片。\n",
46 | "wrap": true
47 | }
48 | ]
49 | }
50 | ],
51 | "sourceAttributions": [],
52 | "feedback": {
53 | "tag": null,
54 | "updatedOn": null,
55 | "type": "None"
56 | },
57 | "contentOrigin": "DeepLeo",
58 | "privacy": null,
59 | "suggestedResponses": [
60 | {
61 | "text": "让它们互相看着",
62 | "author": "user",
63 | "createdAt": "2023-03-23T15:21:06.5905311+00:00",
64 | "timestamp": "2023-03-23T15:21:06.5905311+00:00",
65 | "messageId": "11093c5a-ebad-40a1-9966-c4d4c9fdfd60",
66 | "messageType": "Suggestion",
67 | "offense": "Unknown",
68 | "feedback": {
69 | "tag": null,
70 | "updatedOn": null,
71 | "type": "None"
72 | },
73 | "contentOrigin": "DeepLeo",
74 | "privacy": null
75 | },
76 | {
77 | "text": "给它们加上帽子",
78 | "author": "user",
79 | "createdAt": "2023-03-23T15:21:06.5905338+00:00",
80 | "timestamp": "2023-03-23T15:21:06.5905338+00:00",
81 | "messageId": "fe3c4827-6fb7-4efe-ade2-b7e12384eac4",
82 | "messageType": "Suggestion",
83 | "offense": "Unknown",
84 | "feedback": {
85 | "tag": null,
86 | "updatedOn": null,
87 | "type": "None"
88 | },
89 | "contentOrigin": "DeepLeo",
90 | "privacy": null
91 | },
92 | {
93 | "text": "把它们换成老鼠和大象",
94 | "author": "user",
95 | "createdAt": "2023-03-23T15:21:06.5905342+00:00",
96 | "timestamp": "2023-03-23T15:21:06.5905342+00:00",
97 | "messageId": "e6a16ed5-77d1-4bf4-b913-a4758ba3e02e",
98 | "messageType": "Suggestion",
99 | "offense": "Unknown",
100 | "feedback": {
101 | "tag": null,
102 | "updatedOn": null,
103 | "type": "None"
104 | },
105 | "contentOrigin": "DeepLeo",
106 | "privacy": null
107 | }
108 | ],
109 | "spokenText": "我会尝试创建这样的图片"
110 | },
111 | {
112 | "contentType": "IMAGE",
113 | "text": "一只红色的猫和一只绿色的狗",
114 | "hiddenText": "一只红色的猫和一只绿色的狗",
115 | "author": "bot",
116 | "createdAt": "2023-03-23T15:21:01.3617582+00:00",
117 | "timestamp": "2023-03-23T15:21:01.3617582+00:00",
118 | "messageId": "294eaba6-7da0-4d69-86cd-6c94046f73ae",
119 | "requestId": "df287ac4-3421-4b79-a7f2-082da08f8113",
120 | "messageType": "GenerateContentQuery",
121 | "offense": "None",
122 | "feedback": {
123 | "tag": null,
124 | "updatedOn": null,
125 | "type": "None"
126 | },
127 | "contentOrigin": "DeepLeo",
128 | "privacy": null
129 | }
130 | ],
131 | "firstNewMessageIndex": 1,
132 | "conversationId": "51D|BingProd|80EE7E576CD5037C2A8422504040F81F1D57E10E4CDBE5F313585D592ADC8909",
133 | "requestId": "df287ac4-3421-4b79-a7f2-082da08f8113",
134 | "conversationExpiryTime": "2023-03-23T21:21:06.6765729Z",
135 | "telemetry": {
136 | "metrics": null,
137 | "startTime": "2023-03-23T15:20:51.5558711Z"
138 | },
139 | "throttling": {
140 | "maxNumUserMessagesInConversation": 15,
141 | "numUserMessagesInConversation": 6
142 | },
143 | "result": {
144 | "value": "Success",
145 | "serviceVersion": "20230323.53"
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/README.zh-CN.org:
--------------------------------------------------------------------------------
1 | #+html:
2 | #+html:
3 | #+html:
4 |
5 | * AI Chat in Emacs
6 |
7 | 在 Emacs 中使用 AI 聊天功能,目前包括 Bing Chat 和 OpenAI 的 ChatGpt 。
8 |
9 | * 安装
10 |
11 | ** 依赖
12 |
13 | - [[https://github.com/ahyatt/emacs-websocket][emacs-websocket]] 用来和 Bing Chat 通信,如果你已经安装的这个插件,请更新到最新版本
14 | - [[https://github.com/chuntaro/emacs-async-await][emacs-async-await]]
15 | - [[https://github.com/jrblevin/markdown-mode][markdown-mode]]
16 |
17 | ** 安装
18 |
19 | 克隆仓库到本地,并将路径加入到 =load-path= 中:
20 |
21 | #+begin_src sh
22 | git clone https://github.com/xhcoding/emacs-aichat.git ~/.emacs.d/site-lisp/emacs-aichat
23 | #+end_src
24 |
25 | #+begin_src elisp
26 | (add-to-list 'load-path "~/.emacs.d/site-lisp/emacs-aichat")
27 | #+end_src
28 |
29 | 添加下面的代码到你的 emacs 启动文件中:
30 |
31 | #+begin_src elisp
32 | (require 'aichat)
33 | #+end_src
34 |
35 | 或者单独加载
36 |
37 | #+begin_src elisp
38 | (require 'aichat-bingai)
39 | ;; or
40 | (require 'aichat-openai)
41 | #+end_src
42 |
43 | * Bing Chat
44 |
45 | #+html: 
46 |
47 | [[https://github.com/xhcoding/emacs-aichat/blob/main/images/examples.org][查看更多例子]]
48 |
49 | 下面是关于使用 Bing Chat 配置,如果你想要了解关于 OpenAI 的使用,请向下移动到 OpenAI 章节
50 |
51 | ** 设置
52 |
53 | *** 检查能否使用 Bing Chat
54 |
55 | - 安装最新版本的 Edge 浏览器
56 | - 打开 http://bing.com/chat ,在国内需要代理,不然会被重定向到必应中国
57 | - 如果能够使用聊天功能,说明可以使用 Bing Chat
58 |
59 | *** 登录
60 |
61 | emacs-aichat 获取浏览器的 cookie 登录 Bing Chat ,所以你需要先在浏览器登录 http://www.bing.com 。
62 |
63 | 有两种方式获取浏览器的 cookie ,你可以任选一种方便的方法。
64 |
65 | **** 方式一
66 |
67 | 如果你有 Python 环境,运行 =pip3 install rookiepy= 安装 =rookiepy= ,
68 | 然后打开终端,执行下面的代码:
69 |
70 | #+begin_src sh
71 | python -c "import rookiepy;list(map(lambda c: print('{} {} {} {} {} {}'.format(c['name'], c['value'], c['expires'], c['domain'], c['path'], c['secure'])), filter(lambda c: c['domain'] in ('.bing.com'), rookiepy.edge(['.bing.com']))))"
72 | #+end_src
73 |
74 | 如果你能够看到输出,并且输出中有 =_U= 字段,那么你能够用这种方式登录成功。
75 |
76 | **** 方式二
77 |
78 | - 安装 Edge 的 [[https://microsoftedge.microsoft.com/addons/detail/cookieeditor/neaplmfkghagebokkhpjpoebhdledlfi][Cookie Editor]] 扩展
79 | - 打开 https://bing.com
80 | - 打开 Cookie Editor
81 | - 点击右下角的 "Export" ,成功后会把网站 cookie 保存到剪贴板
82 | - 将剪贴板内容粘贴到 cookies.json 中
83 | - 设置 =aichat-bingai-cookies-file= 的值为 cookies.json 文件的路径
84 |
85 | *** 配置代理 [可选]
86 |
87 | 目前只测试过 http 代理,设置 =aichat-bingai-proxy= 为代理地址,比如:
88 |
89 | #+begin_src elisp
90 | (setq aichat-bingai-proxy "localhost:51837")
91 | #+end_src
92 |
93 |
94 | *如果你的 IP 地址在中国,必须要设置代理才能访问*
95 |
96 | ** 使用
97 |
98 | *** 命令
99 |
100 | - =aichat-bingai-chat= : 发送对话消息,消息会保存在 =aichat-bingai-chat-file= 中,加上 =C-u= 前缀会重新开始一个对话
101 | - =aichat-bingai-assistant= : 发送选中区域或者输入的内容,返回的结果显示在 =aichat-bingai-assistant-buffer= 定义的 Buffer 中
102 | - =aichat-bingai-replace-or-insert= : 发送选中区域或者输入的内容,将返回的结果替代选中区域或者插入当前位置。
103 |
104 | *** 自定义固定提问方式
105 |
106 | 使用 =aichat-bingai-prompt-create= 创建一个固定的提问方式,比如:
107 |
108 | #+begin_src elisp
109 | (aichat-bingai-prompt-create "translator"
110 | :input-prompt "请翻译: "
111 | :text-format "请识别我说的语言,如果我说的语言是中文,请把它翻译成英文;如果我说的语言不是中文,请把它翻译成中文。你的返回内容必须是翻译后的文本,不能包括其它内容:\n%s"
112 | :chat t
113 | :assistant t
114 | :replace-or-insert t)
115 | #+end_src
116 |
117 | 上面这个配置会生成三个函数: =aichat-bingai-chat-translator= , =aichat-bingai-assistant-translator= , =aichat-bingai-replace-or-insert-translator= ,效果和上面三个命令一样,只是提问的内容会按照 =text-format= 的格式。
118 |
119 | *** 选项
120 |
121 | - =aichat-bingai-cookies-file= : Bing cookies 文件路径
122 | - =aichat-bingai-conversation-style= : 对话风格,可选项为:=creative= , =balanced= 或 =precise= ,默认为 =balanced=
123 | - =aichat-bingai-chat-file= : 保存聊天记录的文件
124 | - =aichat-bingai-chat-display-function= :显示聊天文件的函数
125 | - =aichat-bingai-assistant-buffer= : 显示 =aichat-bingai-assistant= 返回的内容的 Buffer 名称
126 | - =aichat-bingai-assistant-display-function= : 显示 =aichat-bingai-assistant-buffer= 的方式,默认为 =display-buffer=
127 |
128 | * OpenAI
129 |
130 | #+html: 
131 |
132 | ** 设置
133 |
134 | *** 配置 AppKey
135 |
136 | emacs-aichat 通过 =auth-source-pick-first-password= 获取 AppKey ,将你的 appkey 写入
137 | =~/.authinfo= 文件里,格式如下:
138 |
139 | #+begin_src conf
140 | machine platform.openai.com login aichat-openai password your-app-key
141 | #+end_src
142 |
143 | *** 配置代理 [可选]
144 |
145 | 目前只测试过 http 代理,设置 =aichat-openai-proxy= 为代理地址,比如:
146 |
147 | #+begin_src elisp
148 | (setq aichat-openai-proxy "localhost:51837")
149 | #+end_src
150 |
151 | ** 使用
152 |
153 | *** 聊天模式
154 |
155 | 1. 用 =aichat-openai-chat= 创建一个新的聊天文件
156 | 2. 输入聊天内容
157 | 3. =aichat-openai-chat-send-buffer (C-c C-c)= 发送整个 buffer 的内容 ,
158 | 或者用 =aichat-openai-chat-last-heading (C-c C-l)= 发送最后一个 User 内容
159 |
160 | *** 助手模式
161 |
162 | - =aichat-openai-assistant= : 发送选中区域或输入内容,将结果显示在 =aichat-openai-assistant-buffer= 中
163 | - =aichat-openai-replace-or-insert= : 发送选中区域或者输入的内容,将返回的结果替代选中区域或者插入当前位置
164 |
165 | *** 自定义固定提问方式
166 |
167 | 使用 =aichat-openai-prompt-create= 创建一个固定的提问方式,比如:
168 |
169 | #+begin_src elisp
170 | (aichat-openai-prompt-create "translator"
171 | :input-prompt "请翻译: "
172 | :text-format "请识别我说的语言,如果我说的语言是中文,请把它翻译成英文;如果我说的语言不是中文,请把它翻译成中文。你的返回内容必须是翻译后的文本,不能包括其它内容:\n%s"
173 | :assistant t
174 | :replace-or-insert t)
175 | #+end_src
176 |
177 | 上面这个配置会生成两个函数: =aichat-openai-assistant-translator= , =aichat-openai-replace-or-insert-translator= ,效果和上面两个命令一样,只是提问的内容会按照 =text-format= 的格式。
178 |
179 |
180 | *** 选项
181 |
182 | - =aichat-openai-proxy= : 设置请求 openai 的 http 代理
183 | - =aichat-openai-assistant-buffer= : OpenAI 助手信息的 buffer 名
184 | - =aichat-openai-assistant-display-function= :显示 OpenAI 助手 Buffer 的函数
185 | - =aichat-openai-chat-directory= : 存放聊天文件的目录
186 | - =aichat-openai-chat-display-function= : 显示聊天 buffer 的函数
187 |
188 |
189 | * 反馈问题
190 |
191 | 要反馈你的问题,请按照以下步骤操作:
192 | 1. 通过输入命令 =aichat-toggle-debug= 来打开调试模式。
193 | 2. 当出现错误时,[[https://github.com/xhcoding/emacs-aichat/issues/new][提交一个问题]] 并附上 =*AICHAT-DEBUG*= 缓冲区的内容。
194 |
195 | * 感谢
196 |
197 | - [[https://github.com/acheong08/EdgeGPT][EdgeGPT]]
198 | - [[https://github.com/manateelazycat/mind-wave][mind-wave]]
199 | - [[https://github.com/f/awesome-chatgpt-prompts][awesome-chatgpt-prompts]]
200 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+html:
2 | #+html:
3 | #+html:
4 | #+html:
5 |
6 | * AI Chat in Emacs
7 |
8 | Use AI chat features in Emacs, currently including Bing Chat and OpenAI’s ChatGpt.
9 |
10 | * Installation
11 |
12 | ** Requirements
13 |
14 | - [[https://github.com/ahyatt/emacs-websocket][emacs-websocket]] To communicate with Bing Chat, if you have already installed this package, please update to the latest version.
15 | - [[https://github.com/chuntaro/emacs-async-await][emacs-async-await]]
16 | - [[https://github.com/jrblevin/markdown-mode][markdown-mode]]
17 |
18 | ** Install
19 |
20 | Clone the repository to your local machine, and add the path to your =load-path= :
21 |
22 | #+begin_src sh
23 | git clone https://github.com/xhcoding/emacs-aichat.git ~/.emacs.d/site-lisp/emacs-aichat
24 | #+end_src
25 |
26 | #+begin_src elisp
27 | (add-to-list 'load-path "~/.emacs.d/site-lisp/emacs-aichat")
28 | #+end_src
29 |
30 | add the following code to your emacs startup file:
31 |
32 | #+begin_src elisp
33 | (require 'aichat)
34 | #+end_src
35 |
36 | Or load separately
37 |
38 | #+begin_src elisp
39 | (require 'aichat-bingai)
40 | ;; or
41 | (require 'aichat-openai)
42 | #+end_src
43 |
44 | * Bing Chat
45 |
46 | #+html: 
47 |
48 | [[https://github.com/xhcoding/emacs-aichat/blob/main/images/examples.org][View more examples]]
49 |
50 |
51 | Below is information on setting up Bing AI. If you are interested in learning about using OpenAI, please scroll down to the OpenAI section.
52 |
53 | ** Setup
54 |
55 | *** Check access (Required)
56 |
57 | - Install the latest version of Microsoft Edge
58 | - Open http://bing.com/chat
59 | - If you see a chat feature, you are good to go
60 |
61 | *** Getting authentication (Required)
62 |
63 | emacs-aichat uses browser cookies to log in to Bing Chat, so you need to log in to http://www.bing.com first in your browser.
64 |
65 | There are two ways to get the browser's cookie, you can choose any convenient method.
66 |
67 | **** Option one
68 |
69 | If you have a Python environment, run =pip3 install rookiepy= to install =rookiepy=, then open the terminal and execute the following code:
70 |
71 |
72 | #+begin_src sh
73 | python -c "import rookiepy;list(map(lambda c: print('{} {} {} {} {} {}'.format(c['name'], c['value'], c['expires'], c['domain'], c['path'], c['secure'])), filter(lambda c: c['domain'] in ('.bing.com'), rookiepy.edge(['.bing.com']))))"
74 | #+end_src
75 |
76 | If you can see the output and there is a =_U= field in the output, then you can log in successfully with this method.
77 |
78 | **** Option two
79 |
80 | - Install the cookie editor extension for [[https://microsoftedge.microsoft.com/addons/detail/cookieeditor/neaplmfkghagebokkhpjpoebhdledlfi][Edge]]
81 | - Go to bing.com
82 | - Open the extension
83 | - Click "Export" on the bottom right (This saves your cookies to clipboard)
84 | - Paste your cookies into a file cookies.json
85 | - Set =aichat-bingai-cookies-file= to your cookies.json path
86 |
87 | *** Proxy [Optional]
88 |
89 | Currently only tested http proxy, set =aichat-bingai-proxy= as proxy address, for example:
90 |
91 | #+begin_src elisp
92 | (setq aichat-bingai-proxy "localhost:51837")
93 | #+end_src
94 |
95 | *If your IP address is in China, you must set up a proxy to access it.*
96 |
97 | ** Usage
98 |
99 | *** Commands
100 |
101 | - =aichat-bingai-chat= : Send a conversation message, adding =C-u= prefix will start a new conversation
102 | - =aichat-bingai-assistant= : Send the selected region or the input content, and the returned result is displayed in the Buffer defined by =aichat-bingai-assistant-buffer=
103 | - =aichat-bingai-replace-or-insert= : Send the selected region or the input content, and replace the selected region or insert at the current position with the returned result
104 |
105 | *** Customized fixed questioning approach.
106 |
107 | Use =aichat-bingai-prompt-create= to create a fixed way of asking questions, such as:
108 |
109 | #+begin_src elisp
110 | (aichat-bingai-prompt-create "translator"
111 | :input-prompt "Please translate: "
112 | :text-format "Please identify the language I am speaking. If it is Chinese, please translate it into English; if it is not Chinese, please translate it into Chinese. Your answer must only contain the translated content. The following is the sentence I want to translate:\n%s"
113 | :chat t
114 | :assistant t
115 | :replace-or-insert t)
116 | #+end_src
117 |
118 | The above configuration will generate three functions: =aichat-bingai-chat-translator=, =aichat-bingai-assistant-translator=, and =aichat-bingai-replace-or-insert-translator=, which have the same effect as the above three commands, but the content of the query will be in the format of =text-format=.
119 |
120 |
121 | *** Options
122 |
123 | - =aichat-bingai-cookies-file= : The path of cookies file.
124 | - =aichat-bingai-conversation-style= : Conversation style, optional values are: =creative= , =balanced= or =precise= , default is =balanced=
125 | - =aichat-bingai-chat-file= : a file that saves chat history
126 | - =aichat-bingai-chat-display-function= : a function that displays chat files
127 | - =aichat-bingai-assistant-buffer= : Display the buffer name of the content returned by =aichat-bingai-assistant=
128 | - =aichat-bingai-assistant-display-function= : The function to display =aichat-bingai-assistant-buffer=, default to =display-buffer=
129 |
130 | * OpenAI
131 |
132 | #+html: 
133 |
134 | ** Setup
135 |
136 | *** AppKey
137 |
138 | emacs-aichat obtains AppKey through =auth-source-pick-first-password=, and writes your appkey into =~/.authinfo= file, with the following format:
139 |
140 | #+begin_src conf
141 | machine platform.openai.com login aichat-openai password your-app-key
142 | #+end_src
143 |
144 | *** Proxy [Optional]
145 |
146 | Currently only tested http proxy, set =aichat-openai-proxy= as proxy address, for example:
147 |
148 | #+begin_src elisp
149 | (setq aichat-openai-proxy "localhost:51837")
150 | #+end_src
151 |
152 |
153 |
154 | ** Usage
155 |
156 | *** Chat
157 |
158 | 1. Create a new chat file using =aichat-openai-chat=.
159 | 2. Enter the chat content.
160 | 3. Use =aichat-openai-chat-send-buffer (C-c C-c)= to send the entire contents of the buffer,
161 | or use =aichat-openai-chat-last-heading (C-c C-l)= to only send the last heading message.
162 |
163 | *** Assistant
164 |
165 | - =aichat-openai-assistant= : Send the selected region or the input content, and the returned result is displayed in the Buffer defined by =aichat-openai-assistant-buffer=
166 | - =aichat-openai-replace-or-insert= : Send the selected region or the input content, and replace the selected region or insert at the current position with the returned result
167 |
168 | *** Customized fixed questioning approach.
169 |
170 | Use =aichat-openai-prompt-create= to create a fixed way of asking questions, such as:
171 |
172 | #+begin_src elisp
173 | (aichat-openai-prompt-create "translator"
174 | :input-prompt "Please translate: "
175 | :text-format "Please identify the language I am speaking. If it is Chinese, please translate it into English; if it is not Chinese, please translate it into Chinese. Your answer must only contain the translated content. The following is the sentence I want to translate:\n%s"
176 | :assistant t
177 | :replace-or-insert t)
178 | #+end_src
179 |
180 | The above configuration will generate two functions: =aichat-openai-assistant-translator=, and =aichat-openai-replace-or-insert-translator=, which have the same effect as the above three commands, but the content of the query will be in the format of =text-format=.
181 |
182 | *** Options
183 |
184 | - =aichat-openai-proxy= : set up an HTTP proxy that requests OpenAI
185 | - =aichat-openai-assistant-buffer= : the buffer name of the OpenAI Assistant result
186 | - =aichat-openai-assistant-display-function= : the function of display assistant buffer
187 | - =aichat-openai-chat-directory= : Directory for storing chat files
188 | - =aichat-openai-chat-display-function= : Function to display chat buffer
189 |
190 | * Feedback Issues
191 |
192 | To feedback your issue, please follow these steps:
193 | 1. Open the debug mode by typing the command =aichat-toggle-debug=.
194 | 2. [[https://github.com/xhcoding/emacs-aichat/issues/new][Submit an issue]] and attach the content of the =*AICHAT-DEBUG*= buffer when an error occurs.
195 |
196 |
197 | * Acknowledgements
198 |
199 | - [[https://github.com/acheong08/EdgeGPT][EdgeGPT]]
200 | - [[https://github.com/manateelazycat/mind-wave][mind-wave]]
201 | - [[https://github.com/f/awesome-chatgpt-prompts][awesome-chatgpt-prompts]]
202 |
--------------------------------------------------------------------------------
/test/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": ".bing.com",
4 | "expirationDate": 1710498317.051456,
5 | "hostOnly": false,
6 | "httpOnly": false,
7 | "name": "SnrOvr",
8 | "path": "/",
9 | "sameSite": "no_restriction",
10 | "secure": true,
11 | "session": false,
12 | "storeId": null,
13 | "value": "X=rebateson"
14 | },
15 | {
16 | "domain": ".bing.com",
17 | "expirationDate": 1713432655.15995,
18 | "hostOnly": false,
19 | "httpOnly": false,
20 | "name": "SRCHUSR",
21 | "path": "/",
22 | "sameSite": "no_restriction",
23 | "secure": true,
24 | "session": false,
25 | "storeId": null,
26 | "value": "DOB=20230313&T=1678872653000"
27 | },
28 | {
29 | "domain": "www.bing.com",
30 | "expirationDate": 1678877717,
31 | "hostOnly": true,
32 | "httpOnly": false,
33 | "name": "ai_session",
34 | "path": "/",
35 | "sameSite": "no_restriction",
36 | "secure": true,
37 | "session": false,
38 | "storeId": null,
39 | "value": "YB3viLWO/Znf6Lk3ILG7Vu|1678875917973|1678875917973"
40 | },
41 | {
42 | "domain": ".bing.com",
43 | "expirationDate": 1678894986.106793,
44 | "hostOnly": false,
45 | "httpOnly": true,
46 | "name": "SUID",
47 | "path": "/",
48 | "sameSite": "no_restriction",
49 | "secure": true,
50 | "session": false,
51 | "storeId": null,
52 | "value": "A"
53 | },
54 | {
55 | "domain": ".bing.com",
56 | "expirationDate": 1713435929.448756,
57 | "hostOnly": false,
58 | "httpOnly": false,
59 | "name": "SRCHHPGUSR",
60 | "path": "/",
61 | "sameSite": "no_restriction",
62 | "secure": true,
63 | "session": false,
64 | "storeId": null,
65 | "value": "SRCHLANG=zh-Hans&PV=15.0.0&BRW=XW&BRH=M&CW=1536&CH=713&SCW=1536&SCH=713&DPR=2.5&UTC=480&DM=0&PRVCW=376&PRVCH=654&HV=1678875929"
66 | },
67 | {
68 | "domain": ".bing.com",
69 | "expirationDate": 1713435826.720771,
70 | "hostOnly": false,
71 | "httpOnly": false,
72 | "name": "ANON",
73 | "path": "/",
74 | "sameSite": "no_restriction",
75 | "secure": true,
76 | "session": false,
77 | "storeId": null,
78 | "value": "A=851E7E1B1CDEA6BA4C7935D8FFFFFFFF"
79 | },
80 | {
81 | "domain": ".bing.com",
82 | "expirationDate": 1713432592.626544,
83 | "hostOnly": false,
84 | "httpOnly": false,
85 | "name": "EDGSRVCUSR",
86 | "path": "/",
87 | "sameSite": "no_restriction",
88 | "secure": true,
89 | "session": false,
90 | "storeId": null,
91 | "value": "udscdxtone=Balanced"
92 | },
93 | {
94 | "domain": "www.bing.com",
95 | "expirationDate": 1710411917.855038,
96 | "hostOnly": true,
97 | "httpOnly": false,
98 | "name": "MicrosoftApplicationsTelemetryDeviceId",
99 | "path": "/",
100 | "sameSite": "no_restriction",
101 | "secure": true,
102 | "session": false,
103 | "storeId": null,
104 | "value": "3b06678e-f1e7-4838-bf17-5c1e98627e28"
105 | },
106 | {
107 | "domain": ".bing.com",
108 | "hostOnly": false,
109 | "httpOnly": false,
110 | "name": "_SS",
111 | "path": "/",
112 | "sameSite": "no_restriction",
113 | "secure": true,
114 | "session": true,
115 | "storeId": null,
116 | "value": "SID=027B58C3BAA465320D904A16BBE8648E&R=682&RB=682&GB=0&RG=0&RP=679"
117 | },
118 | {
119 | "domain": ".bing.com",
120 | "hostOnly": false,
121 | "httpOnly": false,
122 | "name": "ipv6",
123 | "path": "/",
124 | "sameSite": "no_restriction",
125 | "secure": true,
126 | "session": true,
127 | "storeId": null,
128 | "value": "hit=1678879516078&t=6"
129 | },
130 | {
131 | "domain": ".bing.com",
132 | "expirationDate": 1680085426.720741,
133 | "hostOnly": false,
134 | "httpOnly": false,
135 | "name": "_U",
136 | "path": "/",
137 | "sameSite": "no_restriction",
138 | "secure": true,
139 | "session": false,
140 | "storeId": null,
141 | "value": "invalid, do not use this"
142 | },
143 | {
144 | "domain": ".bing.com",
145 | "expirationDate": 1713252288.20224,
146 | "hostOnly": false,
147 | "httpOnly": false,
148 | "name": "SRCHD",
149 | "path": "/",
150 | "sameSite": "no_restriction",
151 | "secure": true,
152 | "session": false,
153 | "storeId": null,
154 | "value": "AF=NOFORM"
155 | },
156 | {
157 | "domain": ".bing.com",
158 | "expirationDate": 1681445753,
159 | "hostOnly": false,
160 | "httpOnly": false,
161 | "name": "ANIMIA",
162 | "path": "/",
163 | "sameSite": "no_restriction",
164 | "secure": true,
165 | "session": false,
166 | "storeId": null,
167 | "value": "FRE=1"
168 | },
169 | {
170 | "domain": ".bing.com",
171 | "expirationDate": 1712388288.202212,
172 | "hostOnly": false,
173 | "httpOnly": true,
174 | "name": "_EDGE_V",
175 | "path": "/",
176 | "sameSite": null,
177 | "secure": false,
178 | "session": false,
179 | "storeId": null,
180 | "value": "1"
181 | },
182 | {
183 | "domain": ".bing.com",
184 | "expirationDate": 1710498317.0514,
185 | "hostOnly": false,
186 | "httpOnly": false,
187 | "name": "_RwBf",
188 | "path": "/",
189 | "sameSite": "no_restriction",
190 | "secure": true,
191 | "session": false,
192 | "storeId": null,
193 | "value": "r=1&mta=0&rc=682&rb=682&gb=0&rg=0&pc=679&mtu=0&rbb=0.0&g=0&cid=&clo=0&v=1&l=2023-03-15T07:00:00.0000000Z&lft=0001-01-01T00:00:00.0000000&aof=0&o=0&p=bingcopilotwaitlist&c=MY00IA&t=278&s=2023-02-08T12:21:14.6708977+00:00&ts=2023-03-15T10:25:15.2541530+00:00&rwred=0&wls=2&lka=0&lkt=0&TH=&e=CwzCZ3nLPLkMTGl4xLup1VSFF1VNPu5xOIUgc4Bmzcmv7EAbH0TCc9Zx3rkfCE270QhrlYh3PZPt2CWaYRM4Ub4sjg2e28u8Nj-ncm7QD1I&A="
194 | },
195 | {
196 | "domain": ".bing.com",
197 | "expirationDate": 1713435910.411322,
198 | "hostOnly": false,
199 | "httpOnly": false,
200 | "name": "_UR",
201 | "path": "/",
202 | "sameSite": "no_restriction",
203 | "secure": true,
204 | "session": false,
205 | "storeId": null,
206 | "value": "QS=0&TQS=0"
207 | },
208 | {
209 | "domain": ".bing.com",
210 | "hostOnly": false,
211 | "httpOnly": true,
212 | "name": "_EDGE_S",
213 | "path": "/",
214 | "sameSite": null,
215 | "secure": false,
216 | "session": true,
217 | "storeId": null,
218 | "value": "SID=027B58C3BAA465320D904A16BBE8648E"
219 | },
220 | {
221 | "domain": "www.bing.com",
222 | "expirationDate": 1712571916.350313,
223 | "hostOnly": true,
224 | "httpOnly": true,
225 | "name": "MUIDB",
226 | "path": "/",
227 | "sameSite": null,
228 | "secure": false,
229 | "session": false,
230 | "storeId": null,
231 | "value": "179724F9D38569CC34AB362AD2E768CC"
232 | },
233 | {
234 | "domain": ".bing.com",
235 | "expirationDate": 1713252288.202229,
236 | "hostOnly": false,
237 | "httpOnly": true,
238 | "name": "USRLOC",
239 | "path": "/",
240 | "sameSite": "no_restriction",
241 | "secure": true,
242 | "session": false,
243 | "storeId": null,
244 | "value": "HS=1"
245 | },
246 | {
247 | "domain": ".bing.com",
248 | "expirationDate": 1713435910.701659,
249 | "hostOnly": false,
250 | "httpOnly": false,
251 | "name": "_HPVN",
252 | "path": "/",
253 | "sameSite": "no_restriction",
254 | "secure": true,
255 | "session": false,
256 | "storeId": null,
257 | "value": "CS=eyJQbiI6eyJDbiI6MywiU3QiOjAsIlFzIjowLCJQcm9kIjoiUCJ9LCJTYyI6eyJDbiI6MywiU3QiOjAsIlFzIjowLCJQcm9kIjoiSCJ9LCJReiI6eyJDbiI6MywiU3QiOjAsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyMy0wMy0xNVQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIkRmdCI6bnVsbCwiTXZzIjowLCJGbHQiOjAsIkltcCI6NX0="
258 | },
259 | {
260 | "domain": ".bing.com",
261 | "expirationDate": 1712388289.713958,
262 | "hostOnly": false,
263 | "httpOnly": false,
264 | "name": "MUID",
265 | "path": "/",
266 | "sameSite": "no_restriction",
267 | "secure": true,
268 | "session": false,
269 | "storeId": null,
270 | "value": "179724F9D38569CC34AB362AD2E768CC"
271 | },
272 | {
273 | "domain": ".bing.com",
274 | "expirationDate": 1713252288.20225,
275 | "hostOnly": false,
276 | "httpOnly": false,
277 | "name": "SRCHUID",
278 | "path": "/",
279 | "sameSite": "no_restriction",
280 | "secure": true,
281 | "session": false,
282 | "storeId": null,
283 | "value": "V=2&GUID=A6A61EF487C34A89B9310787D6436BF1&dmnchg=1"
284 | },
285 | {
286 | "domain": ".bing.com",
287 | "hostOnly": false,
288 | "httpOnly": false,
289 | "name": "WLS",
290 | "path": "/",
291 | "sameSite": "no_restriction",
292 | "secure": true,
293 | "session": true,
294 | "storeId": null,
295 | "value": "C=d098a0631aa90c9b&N=%e5%8b%87"
296 | }
297 | ]
298 |
--------------------------------------------------------------------------------
/test/aichat-util-test.el:
--------------------------------------------------------------------------------
1 | ;;; aichat-util-test.el --- aichat-util-test.el -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat-util-test.el
4 | ;; Description: aichat-util-test.el
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-03-04 23:16:38
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-03-04 23:16:38
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat-util-test.el
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat-util-test.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat-util-test)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat-util-test RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/03/04
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 | (require 'ert)
84 | (require 'async-await)
85 |
86 | (add-to-list 'load-path (expand-file-name ".." default-directory))
87 |
88 | (require 'aichat-util)
89 |
90 | ;;; Hack
91 |
92 | (defun promise-wait (timeout promise)
93 | "Return promise to wait synchronously until PROMISE is resolved or rejected or TIMEOUT.
94 |
95 | Arguments:
96 | - TIMEOUT can accept the various formats. See `run-at-time'.
97 | - PROMISE is any promise object.
98 |
99 | Resolve:
100 | - Return (:fullfilled value), value is PROMISE resolved value.
101 |
102 | Reject:
103 | - Return (:rejected reason), reason is PROMISE rejected reason.
104 |
105 | Timeout:
106 | - Return (:timeouted)."
107 | (declare (indent 1))
108 | (catch 'done
109 | (let* (handled
110 | (timer (run-at-time timeout nil
111 | (lambda ()
112 | (unless handled
113 | (setq handled t)
114 | (throw 'done (promise-reject '(:timeouted))))))))
115 | (promise-then promise
116 | (lambda (value)
117 | (unless handled
118 | (setq handled t)
119 | (cancel-timer timer)
120 | (throw 'done (promise-resolve `(:fullfilled ,value)))))
121 | (lambda (reason)
122 | (unless handled
123 | (setq handled t)
124 | (cancel-timer timer)
125 | (throw 'done (promise-reject `(:rejected ,reason))))))
126 | (while t (sit-for 20)))))
127 |
128 | ;;; Code:
129 |
130 | (defun aichat-util-test-create-server-proc ()
131 | (let* ((datas)
132 | (server-file (expand-file-name "test/server.js" (file-name-directory (locate-library "aichat-util"))))
133 | (proc (start-process "event-server" nil "node" server-file)))
134 | (when proc
135 | (sleep-for 2)
136 | proc)))
137 |
138 | (ert-deftest aichat-debug ()
139 | (let ((buffer-name "*AICHAT-DEBUG*")
140 | (msg "debug message"))
141 | (let ((aichat-debug t))
142 | (aichat-debug msg)
143 | (should (string= (concat msg "\n") (with-current-buffer buffer-name
144 | (buffer-string))))
145 | (kill-buffer buffer-name))
146 | (let ((aichat-debug nil))
147 | (aichat-debug msg)
148 | (should (not (get-buffer buffer-name))))))
149 |
150 | (defconst aichat-util-test-json-str "{\"user\":\"xhcoding\",\"age\":1000,\"others\":[\"hello\",1],\"keys\":{\"key\":\"value\"}}")
151 |
152 | (ert-deftest aichat-json-serialize ()
153 | (let ((str "hel\"lo"))
154 | (should (string= "\"hel\\\"lo\"" (aichat-json-serialize str))))
155 | (should (string= aichat-util-test-json-str
156 | (aichat-json-serialize (list :user "xhcoding" :age 1000 :others ["hello" 1] :keys (list :key "value"))))))
157 |
158 | (ert-deftest aichat-json-parse ()
159 | (should (equal (aichat-json-parse aichat-util-test-json-str)
160 | '((user . "xhcoding") (age . 1000) (others . ["hello" 1]) (keys (key . "value"))))))
161 |
162 | (ert-deftest aichat-json-access ()
163 | (let ((object (aichat-json-parse aichat-util-test-json-str)))
164 | (should (string= "xhcoding" (aichat-json-access object "{user}")))
165 | (should (= 1000 (aichat-json-access object "{age}")))
166 | (should (string= "hello" (aichat-json-access object "{others}[0]")))
167 | (should (= 1 (aichat-json-access object "{others}[1]")))
168 | (should (string= "value" (aichat-json-access object "{keys}{key}")))
169 | (should (equal ["hello" 1] (aichat-json-access object "{others}")))))
170 |
171 | (ert-deftest aichat-get-cookies-from-file()
172 | (let ((cookies (aichat-get-cookies-from-file (expand-file-name "test/cookies.json" (file-name-directory (locate-library "aichat-util"))))))
173 | (cl-loop for cookie in cookies
174 | do (let ((name (car cookie))
175 | (value (cadr cookie)))
176 | (when (string= name "_U")
177 | (should (string= value "invalid, do not use this")))))))
178 |
179 |
180 | (defun aichat-http-get-with-backend (backend)
181 | (let ((proc (aichat-util-test-create-server-proc)))
182 | (seq-let (status headers body)
183 | (promise-wait-value
184 | (promise-wait 100
185 | (aichat-http "http://127.0.0.1:12345/get"
186 | :backend backend
187 | :params '(("hello". "world")))))
188 | (should (string= "200" (car status)))
189 | (let ((body-object (aichat-json-parse body)))
190 | (should (equal (aichat-json-access body-object "{params}") '((hello ."world"))))
191 | (should (string= (aichat-json-access body-object "{headers}{user-agent}") aichat-user-agent))))
192 | (delete-process proc)))
193 |
194 | (ert-deftest aichat-http-get ()
195 | (aichat-http-get-with-backend 'curl)
196 | (aichat-http-get-with-backend 'url))
197 |
198 | (defun aichat-http-get-with-proxy-with-backend (backend)
199 | (seq-let (status headers body)
200 | (should-error
201 | (promise-wait-value
202 | (promise-wait 100
203 | (aichat-http "https://httpbin.org/get"
204 | :backend backend
205 | :params '(("hello". "world"))
206 | :proxy "error-proxy"))))))
207 |
208 | (ert-deftest aichat-http-get-with-proxy ()
209 | (aichat-http-get-with-proxy-with-backend 'curl)
210 | (aichat-http-get-with-proxy-with-backend 'url))
211 |
212 | (defun aichat-http-get-with-cookie-with-backend (backend)
213 | (url-cookie-store "test" "aichat-http-get-with-cookie" nil "httpbin.org" "/" t)
214 | (let ((proc (aichat-util-test-create-server-proc)))
215 | (seq-let (status headers body)
216 | (promise-wait-value
217 | (promise-wait 100
218 | (aichat-http "http://127.0.0.1:12345/get"
219 | :backend 'curl
220 | :params '(("hello". "world")))))
221 | (should (string= "200" (car status)))
222 | (let ((body-object (aichat-json-parse body)))
223 | (should (equal (aichat-json-access body-object "{params}") '((hello ."world"))))
224 | (should (string= (aichat-json-access body-object "{headers}{user-agent}") aichat-user-agent))
225 | (should-not (string-empty-p (aichat-json-access body-object "{headers}{Cookie}")))))
226 | (delete-process proc)))
227 |
228 | (ert-deftest aichat-http-get-with-cookie ()
229 | (aichat-http-get-with-cookie-with-backend 'curl)
230 | (aichat-http-get-with-cookie-with-backend 'url))
231 |
232 | (defun aichat-http-post-with-backend (backend)
233 | (let ((data (aichat-json-serialize (list :model "gpt-3.5-turbo"
234 | :stream t
235 | :messages (vector
236 | (list :role "user"
237 | :content "Hello, 我是你的助理" )))))
238 | (proc (aichat-util-test-create-server-proc)))
239 | (seq-let (status headers body)
240 | (promise-wait-value
241 | (promise-wait 100
242 | (aichat-http "http://127.0.0.1:12345/post"
243 | :backend backend
244 | :type "POST"
245 | :headers '(("Content-Type" . "application/json")
246 | ("Authorization" . "Bearer sk-qZbRi"))
247 | :data data)))
248 | (should (string= "200" (car status)))
249 | (let ((body-object (aichat-json-parse body)))
250 | (should (string= data (aichat-json-access body-object "{body}")))))
251 | (delete-process proc)))
252 |
253 | (ert-deftest aichat-http-post ()
254 | (aichat-http-post-with-backend 'curl)
255 | (aichat-http-post-with-backend 'url))
256 |
257 | (defun aichat-http-event-source-with-backend (backend)
258 | (let* ((datas)
259 | (proc (aichat-util-test-create-server-proc)))
260 | (promise-wait 10 (aichat-http-event-source "http://127.0.0.1:12345/stream"
261 | (lambda (id data)
262 | (push (cons id data) datas))
263 | :backend backend))
264 | (should (= 3 (length datas)))
265 | (should (equal (cons "test" "this is data\nthis is new-line") (nth 0 datas)))
266 | (should (equal (cons "test" "this is data\nthis is new-line") (nth 1 datas)))
267 | (should (equal (cons "test" "this is data\nthis is new-line") (nth 2 datas)))
268 | (delete-process proc)))
269 |
270 | (ert-deftest aichat-http-event-source ()
271 | (aichat-http-event-source-with-backend 'curl)
272 | (aichat-http-event-source-with-backend 'url))
273 |
274 | (provide 'aichat-util-test)
275 |
276 | ;;; aichat-util-test.el ends here
277 |
--------------------------------------------------------------------------------
/aichat-openai.el:
--------------------------------------------------------------------------------
1 | ;;; aichat-openai.el --- aichat-openai -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat-openai.el
4 | ;; Description: aichat-openai
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-03-10 12:34:27
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-03-10 12:34:27
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat-openai
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat-openai.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat-openai)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat-openai RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/03/10
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 |
84 | (require 'aichat-util)
85 |
86 | (require 'markdown-mode)
87 |
88 | ;;; Code:
89 |
90 | (defgroup aichat-openai nil
91 | "OpenAI in Emacs."
92 | :group 'aichat
93 | :prefix "aichat-openai-")
94 |
95 | (defcustom aichat-openai-proxy nil
96 | "Http proxy of request openai api.
97 |
98 | (setq aichat-openai-proxy \"localhost:51837\")"
99 | :group 'aichat-openai
100 | :type 'string)
101 |
102 | (defun aichat-openai--default-api-key-function ()
103 | "Fetch the API key with auth-source."
104 | (auth-source-pick-first-password :host "platform.openai.com"))
105 |
106 | (defcustom aichat-openai-api-key #'aichat-openai--default-api-key-function
107 | "OpenAI key as a string or a function that loads and returns it."
108 | :group 'aichat-openai
109 | :type '(choice (function :tag "Function")
110 | (string :tag "String")))
111 |
112 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
113 |
114 | (defcustom aichat-openai-domain "https://api.openai.com"
115 | "OpenAI domain."
116 | :group 'aichat-openai
117 | :type 'string)
118 |
119 | (defconst aichat-openai--chat-completions-url "/v1/chat/completions" "The url of chat completions api.")
120 |
121 | (defconst aichat-openai--chat-completions-model "gpt-3.5-turbo" "The model of chat completions api.")
122 |
123 | (defconst aichat-openai--chat-completions-request-type "POST" "The request type of chat completions api.")
124 |
125 | (defun aichat-openai--make-http-headers ()
126 | "Create http headers to send in requests to the API."
127 | `(("Content-Type" . "application/json")
128 | ("Authorization" . ,(format "Bearer %s" (if (functionp aichat-openai-api-key)
129 | (funcall aichat-openai-api-key)
130 | aichat-openai-api-key)))))
131 |
132 | (cl-defun aichat-openai-make-chat-messages (&rest settings
133 | &key
134 | user
135 | system
136 | assistant)
137 | "Make chat completions messages.
138 |
139 | (aichat-openai--make-chat-messages
140 | :user \"hello\"
141 | :assistant \"Hello there, how may I assist you today?\")"
142 | (vconcat
143 | (cl-loop for (key value) on settings by #'cddr
144 | collect (list :role (string-trim-left (symbol-name key) ":") :content value))))
145 |
146 | (cl-defun aichat-openai-chat-completions (messages &rest settings
147 | &key
148 | on-success
149 | on-error
150 | &allow-other-keys
151 | )
152 | "Request openai chat completions api.
153 |
154 | Look https://platform.openai.com/docs/api-reference/chat."
155 |
156 | (let* ((args (cl-loop for (key value) on settings by #'cddr
157 | append (unless (member key '(:on-success :on-error))
158 | (list key value))))
159 | (data (aichat-json-serialize (append
160 | (list
161 | :model aichat-openai--chat-completions-model
162 | :messages messages)
163 | args))))
164 |
165 | (aichat-debug "Request %s with data:\n%s\n" aichat-openai--chat-completions-url data)
166 |
167 | (promise-then (aichat-http (concat aichat-openai-domain aichat-openai--chat-completions-url)
168 | :proxy aichat-openai-proxy
169 | :type aichat-openai--chat-completions-request-type
170 | :headers (aichat-openai--make-http-headers)
171 | :data data)
172 | (lambda (value)
173 | (aichat-debug "Received: %s" value)
174 | (seq-let (status headers body) value
175 | (if (string= "200" (car status))
176 | (when on-success
177 | (funcall on-success (aichat-json-parse body)))
178 | (when on-error
179 | (funcall on-error body)))))
180 | (lambda (err)
181 | (when on-error
182 | (funcall on-error err))))))
183 |
184 | (cl-defun aichat-openai-chat-completions-stream (messages callback &rest settings
185 | &key
186 | on-success
187 | on-error
188 | &allow-other-keys
189 | )
190 | "Request openai chat completions api with stream mode.
191 |
192 | Look https://platform.openai.com/docs/api-reference/chat for more request params."
193 | (let* ((args (cl-loop for (key value) on settings by #'cddr
194 | append (unless (member key '(:on-success :on-error))
195 | (list key value))))
196 | (data (aichat-json-serialize (append
197 | (list
198 | :model aichat-openai--chat-completions-model
199 | :messages messages
200 | :stream t)
201 | args))))
202 |
203 | (aichat-debug "Request %s with data:\n%s\n" aichat-openai--chat-completions-url data)
204 |
205 | (promise-then (aichat-http-event-source (concat aichat-openai-domain aichat-openai--chat-completions-url)
206 | (lambda (event-id event-data)
207 | (aichat-debug "Received event: %s, data: \n%s\n" event-id event-data)
208 | (unless (string= event-data "[DONE]")
209 | (when callback
210 | (funcall callback (aichat-json-parse event-data)))))
211 | :proxy aichat-openai-proxy
212 | :type aichat-openai--chat-completions-request-type
213 | :headers (aichat-openai--make-http-headers)
214 | :data data)
215 | (lambda (value)
216 | (seq-let (status headers body) value
217 | (if (string= "200" (car status))
218 | (when on-success
219 | (funcall on-success body))
220 | (when on-error
221 | (funcall on-error body)))))
222 | (lambda (err)
223 | (when on-error
224 | (funcall on-error err))))))
225 |
226 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Message API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
227 |
228 | (defun aichat-openai-chat-completions-content (msg)
229 | "[choices][0][message][content]"
230 | (aichat-json-access msg "{choices}[0]{message}{content}"))
231 |
232 | (defun aichat-openai-chat-completions-delta-content (msg)
233 | "[choices][0][delta][content]"
234 | (aichat-json-access msg "{choices}[0]{delta}{content}"))
235 |
236 | (defun aichat-openai-chat-completions-delta-role (msg)
237 | "[choices][0][delta][role]"
238 | (aichat-json-access msg "{choices}[0]{delta}{role}"))
239 |
240 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Chat ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
241 |
242 | (defcustom aichat-openai-chat-directory (expand-file-name "aichat/openai" user-emacs-directory)
243 | "The directory of save chat file."
244 | :group 'aichat-openai
245 | :type 'string)
246 |
247 | (defcustom aichat-openai-chat-display-function 'switch-to-buffer
248 | "The function of display chat buffer."
249 | :group 'aichat-openai
250 | :type 'symbol)
251 |
252 | (defun aichat-openai-chat--buffer-messages (buffer)
253 | "Make messages with BUFFER's content."
254 | (with-current-buffer buffer
255 | (save-mark-and-excursion
256 | (goto-char (point-min))
257 | (let ((messages (list))
258 | (role)
259 | (message-beg)
260 | (message-end)
261 | (message))
262 | (catch 'finished
263 | (while t
264 | (if (re-search-forward "^# \\([a-zA-Z]+\\)\n" nil t)
265 | (setq role (downcase (string-trim (match-string 1))))
266 | (throw 'finished nil))
267 | (setq message-beg (point))
268 | (if (re-search-forward "^#" nil t)
269 | (progn
270 | (backward-char)
271 | (setq message-end (point)))
272 | (setq message-end (point-max)))
273 | (setq message (string-trim (buffer-substring-no-properties message-beg message-end)))
274 | (push (intern (concat ":" role)) messages)
275 | (push message messages)))
276 | (reverse messages)))))
277 |
278 | (defun aichat-openai-chat--heading-messages (buffer)
279 | "Make messages with BUFFER's last heading content."
280 | (with-current-buffer buffer
281 | (save-mark-and-excursion
282 | (goto-char (point-max))
283 | (when (re-search-backward "^# " nil t)
284 | (save-restriction
285 | (narrow-to-region (point) (point-max))
286 | (aichat-openai-chat--buffer-messages (current-buffer)))))))
287 |
288 | (defun aichat-openai-chat--send-messages (messages buffer)
289 | "Send MESSAGES to OpenAI, insert response to BUFFER."
290 | (aichat-openai-chat-completions-stream
291 | (apply #'aichat-openai-make-chat-messages messages)
292 | (lambda (msg)
293 | (let ((delta-role (aichat-openai-chat-completions-delta-role msg))
294 | (delta-content (aichat-openai-chat-completions-delta-content msg)))
295 | (when (buffer-live-p buffer)
296 | (with-current-buffer buffer
297 | (save-mark-and-excursion
298 | (goto-char (point-max))
299 | (when delta-role
300 | (insert (format "\n# %s%s\n\n" (upcase (substring delta-role 0 1)) (substring delta-role 1))))
301 | (when delta-content
302 | (insert delta-content)))))))
303 | :on-success (lambda (_)
304 | (when (buffer-live-p buffer)
305 | (with-current-buffer buffer
306 | (goto-char (point-max))
307 | (insert "\n\n# User\n\n"))))
308 | :on-error (lambda (err)
309 | (message "error: %s"err))))
310 |
311 | (defun aichat-openai-chat-send-buffer ()
312 | "Send current buffer content to OpenAI."
313 | (interactive)
314 | (when-let* ((buffer (current-buffer))
315 | (messages (aichat-openai-chat--buffer-messages buffer)))
316 | (aichat-openai-chat--send-messages messages buffer)))
317 |
318 | (defun aichat-openai-chat-send-last-heading ()
319 | "Send last heading content to OpenAI."
320 | (interactive)
321 | (when-let* ((buffer (current-buffer))
322 | (messages (aichat-openai-chat--heading-messages buffer)))
323 | (aichat-openai-chat--send-messages messages buffer)))
324 |
325 | ;;;###autoload
326 | (add-to-list 'auto-mode-alist '("\\.aichat\\'" . aichat-openai-chat-mode))
327 |
328 | ;;;###autoload
329 | (define-derived-mode aichat-openai-chat-mode markdown-mode "aichat-openai-chat"
330 | "Major mode for openai chat."
331 | (setq-local markdown-hide-markup t)
332 | (when (string-empty-p (buffer-string))
333 | (insert "# User\n"))
334 | (define-key aichat-openai-chat-mode-map (kbd "C-c C-c") #'aichat-openai-chat-send-buffer)
335 | (define-key aichat-openai-chat-mode-map (kbd "C-c C-l") #'aichat-openai-chat-send-last-heading))
336 |
337 | ;;;###autoload
338 | (defun aichat-openai-chat (buffer)
339 | "If the chat buffer is not found, create a new chat buffer, otherwise switch to the opened chat buffer."
340 | (interactive (list (let ((opened-buffers (cl-loop for buffer in (buffer-list)
341 | append (with-current-buffer buffer
342 | (when (derived-mode-p 'aichat-openai-chat-mode)
343 | (list (buffer-name)))))))
344 | (if (and opened-buffers (not (and (car current-prefix-arg)
345 | (= (car current-prefix-arg) 4))))
346 | (if (= 1 (length opened-buffers))
347 | (car opened-buffers)
348 | (completing-read "Select buffer: " opened-buffers))
349 | (when-let* ((name
350 | (completing-read "Chat name: "
351 | (if (file-exists-p aichat-openai-chat-directory)
352 | (directory-files aichat-openai-chat-directory nil
353 | "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)")
354 | (list))))
355 | (filename (expand-file-name (if (string-suffix-p ".aichat" name) name
356 | (concat name ".aichat"))
357 | aichat-openai-chat-directory)))
358 | (make-directory aichat-openai-chat-directory t)
359 | (find-file-noselect filename))))))
360 | (funcall aichat-openai-chat-display-function buffer))
361 |
362 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Assistant ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
363 |
364 | (defcustom aichat-openai-assistant-buffer "*Aichat-OpenAI-Assistant*"
365 | "The buffer of show assistant message."
366 | :group 'aichat
367 | :type 'string)
368 |
369 | (defcustom aichat-openai-assistant-display-function 'display-buffer
370 | "The function of display `aichat-openai-assistant-buffer'."
371 | :group 'aichat
372 | :type 'symbol)
373 |
374 | (defun aichat-openai-assistant-get-buffer ()
375 | (get-buffer-create aichat-openai-assistant-buffer))
376 |
377 | (defun aichat-openai-assistant (text)
378 | "Send the region or input to Bing and display the returned result to `aichat-openai-assistant-buffer'."
379 | (interactive (list (aichat-read-region-or-input "Input text: ")))
380 | (aichat-openai-chat-completions (aichat-openai-make-chat-messages
381 | :user text)
382 | :on-success (lambda (msg)
383 | (let ((content (aichat-openai-chat-completions-content msg))
384 | (buffer (aichat-openai-assistant-get-buffer)))
385 | (with-current-buffer buffer
386 | (goto-char (point-max))
387 | (insert content)
388 | (insert "\n\n"))
389 | (funcall aichat-openai-assistant-display-function buffer)))
390 | :on-error (lambda (err)
391 | (message "error: %s"err))))
392 |
393 | (defun aichat-openai-replace-or-insert (text)
394 | "Send the region or input to OpenAI and replace the selected region or insert at the current position with the returned result."
395 | (interactive (list (aichat-read-region-or-input "Input text: ")))
396 | (when (and text (not (string-empty-p text)))
397 | (let* ((cur-buf (current-buffer))
398 | (cur-pos (with-current-buffer cur-buf (point)))
399 | (reg-beg (when (use-region-p) (region-beginning)))
400 | (reg-end (when (use-region-p) (region-end))))
401 | (aichat-openai-chat-completions (aichat-openai-make-chat-messages
402 | :user text)
403 | :on-success (lambda (msg)
404 | (when-let ((content (aichat-openai-chat-completions-content msg)))
405 | (with-current-buffer cur-buf
406 | (if (and reg-beg reg-end)
407 | (replace-region-contents reg-beg reg-end (lambda () content))
408 | (goto-char cur-pos)
409 | (insert content)))))
410 | :on-error (lambda (err)
411 | (message "Error: %s" err))))))
412 |
413 | (cl-defmacro aichat-openai-prompt-create (name &rest args
414 | &key
415 | (input-prompt "Input text: ")
416 | (text-format "%s")
417 | (assistant nil)
418 | (replace-or-insert nil))
419 | "This macro will generate two functions: aichat-openai-assistant-name or aichat-openai-replace-or-insert-name.
420 |
421 | INPUT-PROMPT: The prompt before the user input in minibuffer.
422 | TEXT-FORMAT: Formating string, %s is replaced by what the user input."
423 | (let ((assistant-func (intern (format "aichat-openai-assistant-%s" name)))
424 | (replace-func (intern (format "aichat-openai-replace-or-insert-%s" name))))
425 | `(progn
426 | (when ,assistant
427 | (defun ,assistant-func(text)
428 | (interactive (list (aichat-read-region-or-input ,input-prompt)))
429 | (when text
430 | (let ((query (format ,text-format text)))
431 | (aichat-openai-assistant query)))))
432 | (when ,replace-or-insert
433 | (defun ,replace-func(text)
434 | (interactive (list (aichat-read-region-or-input ,input-prompt)))
435 | (when text
436 | (let ((query (format ,text-format text)))
437 | (aichat-openai-replace-or-insert query))))))))
438 |
439 | (provide 'aichat-openai)
440 |
441 | ;;; aichat-openai.el ends here
442 |
--------------------------------------------------------------------------------
/test/aichat-bingai-message-type-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": 2,
3 | "invocationId": "6",
4 | "item": {
5 | "messages": [
6 | {
7 | "text": "如何判断链表的长度?",
8 | "author": "user",
9 | "from": {
10 | "id": "1055518581560278",
11 | "name": null,
12 | "partnerId": null
13 | },
14 | "createdAt": "2023-11-18T04:05:13.1221273+00:00",
15 | "timestamp": "2023-11-18T11:35:37+08:00",
16 | "locale": "zh-CN",
17 | "market": "zh-AU",
18 | "region": "AU",
19 | "location": "lat:47.639557;long:-122.128159;re=1000m;",
20 | "locationInfo": {
21 | "country": "澳洲",
22 | "state": "New South Wales",
23 | "city": "悉尼中央商务区",
24 | "sourceType": 1
25 | },
26 | "locationHints": [
27 | {
28 | "countryConfidence": 9,
29 | "dma": 0,
30 | "sourceType": 1,
31 | "utcOffset": 10,
32 | "postCodeName": "2000",
33 | "populatedPlaceConfidence": 9,
34 | "populatedPlaceName": "Sydney",
35 | "admin1Name": "New South Wales",
36 | "countryName": "Australia",
37 | "fdConfidence": 0.9,
38 | "accuracy": 24902,
39 | "name": "Sydney, New South Wales",
40 | "radius": 24902,
41 | "center": {
42 | "latitude": -33.8683,
43 | "longitude": 151.2082,
44 | "height": null
45 | },
46 | "regionType": 2
47 | }
48 | ],
49 | "userIpAddress": "2a11:3:200::108c",
50 | "messageId": "77b13b73-8138-11f6-ff28-b0375509df59",
51 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
52 | "nlu": {
53 | "scoredClassification": {
54 | "classification": "DEEP_LEO",
55 | "score": null
56 | },
57 | "classificationRanking": [
58 | {
59 | "classification": "DEEP_LEO",
60 | "score": null
61 | }
62 | ],
63 | "qualifyingClassifications": null,
64 | "ood": null,
65 | "metaData": null,
66 | "entities": null
67 | },
68 | "offense": "None",
69 | "feedback": {
70 | "tag": null,
71 | "updatedOn": null,
72 | "type": "None"
73 | },
74 | "contentOrigin": "cib",
75 | "scores": [
76 | {
77 | "component": "UserOffense",
78 | "score": 0.00019260288
79 | },
80 | {
81 | "component": "suicide_help",
82 | "score": 0.016403032
83 | }
84 | ],
85 | "inputMethod": "Keyboard"
86 | },
87 | {
88 | "text": "Searching the web for: `如何判断链表的长度`",
89 | "hiddenText": "如何判断链表的长度",
90 | "author": "bot",
91 | "createdAt": "2023-11-18T04:05:17.5267446+00:00",
92 | "timestamp": "2023-11-18T04:05:17.5267446+00:00",
93 | "messageId": "ae3eb3f1-f874-4c8a-bea9-fa75e907c994",
94 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
95 | "messageType": "InternalSearchQuery",
96 | "offense": "None",
97 | "adaptiveCards": [
98 | {
99 | "type": "AdaptiveCard",
100 | "version": "1.0",
101 | "body": [
102 | {
103 | "type": "RichTextBlock",
104 | "inlines": [
105 | {
106 | "type": "TextRun",
107 | "isSubtle": true,
108 | "italic": true,
109 | "text": "Searching the web for: `如何判断链表的长度`"
110 | }
111 | ]
112 | }
113 | ]
114 | }
115 | ],
116 | "feedback": {
117 | "tag": null,
118 | "updatedOn": null,
119 | "type": "None"
120 | },
121 | "contentOrigin": "DeepLeo",
122 | "toolResultPublishPreference": "Never"
123 | },
124 | {
125 | "text": "{\"question_answering_results\":[{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\"],\"url\":\"https://bing.com/search?q=%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e9%93%be%e8%a1%a8%e7%9a%84%e9%95%bf%e5%ba%a6\"}],\"web_search_results\":[{\"title\":\"Java LinkedList | 菜鸟教程\",\"snippets\":[\"Java LinkedList | 菜鸟教程 Java LinkedList Java 集合框架 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。 链表可分为单向链表和双向链表。 一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。 LinkedList 实现了 Queue 接口,可作为队列使用。 LinkedList 实现了 List 接口,可进行列表的相关操作。 LinkedList 实现了 Deque 接口,可作为队列使用。 LinkedList 实现了 Cloneable 接口,可实现克隆。 LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。 LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下: // 引入 LinkedList 类 import java.util.LinkedList; LinkedList list = new LinkedList (); // 普通创建方法 或者 LinkedList list = new LinkedList (Collection extends E> c); // 使用集合创建链表 创建一个简单的链表实例: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Weibo] 更多的情况下我们使用 ArrayList 访问列表中的随机元素更加高效,但以下几种情况 LinkedList 提供了更高效的方法。 在列表开头添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addFirst () 在头部添加元素 sites. addFirst(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Wiki, Google, Runoob, Taobao] 在列表结尾添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addLast () 在尾部添加元素 sites. addLast(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Wiki] 在列表开头移除元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeFirst(); System. out. println( sites); } } 以上实例,执行输出结果为: [Runoob, Taobao, Weibo] 在列表结尾移除元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeLast(); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao] 获取列表开头的元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites. getFirst()); } } 以上实例,执行输出结果为:\",\"一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。\"],\"url\":\"https://www.runoob.com/java/java-linkedlist.html\"},{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"C++程序 寻找链表长度|极客笔记 当前位置: 极客笔记 > C++ > C++ 示例 > C++程序 寻找链表长度 C++程序 寻找链表长度 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个 C ++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表定义示例,包含了节点的数据类型和链表的数据类型: struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } }; 计算链表长度 要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示: int getLengthRecursion(ListNode *node) { if (node == NULL) { return 0; } else { return 1 + getLengthRecursion(node->next); } } 函数中如果节点为空,则长度为0;否则不断递归到下一个节点,一直到最后一个节点,返回的是递归到该节点的长度+1。 使用示例 我们可以编写一个主函数,来验证计算链表长度的函数是否正确。 下面是一个使用示例,包含了链表的添加和输出功能,以及计算链表长度的功能: #include using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } void addNode(int val) { if (head == NULL) { head = new ListNode(val); } else { ListNode *p = head; while (p->next != NULL) p = p->next; p->next = new ListNode(val); } } void printList() { ListNode *p = head; while (p != NULL) { cout << p->val << \\\" \\\"; p = p->next; } cout << endl; } }; int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } int main() { LinkedList list; list.addNode(1); list.addNode(2); list.addNode(3); list.addNode(4); list.addNode(5); list.printList(); cout << \\\"Length of the linked list: \\\" << getLength(&list) << endl; return 0; } 输出: 1 2 3 4 5 Length of the linked list: 5 结论 计算链表长度是一个简单而常用的操作,可以使用循环和递归两种方法来实现。 两种方法虽然代码不同,但本质相同,都是遍历整个链表,统计节点数量。 在实际应用中,要根据具体情况选择不同的方法。 上一篇 C++程序 查找两个链表交点 下一篇 C++程序 寻找给定和的子数组-第1集(非负数) Camera课程 API2 教程 Perf 教程 Native FW 教程 ISP 教程 Sensor 教程 Python教程 Python 教程 NumPy 教程 Django 教程 Java教程 Java 教程 Web教程 JavaScript 教程 jQuery 教程 CSS 教程 PHP 教程 Laravel 教程 TypeScript 教程 WordPress 教程 数据库教程 SQL 教程 SQLite 教程 MySQL 教程 PL/SQL 教程 MongoDB 教程 图形图像教程 OpenCV 教程 办公软件教程 Excel 教程 Linux教程 Linux命令全集 Linux内核API LVGL 教程 计算机教程 操作系统 教程 计算机网络 教程 C语言 教程 C++ 教程 Swift 教程 大数据教程 Hadoop 教程 Spark 教程 Scala 教程 开发工具教程 Git 教程 Jenkins 教程 ChatGPT 教程 IntelliJ 教程 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个C++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表\",\"下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\",\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示:\"],\"url\":\"https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html\"}]}",
126 | "hiddenText": "search_web('如何判断链表的长度') was invoked and returned:\n```json\n{\"question_answering_results\":[{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\"],\"url\":\"https://bing.com/search?q=%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e9%93%be%e8%a1%a8%e7%9a%84%e9%95%bf%e5%ba%a6\"}],\"web_search_results\":[{\"title\":\"Java LinkedList | 菜鸟教程\",\"snippets\":[\"Java LinkedList | 菜鸟教程 Java LinkedList Java 集合框架 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。 链表可分为单向链表和双向链表。 一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。 LinkedList 实现了 Queue 接口,可作为队列使用。 LinkedList 实现了 List 接口,可进行列表的相关操作。 LinkedList 实现了 Deque 接口,可作为队列使用。 LinkedList 实现了 Cloneable 接口,可实现克隆。 LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。 LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下: // 引入 LinkedList 类 import java.util.LinkedList; LinkedList list = new LinkedList (); // 普通创建方法 或者 LinkedList list = new LinkedList (Collection extends E> c); // 使用集合创建链表 创建一个简单的链表实例: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Weibo] 更多的情况下我们使用 ArrayList 访问列表中的随机元素更加高效,但以下几种情况 LinkedList 提供了更高效的方法。 在列表开头添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addFirst () 在头部添加元素 sites. addFirst(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Wiki, Google, Runoob, Taobao] 在列表结尾添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addLast () 在尾部添加元素 sites. addLast(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Wiki] 在列表开头移除元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeFirst(); System. out. println( sites); } } 以上实例,执行输出结果为: [Runoob, Taobao, Weibo] 在列表结尾移除元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeLast(); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao] 获取列表开头的元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites. getFirst()); } } 以上实例,执行输出结果为:\",\"一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。\"],\"url\":\"https://www.runoob.com/java/java-linkedlist.html\"},{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"C++程序 寻找链表长度|极客笔记 当前位置: 极客笔记 > C++ > C++ 示例 > C++程序 寻找链表长度 C++程序 寻找链表长度 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个 C ++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表定义示例,包含了节点的数据类型和链表的数据类型: struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } }; 计算链表长度 要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示: int getLengthRecursion(ListNode *node) { if (node == NULL) { return 0; } else { return 1 + getLengthRecursion(node->next); } } 函数中如果节点为空,则长度为0;否则不断递归到下一个节点,一直到最后一个节点,返回的是递归到该节点的长度+1。 使用示例 我们可以编写一个主函数,来验证计算链表长度的函数是否正确。 下面是一个使用示例,包含了链表的添加和输出功能,以及计算链表长度的功能: #include using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } void addNode(int val) { if (head == NULL) { head = new ListNode(val); } else { ListNode *p = head; while (p->next != NULL) p = p->next; p->next = new ListNode(val); } } void printList() { ListNode *p = head; while (p != NULL) { cout << p->val << \\\" \\\"; p = p->next; } cout << endl; } }; int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } int main() { LinkedList list; list.addNode(1); list.addNode(2); list.addNode(3); list.addNode(4); list.addNode(5); list.printList(); cout << \\\"Length of the linked list: \\\" << getLength(&list) << endl; return 0; } 输出: 1 2 3 4 5 Length of the linked list: 5 结论 计算链表长度是一个简单而常用的操作,可以使用循环和递归两种方法来实现。 两种方法虽然代码不同,但本质相同,都是遍历整个链表,统计节点数量。 在实际应用中,要根据具体情况选择不同的方法。 上一篇 C++程序 查找两个链表交点 下一篇 C++程序 寻找给定和的子数组-第1集(非负数) Camera课程 API2 教程 Perf 教程 Native FW 教程 ISP 教程 Sensor 教程 Python教程 Python 教程 NumPy 教程 Django 教程 Java教程 Java 教程 Web教程 JavaScript 教程 jQuery 教程 CSS 教程 PHP 教程 Laravel 教程 TypeScript 教程 WordPress 教程 数据库教程 SQL 教程 SQLite 教程 MySQL 教程 PL/SQL 教程 MongoDB 教程 图形图像教程 OpenCV 教程 办公软件教程 Excel 教程 Linux教程 Linux命令全集 Linux内核API LVGL 教程 计算机教程 操作系统 教程 计算机网络 教程 C语言 教程 C++ 教程 Swift 教程 大数据教程 Hadoop 教程 Spark 教程 Scala 教程 开发工具教程 Git 教程 Jenkins 教程 ChatGPT 教程 IntelliJ 教程 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个C++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表\",\"下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\",\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示:\"],\"url\":\"https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html\"}]}\n```",
127 | "author": "bot",
128 | "createdAt": "2023-11-18T04:05:17.8441733+00:00",
129 | "timestamp": "2023-11-18T04:05:17.8441733+00:00",
130 | "messageId": "0fedf8a8-2d62-4f37-b3ce-f5378e798356",
131 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
132 | "messageType": "InternalSearchResult",
133 | "offense": "None",
134 | "adaptiveCards": [
135 | {
136 | "type": "AdaptiveCard",
137 | "version": "1.0",
138 | "body": [
139 | {
140 | "type": "RichTextBlock",
141 | "inlines": [
142 | {
143 | "type": "TextRun",
144 | "isSubtle": true,
145 | "italic": true,
146 | "text": "{\"question_answering_results\":[{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\"],\"url\":\"https://bing.com/search?q=%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e9%93%be%e8%a1%a8%e7%9a%84%e9%95%bf%e5%ba%a6\"}],\"web_search_results\":[{\"title\":\"Java LinkedList | 菜鸟教程\",\"snippets\":[\"Java LinkedList | 菜鸟教程 Java LinkedList Java 集合框架 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。 链表可分为单向链表和双向链表。 一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。 LinkedList 实现了 Queue 接口,可作为队列使用。 LinkedList 实现了 List 接口,可进行列表的相关操作。 LinkedList 实现了 Deque 接口,可作为队列使用。 LinkedList 实现了 Cloneable 接口,可实现克隆。 LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。 LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下: // 引入 LinkedList 类 import java.util.LinkedList; LinkedList list = new LinkedList (); // 普通创建方法 或者 LinkedList list = new LinkedList (Collection extends E> c); // 使用集合创建链表 创建一个简单的链表实例: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Weibo] 更多的情况下我们使用 ArrayList 访问列表中的随机元素更加高效,但以下几种情况 LinkedList 提供了更高效的方法。 在列表开头添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addFirst () 在头部添加元素 sites. addFirst(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Wiki, Google, Runoob, Taobao] 在列表结尾添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); // 使用 addLast () 在尾部添加元素 sites. addLast(\\\"Wiki\\\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Wiki] 在列表开头移除元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeFirst(); System. out. println( sites); } } 以上实例,执行输出结果为: [Runoob, Taobao, Weibo] 在列表结尾移除元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); sites. removeLast(); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao] 获取列表开头的元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\\\"Google\\\"); sites. add(\\\"Runoob\\\"); sites. add(\\\"Taobao\\\"); sites. add(\\\"Weibo\\\"); System. out. println( sites. getFirst()); } } 以上实例,执行输出结果为:\",\"一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。\"],\"url\":\"https://www.runoob.com/java/java-linkedlist.html\"},{\"title\":\"C++程序 寻找链表长度|极客笔记\",\"snippets\":[\"C++程序 寻找链表长度|极客笔记 当前位置: 极客笔记 > C++ > C++ 示例 > C++程序 寻找链表长度 C++程序 寻找链表长度 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个 C ++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表定义示例,包含了节点的数据类型和链表的数据类型: struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } }; 计算链表长度 要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示: int getLengthRecursion(ListNode *node) { if (node == NULL) { return 0; } else { return 1 + getLengthRecursion(node->next); } } 函数中如果节点为空,则长度为0;否则不断递归到下一个节点,一直到最后一个节点,返回的是递归到该节点的长度+1。 使用示例 我们可以编写一个主函数,来验证计算链表长度的函数是否正确。 下面是一个使用示例,包含了链表的添加和输出功能,以及计算链表长度的功能: #include using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } void addNode(int val) { if (head == NULL) { head = new ListNode(val); } else { ListNode *p = head; while (p->next != NULL) p = p->next; p->next = new ListNode(val); } } void printList() { ListNode *p = head; while (p != NULL) { cout << p->val << \\\" \\\"; p = p->next; } cout << endl; } }; int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } int main() { LinkedList list; list.addNode(1); list.addNode(2); list.addNode(3); list.addNode(4); list.addNode(5); list.printList(); cout << \\\"Length of the linked list: \\\" << getLength(&list) << endl; return 0; } 输出: 1 2 3 4 5 Length of the linked list: 5 结论 计算链表长度是一个简单而常用的操作,可以使用循环和递归两种方法来实现。 两种方法虽然代码不同,但本质相同,都是遍历整个链表,统计节点数量。 在实际应用中,要根据具体情况选择不同的方法。 上一篇 C++程序 查找两个链表交点 下一篇 C++程序 寻找给定和的子数组-第1集(非负数) Camera课程 API2 教程 Perf 教程 Native FW 教程 ISP 教程 Sensor 教程 Python教程 Python 教程 NumPy 教程 Django 教程 Java教程 Java 教程 Web教程 JavaScript 教程 jQuery 教程 CSS 教程 PHP 教程 Laravel 教程 TypeScript 教程 WordPress 教程 数据库教程 SQL 教程 SQLite 教程 MySQL 教程 PL/SQL 教程 MongoDB 教程 图形图像教程 OpenCV 教程 办公软件教程 Excel 教程 Linux教程 Linux命令全集 Linux内核API LVGL 教程 计算机教程 操作系统 教程 计算机网络 教程 C语言 教程 C++ 教程 Swift 教程 大数据教程 Hadoop 教程 Spark 教程 Scala 教程 开发工具教程 Git 教程 Jenkins 教程 ChatGPT 教程 IntelliJ 教程 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个C++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表\",\"下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。\",\"要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示:\"],\"url\":\"https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html\"}]}"
147 | }
148 | ]
149 | }
150 | ]
151 | }
152 | ],
153 | "feedback": {
154 | "tag": null,
155 | "updatedOn": null,
156 | "type": "None"
157 | },
158 | "contentOrigin": "DeepLeo",
159 | "invocation": "search_web(\"如何判断链表的长度\")",
160 | "groundingInfo": {
161 | "question_answering_results": [
162 | {
163 | "index": null,
164 | "title": "C++程序 寻找链表长度|极客笔记",
165 | "snippets": [
166 | "要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。"
167 | ],
168 | "data": null,
169 | "context": null,
170 | "url": "https://bing.com/search?q=%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e9%93%be%e8%a1%a8%e7%9a%84%e9%95%bf%e5%ba%a6",
171 | "lastUpdatedDate": null
172 | }
173 | ],
174 | "web_search_results": [
175 | {
176 | "index": null,
177 | "title": "Java LinkedList | 菜鸟教程",
178 | "snippets": [
179 | "Java LinkedList | 菜鸟教程 Java LinkedList Java 集合框架 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。 链表可分为单向链表和双向链表。 一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。 LinkedList 实现了 Queue 接口,可作为队列使用。 LinkedList 实现了 List 接口,可进行列表的相关操作。 LinkedList 实现了 Deque 接口,可作为队列使用。 LinkedList 实现了 Cloneable 接口,可实现克隆。 LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。 LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下: // 引入 LinkedList 类 import java.util.LinkedList; LinkedList list = new LinkedList (); // 普通创建方法 或者 LinkedList list = new LinkedList (Collection extends E> c); // 使用集合创建链表 创建一个简单的链表实例: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); sites. add(\"Weibo\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Weibo] 更多的情况下我们使用 ArrayList 访问列表中的随机元素更加高效,但以下几种情况 LinkedList 提供了更高效的方法。 在列表开头添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); // 使用 addFirst () 在头部添加元素 sites. addFirst(\"Wiki\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Wiki, Google, Runoob, Taobao] 在列表结尾添加元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); // 使用 addLast () 在尾部添加元素 sites. addLast(\"Wiki\"); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao, Wiki] 在列表开头移除元素: 实例 // 引入 LinkedList 类 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); sites. add(\"Weibo\"); sites. removeFirst(); System. out. println( sites); } } 以上实例,执行输出结果为: [Runoob, Taobao, Weibo] 在列表结尾移除元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); sites. add(\"Weibo\"); sites. removeLast(); System. out. println( sites); } } 以上实例,执行输出结果为: [Google, Runoob, Taobao] 获取列表开头的元素: 实例 import java.util.LinkedList; public class RunoobTest { public static void main (String[] args) { LinkedList < String > sites = new LinkedList < String >(); sites. add(\"Google\"); sites. add(\"Runoob\"); sites. add(\"Taobao\"); sites. add(\"Weibo\"); System. out. println( sites. getFirst()); } } 以上实例,执行输出结果为:",
180 | "一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。 Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。 与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。 以下情况使用 ArrayList : 频繁访问列表中的某一个元素。 只需要在列表末尾进行添加和删除元素操作。 以下情况使用 LinkedList : 你需要通过循环迭代来访问列表中的某些元素。 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。 LinkedList 继承了 AbstractSequentialList 类。"
181 | ],
182 | "data": null,
183 | "context": null,
184 | "url": "https://www.runoob.com/java/java-linkedlist.html",
185 | "lastUpdatedDate": null
186 | },
187 | {
188 | "index": null,
189 | "title": "C++程序 寻找链表长度|极客笔记",
190 | "snippets": [
191 | "C++程序 寻找链表长度|极客笔记 当前位置: 极客笔记 > C++ > C++ 示例 > C++程序 寻找链表长度 C++程序 寻找链表长度 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个 C ++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表定义示例,包含了节点的数据类型和链表的数据类型: struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } }; 计算链表长度 要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示: int getLengthRecursion(ListNode *node) { if (node == NULL) { return 0; } else { return 1 + getLengthRecursion(node->next); } } 函数中如果节点为空,则长度为0;否则不断递归到下一个节点,一直到最后一个节点,返回的是递归到该节点的长度+1。 使用示例 我们可以编写一个主函数,来验证计算链表长度的函数是否正确。 下面是一个使用示例,包含了链表的添加和输出功能,以及计算链表长度的功能: #include using namespace std; struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; class LinkedList { public: ListNode *head; LinkedList() { head = NULL; } void addNode(int val) { if (head == NULL) { head = new ListNode(val); } else { ListNode *p = head; while (p->next != NULL) p = p->next; p->next = new ListNode(val); } } void printList() { ListNode *p = head; while (p != NULL) { cout << p->val << \" \"; p = p->next; } cout << endl; } }; int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } int main() { LinkedList list; list.addNode(1); list.addNode(2); list.addNode(3); list.addNode(4); list.addNode(5); list.printList(); cout << \"Length of the linked list: \" << getLength(&list) << endl; return 0; } 输出: 1 2 3 4 5 Length of the linked list: 5 结论 计算链表长度是一个简单而常用的操作,可以使用循环和递归两种方法来实现。 两种方法虽然代码不同,但本质相同,都是遍历整个链表,统计节点数量。 在实际应用中,要根据具体情况选择不同的方法。 上一篇 C++程序 查找两个链表交点 下一篇 C++程序 寻找给定和的子数组-第1集(非负数) Camera课程 API2 教程 Perf 教程 Native FW 教程 ISP 教程 Sensor 教程 Python教程 Python 教程 NumPy 教程 Django 教程 Java教程 Java 教程 Web教程 JavaScript 教程 jQuery 教程 CSS 教程 PHP 教程 Laravel 教程 TypeScript 教程 WordPress 教程 数据库教程 SQL 教程 SQLite 教程 MySQL 教程 PL/SQL 教程 MongoDB 教程 图形图像教程 OpenCV 教程 办公软件教程 Excel 教程 Linux教程 Linux命令全集 Linux内核API LVGL 教程 计算机教程 操作系统 教程 计算机网络 教程 C语言 教程 C++ 教程 Swift 教程 大数据教程 Hadoop 教程 Spark 教程 Scala 教程 开发工具教程 Git 教程 Jenkins 教程 ChatGPT 教程 IntelliJ 教程 C++程序 寻找链表长度 链表是一种常用的数据结构,它不像数组一样需要预先分配一定大小的内存,可以随时添加和删除数据。 但是,链表的一个缺点是不方便查找长度。 因此,本文将介绍如何编写一个C++程序来计算链表的长度。 链表的定义 链表是一组节点组成的集合,每个节点都包含两个部分,一个是数据部分,另一个是指向下一个节点的指针。 链表的头节点是第一个节点,也就是整个链表的起点。 下面是一个简单的链表",
192 | "下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。",
193 | "要计算链表的长度,我们只需要从头节点开始顺序遍历整个链表,直到遇到空节点为止。 为了方便起见,我们可以编写一个函数来计算链表长度。 下面是使用循环的方法来实现计算链表长度的函数: int getLength(LinkedList *list) { int len = 0; ListNode *p = list->head; while (p != NULL) { ++len; p = p->next; } return len; } 函数中使用一个计数器len来记录链表的长度,指针p用来指向当前节点。 循环中不断将p指向下一个节点,直到p指向空节点为止,此时链表长度为len。 我们也可以使用递归的方法来计算链表长度,如下所示:"
194 | ],
195 | "data": null,
196 | "context": null,
197 | "url": "https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html",
198 | "lastUpdatedDate": null
199 | }
200 | ]
201 | }
202 | },
203 | {
204 | "text": "Generating answers for you...",
205 | "hiddenText": "Generating answers for you...",
206 | "author": "bot",
207 | "createdAt": "2023-11-18T04:05:23.8468865+00:00",
208 | "timestamp": "2023-11-18T04:05:23.8468865+00:00",
209 | "messageId": "cc216f3b-e380-426b-8a4f-4d0b94530b9f",
210 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
211 | "messageType": "InternalLoaderMessage",
212 | "offense": "None",
213 | "adaptiveCards": [
214 | {
215 | "type": "AdaptiveCard",
216 | "version": "1.0",
217 | "body": [
218 | {
219 | "type": "RichTextBlock",
220 | "inlines": [
221 | {
222 | "type": "TextRun",
223 | "isSubtle": true,
224 | "italic": true,
225 | "text": "Generating answers for you..."
226 | }
227 | ]
228 | }
229 | ]
230 | }
231 | ],
232 | "feedback": {
233 | "tag": null,
234 | "updatedOn": null,
235 | "type": "None"
236 | },
237 | "contentOrigin": "DeepLeo"
238 | },
239 | {
240 | "text": "判断链表的长度是一个常见的数据结构和算法问题。有多种方法可以解决这个问题,我为你找到了一些相关的资料,你可以参考一下:\n\n- 一种方法是使用**循环**,即定义一个计数器和一个指针,从头节点开始遍历链表,每遍历一个节点,计数器加一,直到指针为空为止,此时计数器的值就是链表的长度[^3^] [^2^]。这种方法的时间复杂度是O (n),空间复杂度是O (1)。\n- 另一种方法是使用**递归**,即定义一个递归函数,如果节点为空,返回0,否则返回1加上对下一个节点的递归调用,最终返回的值就是链表的长度[^3^]。这种方法的时间复杂度也是O (n),但空间复杂度是O (n),因为需要栈空间来存储递归调用。\n\n以上是我能找到的一些判断链表长度的方法,你可以根据自己的需要选择合适的方法。如果你还有其他问题,欢迎继续和我聊天。😊",
241 | "author": "bot",
242 | "createdAt": "2023-11-18T04:05:23.8469882+00:00",
243 | "timestamp": "2023-11-18T04:05:23.8469882+00:00",
244 | "messageId": "53ff8e54-7180-4196-bccc-7e4d44a84ec9",
245 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
246 | "offense": "None",
247 | "adaptiveCards": [
248 | {
249 | "type": "AdaptiveCard",
250 | "version": "1.0",
251 | "body": [
252 | {
253 | "type": "TextBlock",
254 | "text": "[1]: https://bing.com/search?q=%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E9%93%BE%E8%A1%A8%E7%9A%84%E9%95%BF%E5%BA%A6 \"\"\n[2]: https://www.runoob.com/java/java-linkedlist.html \"\"\n[3]: https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html \"\"\n\n判断链表的长度是一个常见的数据结构和算法问题。有多种方法可以解决这个问题,我为你找到了一些相关的资料,你可以参考一下:\n\n- 一种方法是使用**循环**,即定义一个计数器和一个指针,从头节点开始遍历链表,每遍历一个节点,计数器加一,直到指针为空为止,此时计数器的值就是链表的长度[^1^][3] [^2^][2]。这种方法的时间复杂度是O (n),空间复杂度是O (1)。\n- 另一种方法是使用**递归**,即定义一个递归函数,如果节点为空,返回0,否则返回1加上对下一个节点的递归调用,最终返回的值就是链表的长度[^1^][3]。这种方法的时间复杂度也是O (n),但空间复杂度是O (n),因为需要栈空间来存储递归调用。\n\n以上是我能找到的一些判断链表长度的方法,你可以根据自己的需要选择合适的方法。如果你还有其他问题,欢迎继续和我聊天。😊",
255 | "wrap": true
256 | },
257 | {
258 | "type": "TextBlock",
259 | "size": "small",
260 | "text": "Learn more: [1. deepinout.com](https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html) [2. www.runoob.com](https://www.runoob.com/java/java-linkedlist.html) [3. bing.com](https://bing.com/search?q=%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E9%93%BE%E8%A1%A8%E7%9A%84%E9%95%BF%E5%BA%A6)",
261 | "wrap": true
262 | }
263 | ]
264 | }
265 | ],
266 | "sourceAttributions": [
267 | {
268 | "providerDisplayName": "C++程序 寻找链表长度|极客笔记",
269 | "seeMoreUrl": "https://deepinout.com/cpp/cpp-examples/g_cpp-program-for-finding-length-of-a-linked-list-iterative-and-recursive-approach.html",
270 | "urlSignature": "qAlH6lfdP4W8Swbx0h0nZi63iGlXt8HShFnPhoyUmlw=",
271 | "searchQuery": "如何判断链表的长度",
272 | "provider": "search_web"
273 | },
274 | {
275 | "providerDisplayName": "Java LinkedList | 菜鸟教程",
276 | "seeMoreUrl": "https://www.runoob.com/java/java-linkedlist.html",
277 | "urlSignature": "jVNX0SPyMA5Rcb+I2Sw/KPkpY8UhkqB9Tt3mycTtw+U=",
278 | "imageLink": "https://bing.com/th?id=OIP.LCbOxeg1Pk0boiBVDu7X0wHaFj&pid=cdx",
279 | "imageWidth": "681",
280 | "imageHeight": "511",
281 | "imageFavicon": "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVOSURBVFhHrVdpTFxVFB63xPWHP/3XPxqNsbHRRFOjNrGNSxvmvRmWAqVsphtgw1bbAAOE2oq1DDNQQ9vI0qCy1JLSVsSyKdubgVboyjIzLMOOSyUaTax+nvPmzTBDZ5hBOMmXee8u5/vOuefde0cVqGUj+/6CHvEpo1mzuVASkw2S8GmhSail3yp6P2yQxAR9t3oTj+GxyrTV28neXQ8ZJfXrRFZiMAn9hSbxNyL812AS4Q7qv1soCXeo/xo964tM2pd5ruJm5cZR6LuDXiGyMiL+eSmhX0jiPM0r1ZvULyouA7ey1uiHKZpEwrRX5ysAZcRmkDRRAWcjvyPoCVJeQGn805vD/4kFykja8a7gRxQa78aRMzlN+HuJg1VDDohEZLe++aBC52mA6j4amOSM3GheWyhCFvSSuFOh9DRjj+Y155rru0QkngrB9pywFSEsOwyhOnrODpURRm0hhP2lIaBidoiQBKvBrN2g0DrMUXRitTPyvAYtNoZHYP17kVi/NTC8QGPVsduRodPipbgEPLNfhyAhGslBUXgrOgJHmzRumRBKPZaisFvcwumRBfSIOFSrxQYhnJwStgaG598Nx95ULc6f2YZX0w5g3ccnsCckFhViJDZHhONwo9YlgDI9x/uETF5TE/IAFUe5QxlnQEDehQjsTD+EqJRMRKUGjpj0TOw6lIGoNB12fJiDaGp7PzUL8VkH8UlLGAyLtcBFqZcFFHZrnqb0WxY71Kj8MQXW0QGMjdkI1oAxyhhV3umX39nHbdsVnOqNY1KXAFqGft62VUZJCCcBf7kL+LIvHROTVsxM2TE9Nb4sZqbtMrz1MbjPar+G073xngIk8Vc+O1S0U2W5GpcI8OXQSThDsNksGBwe8CmEg/AmgJ7vkogEFRVEZSAC2PnszAT6rvdjcEghpPb8ks9RUlmF24O3cP3mDczRGHchvgTIXJKYp6KHGo/GJQKYhIkHiJSJIlMy0NbVhblZbrtN75lo+qEd3zS3YAcVXUVtHYYtQ/IcfwL4KF9WAE+2jVhQfrYOEckZiD+Yi7qGRoyPj8gCzjdeRoLuKEbkwrOhqv4ioukLiEnPRnX9JbmNs+FTAHGrWIV7o7sAjuLi5WZsitwNcW8qOs1mzM9Nyk6nKDqd/gROf1XjSvv87CSa29vxTlwS3o5JREtHB/VN+l4Ck3CMakDM82z0XAK7fQTftX2PfbojCE48QIS1cgau9vfJy2G+ekUWarUNo6j8C2j2pSHlo+No6+zEhH3U9xJIwj/EnbzsZ+gsJCYYoaWoOn8Re7OOoMMkobz2HD7IzZdJOAOXmppJ5FGca/hWTr2/GqDiv8PXO5W+W3iOOkaXEyA7mh6XiTjSIcsgkoj8DNUGt3G/xToki3S+u+b5LEJlI3LcAcQ6fwJcDrmoSERp9deuz9HRzmRexvvKAN0x+Qrg2I5NokjL8MeigDSfApxwEvuDNwHE9ZPepHlDJmc71rflMWqsV5Sh7OoejE7cwuz05BpgCpbxPjoLYt0FlN1zM+IjmTLxCw8oNofi0k09Ooer0TFUtSqwj/ob+XTKBjtTb+fbtkK7aKyIBOTSIPk+6FC7NnBFTtc9oyTuVijvteLWkMdpYBF/ozxhjUGBCQVc9Aqdd/usfeuTpLTYMcGroxWDI2dyvvIrNMubkokcZ02sBnzRJST6jXyp8T8Z+a4oiRcIv3tz7gcLVO3VBrN646r+rJ7s3fYo7xPksI5qY4ycurbtpaA+2kuEIUIFb7N+/wmtxDiFxl7Ns8rZkcenKK1trQy61BhNQia1qematS7wf8Uq1X83tUJh8W4nfQAAAABJRU5ErkJggg==",
282 | "searchQuery": "如何判断链表的长度",
283 | "mediaUniqueId": "",
284 | "sourceType": "CITATION",
285 | "provider": "search_web"
286 | },
287 | {
288 | "providerDisplayName": "C++程序 寻找链表长度|极客笔记",
289 | "seeMoreUrl": "https://bing.com/search?q=%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e9%93%be%e8%a1%a8%e7%9a%84%e9%95%bf%e5%ba%a6",
290 | "urlSignature": "hPQs1b/TLgfHUJpWQuxrsRmARGhlY8Suunk7Rx3xMyY=",
291 | "searchQuery": "如何判断链表的长度",
292 | "provider": "search_web"
293 | }
294 | ],
295 | "feedback": {
296 | "tag": null,
297 | "updatedOn": null,
298 | "type": "None"
299 | },
300 | "contentOrigin": "DeepLeo",
301 | "scores": [
302 | {
303 | "component": "BotOffense",
304 | "score": 0.0006825019
305 | },
306 | {
307 | "component": "BotOffense",
308 | "score": 0.0006851713
309 | },
310 | {
311 | "component": "BotOffense",
312 | "score": 0.00056588
313 | }
314 | ],
315 | "suggestedResponses": [
316 | {
317 | "text": "如何删除链表中的节点?",
318 | "author": "user",
319 | "createdAt": "2023-11-18T04:05:45.9910225+00:00",
320 | "timestamp": "2023-11-18T04:05:45.9910225+00:00",
321 | "messageId": "bb517bed-7b2a-4fa1-bbf0-c640c0d12ed8",
322 | "messageType": "Suggestion",
323 | "offense": "Unknown",
324 | "feedback": {
325 | "tag": null,
326 | "updatedOn": null,
327 | "type": "None"
328 | },
329 | "contentOrigin": "SuggestionChipsFalconService"
330 | },
331 | {
332 | "text": "什么是递归函数?",
333 | "author": "user",
334 | "createdAt": "2023-11-18T04:05:45.9910235+00:00",
335 | "timestamp": "2023-11-18T04:05:45.9910235+00:00",
336 | "messageId": "42bed100-ab08-49ab-8b38-55206320376d",
337 | "messageType": "Suggestion",
338 | "offense": "Unknown",
339 | "feedback": {
340 | "tag": null,
341 | "updatedOn": null,
342 | "type": "None"
343 | },
344 | "contentOrigin": "SuggestionChipsFalconService"
345 | },
346 | {
347 | "text": "有没有其他数据结构和算法问题可以学习?",
348 | "author": "user",
349 | "createdAt": "2023-11-18T04:05:45.9910242+00:00",
350 | "timestamp": "2023-11-18T04:05:45.9910242+00:00",
351 | "messageId": "c2a4f399-7503-442d-b8a6-1727f4fa4d03",
352 | "messageType": "Suggestion",
353 | "offense": "Unknown",
354 | "feedback": {
355 | "tag": null,
356 | "updatedOn": null,
357 | "type": "None"
358 | },
359 | "contentOrigin": "SuggestionChipsFalconService"
360 | }
361 | ]
362 | }
363 | ],
364 | "firstNewMessageIndex": 1,
365 | "defaultChatName": null,
366 | "conversationId": "51D|BingProd|25ADAFC76374850A62CF2C285A7B253E0BF90C4E722A8A813075F258A649EF3F",
367 | "requestId": "77b13b73-8138-11f6-ff28-b0375509df59",
368 | "conversationExpiryTime": "2024-02-16T04:05:46.0295914Z",
369 | "telemetry": {
370 | "startTime": "2023-11-18T04:05:13.1173554Z"
371 | },
372 | "throttling": {
373 | "maxNumUserMessagesInConversation": 30,
374 | "numUserMessagesInConversation": 3,
375 | "maxNumLongDocSummaryUserMessagesInConversation": 50,
376 | "numLongDocSummaryUserMessagesInConversation": 0
377 | },
378 | "result": {
379 | "value": "Success",
380 | "message": "判断链表的长度是一个常见的数据结构和算法问题。有多种方法可以解决这个问题,我为你找到了一些相关的资料,你可以参考一下:\n\n- 一种方法是使用**循环**,即定义一个计数器和一个指针,从头节点开始遍历链表,每遍历一个节点,计数器加一,直到指针为空为止,此时计数器的值就是链表的长度[^3^] [^2^]。这种方法的时间复杂度是O (n),空间复杂度是O (1)。\n- 另一种方法是使用**递归**,即定义一个递归函数,如果节点为空,返回0,否则返回1加上对下一个节点的递归调用,最终返回的值就是链表的长度[^3^]。这种方法的时间复杂度也是O (n),但空间复杂度是O (n),因为需要栈空间来存储递归调用。\n\n以上是我能找到的一些判断链表长度的方法,你可以根据自己的需要选择合适的方法。如果你还有其他问题,欢迎继续和我聊天。😊",
381 | "serviceVersion": "20231117.80"
382 | }
383 | }
384 | }
385 |
--------------------------------------------------------------------------------
/aichat-bingai.el:
--------------------------------------------------------------------------------
1 | ;;; aichat-bingai.el --- aichat-bingai.el -*- lexical-binding: t; -*-
2 |
3 | ;; Filename: aichat-bingai.el
4 | ;; Description: aichat-bingai.el
5 | ;; Author: xhcoding
6 | ;; Maintainer: xhcoding
7 | ;; Copyright (C) 2023, xhcoding, all rights reserved.
8 | ;; Created: 2023-03-11 15:12:02
9 | ;; Version: 0.1
10 | ;; Last-Updated: 2023-03-11 15:12:02
11 | ;; By: xhcoding
12 | ;; URL: https://github.com/xhcoding/emacs-aichat
13 | ;; Keywords:
14 | ;; Compatibility: GNU Emacs 30.0.50
15 | ;;
16 | ;; Features that might be required by this library:
17 | ;;
18 | ;;
19 | ;;
20 |
21 | ;;; This file is NOT part of GNU Emacs
22 |
23 | ;;; License
24 | ;;
25 | ;; This program is free software; you can redistribute it and/or modify
26 | ;; it under the terms of the GNU General Public License as published by
27 | ;; the Free Software Foundation; either version 3, or (at your option)
28 | ;; any later version.
29 |
30 | ;; This program is distributed in the hope that it will be useful,
31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | ;; GNU General Public License for more details.
34 |
35 | ;; You should have received a copy of the GNU General Public License
36 | ;; along with this program; see the file COPYING. If not, write to
37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
38 | ;; Floor, Boston, MA 02110-1301, USA.
39 |
40 | ;;; Commentary:
41 | ;;
42 | ;; aichat-bingai.el
43 | ;;
44 |
45 | ;;; Installation:
46 | ;;
47 | ;; Put aichat-bingai.el to your load-path.
48 | ;; The load-path is usually ~/elisp/.
49 | ;; It's set in your ~/.emacs like this:
50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
51 | ;;
52 | ;; And the following to your ~/.emacs startup file.
53 | ;;
54 | ;; (require 'aichat-bingai)
55 | ;;
56 | ;; No need more.
57 |
58 | ;;; Customize:
59 | ;;
60 | ;;
61 | ;;
62 | ;; All of the above can customize by:
63 | ;; M-x customize-group RET aichat-bingai RET
64 | ;;
65 |
66 | ;;; Change log:
67 | ;;
68 | ;; 2023/03/11
69 | ;; * First released.
70 | ;;
71 |
72 | ;;; Acknowledgements:
73 | ;;
74 | ;;
75 | ;;
76 |
77 | ;;; TODO
78 | ;;
79 | ;;
80 | ;;
81 |
82 | ;;; Require
83 | (require 'gnutls)
84 | (require 'websocket)
85 |
86 | (require 'aichat-util)
87 |
88 | ;;; Code:
89 |
90 | (defgroup aichat-bingai nil
91 | "Bing AI in Emacs."
92 | :group 'aichat
93 | :prefix "aichat-bingai-")
94 |
95 | (defcustom aichat-bingai-cookies-file nil
96 | "The path of www.bing.com cookies file.
97 |
98 | When you set this value, bingai will login to www.bing.com through the cookies in the file."
99 | :group 'aichat-bingai
100 | :type 'string)
101 |
102 | (defcustom aichat-bingai-conversation-style 'balanced
103 | "Conversation style."
104 | :group 'aichat-bingai
105 | :type '(radio
106 | (const :tag "More Creative" creative)
107 | (const :tag "More Balanced" balanced)
108 | (const :tag "More Precise" precise)))
109 |
110 | (defcustom aichat-bingai-proxy nil
111 | "Http proxy of request bingai.
112 |
113 | (setq aichat-bingai-proxy \"localhost:51837\")"
114 | :group 'aichat-bingai
115 | :type 'string)
116 |
117 |
118 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; websocket ;;;;;;;;;;;;;;;;;;;;;;;;;;
119 |
120 | (defun aichat-bingai--create-proxy-connection(name proxy-host proxy-port server-host server-port use-ssl)
121 | (let ((conn)
122 | (finished))
123 | (setq conn (make-network-process
124 | :name name
125 | :buffer nil
126 | :host proxy-host
127 | :service proxy-port
128 | :filter (lambda (proc data)
129 | (setq finished t))))
130 | (process-send-string
131 | conn
132 | (format "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\nProxy-Connection: Keep-Alive\r\n\r\n"
133 | server-host server-port server-host server-port))
134 | (while (not finished)
135 | (accept-process-output conn 0.2))
136 | (when use-ssl
137 | (setq conn (gnutls-negotiate :process conn :hostname server-host)))
138 | conn))
139 |
140 | (cl-defun aichat-bingai--websocket-open (url &key protocols extensions (on-open 'identity)
141 | (on-message (lambda (_w _f))) (on-close 'identity)
142 | (on-error 'websocket-default-error-handler)
143 | (nowait nil) (custom-header-alist nil)
144 | (proxy nil))
145 | "Open a websocket connection to URL, returning the `websocket' struct.
146 | The PROTOCOL argument is optional, and setting it will declare to
147 | the server that this client supports the protocols in the list
148 | given. We will require that the server also has to support that
149 | protocols.
150 |
151 | Similar logic applies to EXTENSIONS, which is a list of conses,
152 | the car of which is a string naming the extension, and the cdr of
153 | which is the list of parameter strings to use for that extension.
154 | The parameter strings are of the form \"key=value\" or \"value\".
155 | EXTENSIONS can be NIL if none are in use. An example value would
156 | be (\"deflate-stream\" . (\"mux\" \"max-channels=4\")).
157 |
158 | Cookies that are set via `url-cookie-store' will be used during
159 | communication with the server, and cookies received from the
160 | server will be stored in the same cookie storage that the
161 | `url-cookie' package uses.
162 |
163 | Optionally you can specify
164 | ON-OPEN, ON-MESSAGE and ON-CLOSE callbacks as well.
165 |
166 | The ON-OPEN callback is called after the connection is
167 | established with the websocket as the only argument. The return
168 | value is unused.
169 |
170 | The ON-MESSAGE callback is called after receiving a frame, and is
171 | called with the websocket as the first argument and
172 | `websocket-frame' struct as the second. The return value is
173 | unused.
174 |
175 | The ON-CLOSE callback is called after the connection is closed, or
176 | failed to open. It is called with the websocket as the only
177 | argument, and the return value is unused.
178 |
179 | The ON-ERROR callback is called when any of the other callbacks
180 | have an error. It takes the websocket as the first argument, and
181 | a symbol as the second argument either `on-open', `on-message',
182 | or `on-close', and the error as the third argument. Do NOT
183 | rethrow the error, or else you may miss some websocket messages.
184 | You similarly must not generate any other errors in this method.
185 | If you want to debug errors, set
186 | `websocket-callback-debug-on-error' to t, but this also can be
187 | dangerous is the debugger is quit out of. If not specified,
188 | `websocket-default-error-handler' is used.
189 |
190 | For each of these event handlers, the client code can store
191 | arbitrary data in the `client-data' slot in the returned
192 | websocket.
193 |
194 | The following errors might be thrown in this method or in
195 | websocket processing, all of them having the error-condition
196 | `websocket-error' in addition to their own symbol:
197 |
198 | `websocket-unsupported-protocol': Data in the error signal is the
199 | protocol that is unsupported. For example, giving a URL starting
200 | with http by mistake raises this error.
201 |
202 | `websocket-wss-needs-emacs-24': Trying to connect wss protocol
203 | using Emacs < 24 raises this error. You can catch this error
204 | also by `websocket-unsupported-protocol'.
205 |
206 | `websocket-received-error-http-response': Data in the error
207 | signal is the integer error number.
208 |
209 | `websocket-invalid-header': Data in the error is a string
210 | describing the invalid header received from the server.
211 |
212 | `websocket-unparseable-frame': Data in the error is a string
213 | describing the problem with the frame.
214 |
215 | `nowait': If NOWAIT is true, return without waiting for the
216 | connection to complete.
217 |
218 | `custom-headers-alist': An alist of custom headers to pass to the
219 | server. The car is the header name, the cdr is the header value.
220 | These are different from the extensions because it is not related
221 | to the websocket protocol.
222 | "
223 | (let* ((name (format "websocket to %s" url))
224 | (url-struct (url-generic-parse-url url))
225 | (key (websocket-genkey))
226 | (coding-system-for-read 'binary)
227 | (coding-system-for-write 'binary)
228 | (conn (if (member (url-type url-struct) '("ws" "wss"))
229 | (let* ((type (if (equal (url-type url-struct) "ws")
230 | 'plain 'tls))
231 | (port (if (= 0 (url-port url-struct))
232 | (if (eq type 'tls) 443 80)
233 | (url-port url-struct)))
234 | (host (url-host url-struct)))
235 | (if proxy
236 | (let* ((proxy-segs (split-string proxy ":"))
237 | (proxy-host (nth 0 proxy-segs))
238 | (proxy-port (nth 1 proxy-segs))
239 | (use-ssl (eq type 'tls)))
240 | (aichat-bingai--create-proxy-connection
241 | name proxy-host proxy-port host port use-ssl))
242 | (if (eq type 'plain)
243 | (make-network-process :name name :buffer nil :host host
244 | :service port :nowait nowait)
245 | (condition-case-unless-debug nil
246 | (open-network-stream name nil host port :type type :nowait nowait)
247 | (wrong-number-of-arguments
248 | (signal 'websocket-wss-needs-emacs-24 (list "wss")))))))
249 | (signal 'websocket-unsupported-protocol (list (url-type url-struct)))))
250 | (websocket (websocket-inner-create
251 | :conn conn
252 | :url url
253 | :on-open on-open
254 | :on-message on-message
255 | :on-close on-close
256 | :on-error on-error
257 | :protocols protocols
258 | :extensions (mapcar 'car extensions)
259 | :accept-string
260 | (websocket-calculate-accept key))))
261 | (unless conn (error "Could not establish the websocket connection to %s" url))
262 | (process-put conn :websocket websocket)
263 | (set-process-filter conn
264 | (lambda (process output)
265 | (let ((websocket (process-get process :websocket)))
266 | (websocket-outer-filter websocket output))))
267 | (set-process-sentinel
268 | conn
269 | (websocket-sentinel url conn key protocols extensions custom-header-alist nowait))
270 | (set-process-query-on-exit-flag conn nil)
271 | (websocket-ensure-handshake url conn key protocols extensions custom-header-alist nowait)
272 | websocket))
273 |
274 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Internal ;;;;;;;;;;;;;;;;;;;;;;;;;;
275 | (defconst aichat-bingai--domain ".bing.com"
276 | "Bing domain for retrieve cookies.")
277 |
278 | (defun aichat-bingai--login-p ()
279 | "Check if you're already login."
280 | (when-let* ((host-cookies
281 | (seq-find (lambda (host)
282 | (string= (car host) aichat-bingai--domain))
283 | (append url-cookie-secure-storage url-cookie-storage)))
284 | (user (seq-find
285 | (lambda (cookie)
286 | (string= (aref cookie 1) "_U"))
287 | (cdr host-cookies))))
288 | (and user
289 | (not (url-cookie-expired-p user)))))
290 |
291 | (async-defun aichat-bingai--login ()
292 | "Login `aichat-bingai--domain'."
293 | (await t)
294 | (unless (aichat-bingai--login-p)
295 | (await (aichat-refresh-cookies aichat-bingai--domain aichat-bingai-cookies-file))
296 | (unless (aichat-bingai--login-p)
297 | (error "Can not get correct cookie for login!"))))
298 |
299 | (defconst aichat-bingai--create-conversation-url "https://www.bing.com/turing/conversation/create"
300 | "The url of create conversation.")
301 |
302 | (defconst aichat-bingai--conversation-headers
303 | `(("accept" . "application/json")
304 | ("accept-language" . "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
305 | ("content-type" . "application/json")
306 | ("referer" . "https://www.bing.com/search?q=Bing+AI&showconv=1")
307 | ("sec-ch-ua" . "\"Microsoft Edge\"=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"")
308 | ("sec-ch-ua-mobile" . "?0")
309 | ("sec-ch-ua-platform" . "\"Windows\"")
310 | ("sec-fetch-dest" . "empty")
311 | ("sec-fetch-mode" . "cors")
312 | ("sec-fetch-site" . "same-origin")
313 | ("x-ms-client-request-id" . ,(aichat-uuid))
314 | ("x-ms-useragent" . "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.0 OS/Windows"))
315 | "The headers of create conversation.")
316 |
317 | (defconst aichat-bingai--chathub-headers
318 | `(("User-Agent" . ,aichat-user-agent)
319 | ("Accept-Language" . "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"))
320 | "The headers of create chathub.")
321 |
322 | (cl-defstruct (aichat-bingai--conversation
323 | (:constructor aichat-bingai--conversation-new)
324 | (:copier nil))
325 | "A conversation structure.
326 | `id', `signature' and `client-id' are obtained through the GET request `aichat-bingai--conversation-url' response."
327 | id
328 | signature
329 | client-id)
330 |
331 | (async-defun aichat-bingai--create-conversation ()
332 | "Create a conversation through the GET request `aichat-bingai--conversation-url'."
333 | (await (aichat-bingai--login))
334 | (seq-let
335 | (status headers body)
336 | (await (aichat-http aichat-bingai--create-conversation-url
337 | :proxy aichat-bingai-proxy
338 | :headers aichat-bingai--conversation-headers))
339 | (aichat-debug "status:\n%s\nheaders:\n%s\nbody:\n%s\n" status headers body)
340 | (if (not (string= "200" (car status)))
341 | (error "Create conversation failed: %s" status)
342 | (if (string-empty-p body)
343 | (error "Your network settings are preventing access to this feature.")
344 | (let* ((data (aichat-json-parse body))
345 | (result-value (aichat-json-access data "{result}{value}"))
346 | (signature (cdr (seq-find (lambda (header) (string= (downcase (car header)) "x-sydney-encryptedconversationsignature"))
347 | headers))))
348 | (if (not (string= "Success" result-value))
349 | (error "Create conversation failed: %s" body)
350 | (aichat-bingai--conversation-new
351 | :id (aichat-json-access data "{conversationId}")
352 | :client-id (aichat-json-access data "{clientId}")
353 | :signature signature)))))))
354 |
355 | (cl-defstruct (aichat-bingai--session
356 | (:constructor aichat-bingai--session-new)
357 | (:copier nil))
358 | "A session structure.
359 | `conversation' represents the `aichat-bingai--conversation'.
360 | `chathub' represents the chathub websocket connection.
361 | `invocation-id' indicates the number of questions.
362 | `max-conversation' indicates the number of max conversation.
363 | when `reset' is `t', session will be reset.
364 | `replying' indicates whether the reply is in progress.
365 | `buffer' saves the reply message for parsing.
366 | `resolve' and `reject' are promise callback, call `resolve' when the reply ends
367 | and call `reject' when error occurs.
368 | `result' saves the result of conversation.
369 | `err' saves the error of conversation.
370 | Call `user-cb' when a message arrives."
371 | conversation
372 | chathub
373 | (invocation-id 0)
374 | (max-conversation 10)
375 | (reset nil)
376 | (replying nil)
377 | (buffer "")
378 | resolve
379 | reject
380 | (result nil)
381 | (err nil)
382 | (expiry-time nil)
383 | user-cb)
384 |
385 | (defconst aichat-bingai--chathub-message-delimiter (char-to-string #x1e)
386 | "Websocket json message delimiter.")
387 |
388 | (defun aichat-bingai--chathub-send-heart (ws)
389 | "Send type 6 message."
390 | (websocket-send-text ws (concat (aichat-json-serialize (list :type 6))
391 | aichat-bingai--chathub-message-delimiter)))
392 |
393 | (defun aichat-bingai--chathub-reply-finished (session &optional error reset)
394 | "Handler of reply finished."
395 | (setf (aichat-bingai--session-replying session) nil)
396 | (setf (aichat-bingai--session-err session) error)
397 | (setf (aichat-bingai--session-reset session) reset)
398 | (when reset
399 | (websocket-close (aichat-bingai--session-chathub session)))
400 | (let ((err (aichat-bingai--session-err session)))
401 | (if err
402 | (when-let ((reject-func (aichat-bingai--session-reject session)))
403 | (funcall reject-func err))
404 | (when-let ((resolve-func (aichat-bingai--session-resolve session)))
405 | (funcall resolve-func (aichat-bingai--session-result session))))))
406 |
407 | (defun aichat-bingai--chathub-parse-message (session text)
408 | "Parse chathub websocket json message."
409 | (aichat-debug "Recv text:\n%s" text)
410 | (let ((buffer (concat (aichat-bingai--session-buffer session) text))
411 | (start-pos 0)
412 | (match-pos nil)
413 | (object))
414 | (aichat-debug "buffer:\n%s" buffer)
415 | (catch 'not-find
416 | (while t
417 | (setq match-pos (string-match-p aichat-bingai--chathub-message-delimiter buffer start-pos))
418 | (if (not match-pos)
419 | (throw 'not-find match-pos)
420 | (setq object (aichat-json-parse (substring buffer start-pos match-pos)))
421 | (aichat-debug "object:\n%s" object)
422 | (pcase (aichat-json-access object "{type}")
423 | (1 (let* ((user-cb (aichat-bingai--session-user-cb session))
424 | (messages (aichat-json-access object "{arguments}[0]{messages}"))
425 | (throttling (aichat-json-access object "{arguments}[0]{throttling}"))
426 | (message-type (if messages (aichat-json-access messages "[0]{messageType}") nil)))
427 | (cond
428 | (throttling
429 | (setf (aichat-bingai--session-max-conversation session)
430 | (aichat-json-access throttling "{maxNumUserMessagesInConversation}")))
431 | ((string= message-type "Disengaged")
432 | (aichat-bingai--chathub-reply-finished session "Conversation disengaged, next conversation will be restarted." t))
433 | (t
434 | (when user-cb
435 | (condition-case error
436 | (funcall user-cb object)
437 | (error
438 | (aichat-bingai--chathub-reply-finished session (format "User callback error: %s" error)))))))))
439 | (2 (let* ((result (aichat-json-access object "{item}{result}"))
440 | (value (aichat-json-access result "{value}"))
441 | (message (aichat-json-access result "{message}")))
442 | (if (string= "Success" value)
443 | (progn
444 | (when-let* ((expiry-time-str
445 | (ignore-errors
446 | (aichat-json-access object "{item}{conversationExpiryTime}")))
447 | (expiry-time (date-to-time expiry-time-str)))
448 | (setf (aichat-bingai--session-expiry-time session) expiry-time))
449 | (setf (aichat-bingai--session-result session) object))
450 | (aichat-bingai--chathub-reply-finished session (format "%s:%s\n" value message)
451 | (string= "InvalidSession" value)))))
452 | (3 (let ((err (aichat-json-access object "{error}")))
453 | (aichat-bingai--chathub-reply-finished session err)))
454 | (6 (when-let ((chathub (aichat-bingai--session-chathub session))
455 | (replying (aichat-bingai--session-replying session)))
456 | (aichat-bingai--chathub-send-heart chathub))))
457 | (setq start-pos (1+ match-pos)))))
458 | (setf (aichat-bingai--session-buffer session) (substring buffer start-pos))))
459 |
460 | (defconst aichat-bingai--chathub-url "wss://sydney.bing.com/sydney/ChatHub"
461 | "The url of create chathub.")
462 |
463 | (defun aichat-bingai--create-chathub (session)
464 | "Create a websocket connection to `aichat-bingai--chathub-url'.
465 |
466 | Call resolve when the handshake with chathub passed."
467 | (promise-new
468 | (lambda (resolve reject)
469 | (aichat-bingai--websocket-open
470 | (concat aichat-bingai--chathub-url "?sec_access_token="
471 | (url-hexify-string (aichat-bingai--conversation-signature (aichat-bingai--session-conversation session))))
472 | :proxy aichat-bingai-proxy
473 | :custom-header-alist aichat-bingai--chathub-headers
474 | :on-open (lambda (ws)
475 | (aichat-debug "====== chathub opened ======")
476 | ;; send handshake
477 | (if (and ws (websocket-openp ws))
478 | (websocket-send-text ws (concat (aichat-json-serialize
479 | (list :protocol "json" :version 1))
480 | aichat-bingai--chathub-message-delimiter))
481 | (funcall reject "Chathub unexpected closed during handshake.")))
482 | :on-close (lambda (_ws)
483 | (aichat-debug "====== chathub closed ======")
484 | (if (not (aichat-bingai--session-chathub session))
485 | (funcall reject "Chathub unexpected closed during handshake.")
486 | (setf (aichat-bingai--session-chathub session) nil)
487 | (when (aichat-bingai--session-replying session)
488 | ;; close when replying
489 | (setf (aichat-bingai--session-replying session) nil)
490 | (when-let ((reject-func (aichat-bingai--session-reject session)))
491 | (funcall reject-func "Chathub closed unexpectedly during reply.")))))
492 | :on-message (lambda (ws frame)
493 | (let ((text (websocket-frame-text frame)))
494 | (condition-case error
495 | (progn
496 | (aichat-debug "Receive handshake response: %s" text)
497 | (aichat-json-parse (car (split-string text aichat-bingai--chathub-message-delimiter)))
498 | (aichat-bingai--chathub-send-heart ws)
499 | (setf (websocket-on-message ws)
500 | (lambda (_ws frame)
501 | (aichat-bingai--chathub-parse-message session (websocket-frame-text frame))))
502 | (setf (aichat-bingai--session-chathub session) ws)
503 | (funcall resolve t))
504 | (error (funcall reject error)))))))))
505 |
506 | (defun aichat-bingai--close-chathub (session)
507 | "Close chathub websocket connection."
508 | (when-let ((chathub (aichat-bingai--session-chathub session)))
509 | (when (websocket-openp chathub)
510 | (websocket-close chathub))))
511 |
512 | (defun aichat-bingai--reply-options (style)
513 | (apply
514 | #'vector
515 | "nlu_direct_response_filter"
516 | "deepleo"
517 | "disable_emoji_spoken_text"
518 | "responsible_ai_policy_2235"
519 | "enablemm"
520 | "iycapbing"
521 | "iyxapbing"
522 | "refpromptv1"
523 | "enuaug"
524 | "dv3sugg"
525 | "autosave"
526 | "iyoloxap"
527 | "iyoloneutral"
528 | (pcase style
529 | ('creative (list "h3imaginative" "clgalileo" "gencontentv3"))
530 | ('balanced (list "galileo" "saharagenconv5"))
531 | ('precise (list "h3precise" "clgalileo" "gencontentv3")))))
532 |
533 | (defun aichat-bingai--tone (style)
534 | (pcase style
535 | ('creative "Creative")
536 | ('balanced "Balanced")
537 | ('precise "Precise"))
538 | )
539 |
540 | (defconst aichat-bingai--allowed-message-types
541 | [
542 | "Chat"
543 | "InternalSearchQuery"
544 | "InternalSearchResult"
545 | "InternalLoaderMessage"
546 | "RenderCardRequest"
547 | "AdsQuery"
548 | "SemanticSerp"
549 | "GenerateContentQuery"
550 | "SearchQuery"
551 | ])
552 |
553 | (defconst aichat-bingai--slice-ids
554 | [
555 | "winmuid3tf"
556 | "anssupltmrd1"
557 | "imgchatgptv2"
558 | "bingsr3-v1"
559 | "revpayaad"
560 | "winstmsg2tf"
561 | "602refusal"
562 | "621alllocs0"
563 | "621docxfmtho"
564 | "330uaug"
565 | "0626snptrcs0"
566 | "424dagslnv1s0"])
567 |
568 | (defun aichat-bingai--make-request (session text style allowed-message-types options-sets)
569 | (unless allowed-message-types
570 | (setq allowed-message-types aichat-bingai--allowed-message-types))
571 |
572 | (unless options-sets
573 | (setq options-sets (aichat-bingai--reply-options style)))
574 |
575 | (let* ((conversation (aichat-bingai--session-conversation session))
576 | (invocation-id (aichat-bingai--session-invocation-id session))
577 | (request (list :arguments
578 | (vector
579 | (list :source "cib"
580 | :optionsSets options-sets
581 | :allowedMessageTypes (vconcat allowed-message-types ["Disengaged"])
582 | :sliceIds aichat-bingai--slice-ids
583 | :tone (aichat-bingai--tone style)
584 | :spokenTextMode: "None"
585 | :isStartOfSession (if (= 0 invocation-id)
586 | t
587 | :json-false)
588 | :message (list :author "user"
589 | :inputMethod "Keyboard"
590 | :text text
591 | :messageType "Chat")
592 | :participant (list :id (aichat-bingai--conversation-client-id conversation))
593 | :conversationId (aichat-bingai--conversation-id conversation)))
594 | :invocationId (number-to-string (aichat-bingai--session-invocation-id session))
595 | :target "chat"
596 | :type 4)))
597 | (concat (aichat-json-serialize request) aichat-bingai--chathub-message-delimiter)))
598 |
599 | (defvar aichat-bingai--current-session nil ;; only one session
600 | "Bingai session.")
601 |
602 | (defun aichat-bingai--get-current-session ()
603 | "Return current session."
604 | aichat-bingai--current-session)
605 |
606 | (defun aichat-bingai--set-current-session (session)
607 | "Set current session."
608 | (setq aichat-bingai--current-session session))
609 |
610 | (defun aichat-bingai--remove-current-session ()
611 | "Remove current session."
612 | (setq aichat-bingai--current-session nil))
613 |
614 | (defun aichat-bingai--stop-session ()
615 | "Stop current bingai session."
616 | (when-let ((session (aichat-bingai--get-current-session)))
617 | (setf (aichat-bingai--session-conversation session) nil)
618 | (aichat-bingai--close-chathub session)
619 | (aichat-bingai--remove-current-session)))
620 |
621 | (async-defun aichat-bingai--start-session ()
622 | "Start a new aichat-bingai session."
623 | (await t)
624 | (aichat-bingai--stop-session)
625 | (when-let ((conversation (await (aichat-bingai--create-conversation)))
626 | (session (aichat-bingai--session-new
627 | :conversation conversation)))
628 | (aichat-bingai--set-current-session session)
629 | t))
630 |
631 | (defun aichat-bingai--ensure-conversation-valid ()
632 | (when-let* ((session (aichat-bingai--get-current-session))
633 | (invocation-id (aichat-bingai--session-invocation-id session))
634 | (max-conversation (aichat-bingai--session-max-conversation session)))
635 | (when-let* ((expiry-time (aichat-bingai--session-expiry-time session))
636 | (expiry-p (ignore-errors
637 | (time-less-p expiry-time nil))))
638 | (aichat-bingai--stop-session))
639 | (when (aichat-bingai--session-reset session)
640 | (aichat-bingai--stop-session))
641 | (when (>= invocation-id max-conversation)
642 | (aichat-bingai--stop-session))))
643 |
644 | (async-defun aichat-bingai--send-request (text style allowed-message-types options-sets &optional callback)
645 | (aichat-bingai--ensure-conversation-valid)
646 | (let ((session (aichat-bingai--get-current-session)))
647 | (unless session
648 | (await (aichat-bingai--start-session))
649 | (setq session (aichat-bingai--get-current-session)))
650 | (unless (aichat-bingai--session-chathub session)
651 | (await (aichat-bingai--create-chathub session)))
652 |
653 | (await (promise-new
654 | (lambda (resolve reject)
655 | (let ((request (aichat-bingai--make-request session text style allowed-message-types options-sets)))
656 | (aichat-debug "Send request:\n%s\n" request)
657 | (websocket-send-text (aichat-bingai--session-chathub session) request)
658 | (setf (aichat-bingai--session-invocation-id session) (1+ (aichat-bingai--session-invocation-id session))
659 | (aichat-bingai--session-replying session) t
660 | (aichat-bingai--session-buffer session) ""
661 | (aichat-bingai--session-resolve session) resolve
662 | (aichat-bingai--session-reject session) reject
663 | (aichat-bingai--session-result session) nil
664 | (aichat-bingai--session-err session) nil
665 | (aichat-bingai--session-user-cb session) callback)))))))
666 |
667 |
668 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Image API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
669 |
670 | (async-defun aichat-bingai--image-download (urls resolve reject)
671 | (await nil)
672 | (condition-case error
673 | (let ((paths))
674 | (dolist (url urls)
675 | (when-let* ((name (concat (car (last (split-string (car (split-string url "?")) "/"))) ".jpeg"))
676 | (filepath (expand-file-name name (temporary-file-directory)))
677 | (output (await (aichat-shell-command (format "%s --silent \"%s\" --output %s" aichat-curl-program url filepath)))))
678 | (push (cons url filepath) paths)))
679 | (funcall resolve paths))
680 | (error (funcall reject error))))
681 |
682 | (defun aichat-bingai--image-polling (request-id prompt resolve reject)
683 | (let ((polling-url (format "https://www.bing.com/images/create/async/results/%s" request-id)))
684 | (promise-then (aichat-http polling-url
685 | :proxy aichat-bingai-proxy
686 | :params `(("q" . ,prompt)))
687 | (lambda (response)
688 | (if (not (string= "200" (caar response)))
689 | (progn
690 | (funcall reject (format "Polling image error: %s" response)))
691 | (let ((body (car (last response)))
692 | (urls (list)))
693 | (if (string-empty-p body)
694 | (run-at-time 5 nil #'aichat-bingai--image-polling request-id prompt resolve reject)
695 | (with-temp-buffer
696 | (insert body)
697 | (goto-char (point-min))
698 | (while (re-search-forward (rx "src=\""
699 | (group (1+ (not "\"")))
700 | "\"")
701 | nil t)
702 | (push (match-string 1) urls))
703 | (aichat-bingai--image-download urls resolve reject))))))
704 | (lambda (err)
705 | (message "Polling error but continue: %s" err)
706 | (run-at-time 5 nil #'aichat-bingai--image-polling request-id prompt resolve reject)))))
707 |
708 | (async-defun aichat-bingai--image-create (prompt)
709 | "Create images with PROMPT."
710 | (let ((iframeid (aichat-uuid))
711 | (response)
712 | (location)
713 | (redirect-url)
714 | (request-id)
715 | (polling-url))
716 | (setq response (await (aichat-http "https://www.bing.com/images/create"
717 | :proxy aichat-bingai-proxy
718 | :type "POST"
719 | :params `(("q" . ,prompt)
720 | ("rt" . "3")
721 | ("FORM" . "GENCRE"))
722 | :data "")))
723 | (if (not (string= "302" (caar response)))
724 | (error (format "Image create: %s" response))
725 | (setq location (aichat-read-header-value "Location" (cadr response)))
726 | (setq redirect-url (concat "https://www.bing.com" (replace-regexp-in-string "&nfy=1" "" location)))
727 | (setq request-id (cadr (split-string redirect-url "id=")))
728 | (setq response (await (aichat-http redirect-url
729 | :proxy aichat-bingai-proxy)))
730 | (await
731 | (promise-new
732 | (lambda (resolve reject)
733 | (aichat-bingai--image-polling request-id prompt resolve reject)))))))
734 |
735 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; bingai API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
736 |
737 | (defun aichat-bingai-conversationing-p ()
738 | "Whether conversation or not."
739 | (when-let ((session (aichat-bingai--get-current-session)))
740 | (aichat-bingai--session-replying session)))
741 |
742 | (defun aichat-bingai-conversation-start-p ()
743 | "Whether is start of conversation."
744 | (aichat-bingai--ensure-conversation-valid)
745 | (not (aichat-bingai--get-current-session)))
746 |
747 | (defun aichat-bingai-conversation-reset ()
748 | "Reset conversation."
749 | (aichat-bingai--stop-session))
750 |
751 | (cl-defun aichat-bingai-conversation (text &rest settings
752 | &key
753 | (style nil)
754 | (allowed-message-types nil)
755 | (options-sets nil)
756 | (on-success nil)
757 | (on-error nil))
758 | "Send a chat TEXT to Bing.
759 |
760 | `style' is the conversation style, look `aichat-bingai-conversation-stye' for detail.
761 | `allowed-message-types' is the message type allowed to return,
762 | all types in `aichat-bingai--allowed-message-types'."
763 | (when (aichat-bingai-conversationing-p)
764 | (error "Please wait for the conversation finished before call."))
765 | (unless style
766 | (setq style aichat-bingai-conversation-style))
767 | (unless allowed-message-types
768 | (setq allowed-message-types (vector "Chat")))
769 |
770 | (promise-then (aichat-bingai--send-request text style allowed-message-types options-sets)
771 | (lambda (result)
772 | (when on-success
773 | (funcall on-success result)))
774 | (lambda (err)
775 | (when on-error
776 | (funcall on-error err)))))
777 |
778 |
779 | (cl-defun aichat-bingai-conversation-stream (text callback &rest settings
780 | &key
781 | (style nil)
782 | (allowed-message-types nil)
783 | (options-sets nil)
784 | (on-success nil)
785 | (on-error nil))
786 | "Send a chat TEXT to Bing.
787 |
788 | `style' is the conversation style, look `aichat-bingai-conversation-stye' for detail.
789 | `allowed-message-types' is the message type allowed to return,
790 | all types in `aichat-bingai--allowed-message-types'."
791 | (when (aichat-bingai-conversationing-p)
792 | (error "Please wait for the conversation finished before call."))
793 |
794 | (unless style
795 | (setq style aichat-bingai-conversation-style))
796 |
797 | (unless allowed-message-types
798 | (setq allowed-message-types (vector "Chat")))
799 |
800 | (promise-then (aichat-bingai--send-request text style allowed-message-types options-sets
801 | (lambda (message)
802 | (when callback
803 | (funcall callback message))))
804 | (lambda (result)
805 | (when on-success
806 | (funcall on-success result)))
807 | (lambda (err)
808 | (when on-error
809 | (funcall on-error err)))))
810 |
811 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Messages API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
812 |
813 | (defun aichat-bingai-message-type-1-text (message)
814 | "message[arguments][0][messages][0][text]."
815 | (aichat-json-access message "{arguments}[0]{messages}[0]{text}"))
816 |
817 | (defun aichat-bingai-message-type-1-message-type (message)
818 | "msg[arguments][0][messages][0][messageType]."
819 | (aichat-json-access message "{arguments}[0]{messages}[0]{messageType}"))
820 |
821 | (defun aichat-bingai-message-type-1-suggestion (message)
822 | "message[arguments][0][messages][0][suggestedResponses]."
823 | (when-let ((suggested-responses (aichat-json-access message "{arguments}[0]{messages}[0]{suggestedResponses}")))
824 | (cl-loop for suggested-response across suggested-responses
825 | collect (aichat-json-access suggested-response "{text}"))))
826 |
827 | (defun aichat-bingai-message-type-2-text (message)
828 | "message[item][messages][?][text]."
829 | (when-let ((messages (aichat-json-access message "{item}{messages}")))
830 | (cl-loop for msg across messages
831 | do (let ((msg-type (aichat-json-access msg "{messageType}"))
832 | (author (aichat-json-access msg "{author}")))
833 | (when (and (not msg-type) (string= author "bot"))
834 | (cl-return (aichat-json-access msg "{text}")))))))
835 |
836 | (defun aichat-bingai-message-type-2-suggestion (message)
837 | "message[item][messages][?][text]."
838 | (when-let ((messages (aichat-json-access message "{item}{messages}")))
839 | (cl-loop for msg across messages
840 | do (let ((msg-type (aichat-json-access msg "{messageType}"))
841 | (author (aichat-json-access msg "{author}")))
842 | (when (and (not msg-type) (string= author "bot"))
843 | (cl-return
844 | (when-let ((suggested-responses (aichat-json-access msg "{suggestedResponses}")))
845 | (cl-loop for suggested-response across suggested-responses
846 | collect (aichat-json-access suggested-response "{text}")))))))))
847 |
848 | (defun aichat-bingai-message-type-2-image-prompt (message)
849 | "message[arguments][0][messages][?][text]."
850 | (when-let ((messages (aichat-json-access message "{item}{messages}")))
851 | (cl-loop for msg across messages
852 | do (let ((msg-type (aichat-json-access msg "{messageType}"))
853 | (author (aichat-json-access msg "{author}")))
854 | (when (and (string= msg-type "GenerateContentQuery") (string= author "bot"))
855 | (when-let ((type (aichat-json-access msg "{contentType}"))
856 | (text (aichat-json-access msg "{text}")))
857 | (if (string= "IMAGE" type)
858 | (cl-return text)
859 | (cl-return nil))))))))
860 |
861 | (defun aichat-bingai-message-type-2-source-attrs (message)
862 | "message[item][messages][?][sourceAttributions]."
863 | (when-let ((messages (aichat-json-access message "{item}{messages}")))
864 | (cl-loop for msg across messages
865 | do (let ((source-attrs (aichat-json-access msg "{sourceAttributions}")))
866 | (when source-attrs
867 | (cl-return source-attrs))))))
868 |
869 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Chat ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
870 |
871 | (defcustom aichat-bingai-chat-file (expand-file-name "aichat.md" user-emacs-directory)
872 | "File path of save chat message."
873 | :group 'aichat-bingai
874 | :type 'string)
875 |
876 | (defcustom aichat-bingai-chat-display-function 'display-buffer
877 | "The function of how to display `aichat-bingai-chat-file' buffer."
878 | :group 'aichat-bingai
879 | :type 'symbol)
880 |
881 | (defface aichat-bingai-chat-prompt-face '((t (:height 0.8 :foreground "#006800")))
882 | "Face used for prompt overlay.")
883 |
884 | (cl-defstruct (aichat-bingai--chat
885 | (:constructor aichat-bingai--chat-new)
886 | (:copier nil))
887 | "A chat structure.
888 | `buffer' is used to display chat message.
889 | `said' is what the user said.
890 | `replied-length' is the length of the reply.
891 | `reply-point' is where the reply is inserted."
892 | buffer
893 | said
894 | (replied-length 0)
895 | reply-point)
896 |
897 | (defvar aichat-bingai--chat-suggestion nil)
898 |
899 | (defun aichat-bingai--chat-get-buffer ()
900 | "Get chat buffer."
901 | (let ((chat-buffer (get-file-buffer aichat-bingai-chat-file)))
902 | (unless chat-buffer
903 | (setq chat-buffer (find-file-noselect aichat-bingai-chat-file)))
904 | (with-current-buffer chat-buffer
905 | (goto-char (point-max))
906 | (when (derived-mode-p 'markdown-mode)
907 | (unless markdown-hide-markup
908 | (markdown-toggle-markup-hiding)))
909 | (when (and (featurep 'pangu-spacing) pangu-spacing-mode)
910 | (pangu-spacing-mode -1)))
911 | chat-buffer))
912 |
913 | (defun aichat-bingai--chat-say (chat new-p)
914 | "Show user said.
915 | NEW-P is t, which means it is a new conversation."
916 | (with-current-buffer (aichat-bingai--chat-buffer chat)
917 | (goto-char (point-max))
918 | (let ((header-char (if (derived-mode-p 'org-mode) "*" "#")))
919 | (if new-p
920 | (insert "\n" header-char " ")
921 | (insert "\n" header-char header-char " ")))
922 | (insert (aichat-bingai--chat-said chat))
923 | (insert "\n\n")
924 | (setf (aichat-bingai--chat-reply-point chat) (point))
925 | (aichat-bingai--chat-update-prompt chat "Replying...")))
926 |
927 | (defun aichat-bingai--chat-update-prompt (chat text)
928 | (with-current-buffer (aichat-bingai--chat-buffer chat)
929 | (save-mark-and-excursion
930 | (goto-char (aichat-bingai--chat-reply-point chat))
931 | (if (derived-mode-p 'org-mode)
932 | (org-previous-visible-heading +1)
933 | (markdown-previous-visible-heading +1))
934 | (let* ((from (line-beginning-position))
935 | (to (line-end-position)))
936 | (remove-overlays from to 'aichat-bingai--chat-handle-reply t)
937 | (when text
938 | (let ((ov (make-overlay from to)))
939 | (overlay-put ov 'after-string
940 | (propertize
941 | (concat " " text)
942 | 'face 'aichat-bingai-chat-prompt-face))
943 | (overlay-put ov 'aichat-bingai--chat-handle-reply t)))))))
944 |
945 | (defun aichat-bingai--chat-handle-reply (msg chat)
946 | (let ((message-type (aichat-bingai-message-type-1-message-type msg))
947 | (buffer (aichat-bingai--chat-buffer chat)))
948 | (pcase message-type
949 | ("InternalSearchQuery" (when-let ((text (aichat-bingai-message-type-1-text msg)))
950 | (aichat-bingai--chat-update-prompt chat text)))
951 | ("InternalLoaderMessage" (when-let ((text (aichat-bingai-message-type-1-text msg)))
952 | (aichat-bingai--chat-update-prompt chat text)))
953 | ("InternalSearchResult"
954 | nil)
955 | (_
956 | (when-let* ((text (aichat-bingai-message-type-1-text msg))
957 | (replied-length (aichat-bingai--chat-replied-length chat))
958 | (text-length (length text))
959 | (valid (> text-length replied-length)))
960 | (with-current-buffer buffer
961 | (save-mark-and-excursion
962 | (goto-char (aichat-bingai--chat-reply-point chat))
963 | (insert (substring text replied-length))
964 | (setf (aichat-bingai--chat-reply-point chat) (point)
965 | (aichat-bingai--chat-replied-length chat) text-length))))))))
966 |
967 | (defun aichat-bingai--chat-convert-to-org ()
968 | (org-previous-visible-heading +1)
969 | (while (re-search-forward "\\(\\*\\(\\*.*\\*\\)\\*\\|\\[^\\([0-9]+\\)^\\]\\|`\\([^`]+\\)`\\|```\\([a-z]*\\(.\\|\n\\)*\\)```\\)" nil t)
970 | (when (match-string 2)
971 | (replace-match "\\2"))
972 | (when (match-string 3)
973 | (replace-match "[fn:\\3]"))
974 | (when (match-string 4)
975 | (replace-match "=\\4="))
976 | (when (match-string 5)
977 | (replace-match "#+begin_src \\5#+end_src"))))
978 |
979 | (defun aichat-bingai--chat-handle-reply-finished (chat msg)
980 | ;; update suggestion
981 | (setq aichat-bingai--chat-suggestion (aichat-bingai-message-type-2-suggestion msg))
982 |
983 | ;; convert to org
984 | (with-current-buffer (aichat-bingai--chat-buffer chat)
985 | (when (derived-mode-p 'org-mode)
986 | (save-mark-and-excursion
987 | (aichat-bingai--chat-convert-to-org))))
988 |
989 | ;; insert source-attrs
990 | (when-let ((source-attrs (aichat-bingai-message-type-2-source-attrs msg)))
991 | (with-current-buffer (aichat-bingai--chat-buffer chat)
992 | (save-mark-and-excursion
993 | (goto-char (aichat-bingai--chat-reply-point chat))
994 | (end-of-line)
995 | (insert "\n")
996 | (let ((index 1)
997 | (title)
998 | (url))
999 | (mapc (lambda (source-attr)
1000 | (aichat-debug "Insert source attrs: %s" source-attr)
1001 | (setq url (aichat-json-access source-attr "{seeMoreUrl}"))
1002 | (setq title (or (aichat-json-access source-attr "{providerDisplayName}")
1003 | url))
1004 | (insert (format "%s. " index))
1005 | (if (derived-mode-p 'org-mode)
1006 | (org-insert-link nil url (or title url))
1007 | (insert (format "[%s](%s)" (or title url) url)))
1008 | (insert "\n")
1009 | (cl-incf index))
1010 | source-attrs))
1011 | (insert "\n"))))
1012 |
1013 | ;; generage image
1014 | (let ((image-prompt (aichat-bingai-message-type-2-image-prompt msg)))
1015 | (if (not image-prompt)
1016 | (aichat-bingai--chat-update-prompt chat nil)
1017 | (aichat-bingai--chat-update-prompt chat "Generate image...")
1018 | (promise-then (aichat-bingai--image-create image-prompt)
1019 | (lambda (paths)
1020 | (aichat-bingai--chat-update-prompt chat nil)
1021 | (when paths
1022 | (with-current-buffer (aichat-bingai--chat-buffer chat)
1023 | (goto-char (aichat-bingai--chat-reply-point chat))
1024 | (save-mark-and-excursion
1025 | (mapc (lambda (path)
1026 | (if (derived-mode-p 'org-mode)
1027 | (insert (format "\n[[file:%s]] \n" (cdr path)))
1028 | (insert (format "\n \n" (car path) (cdr path)))))
1029 | paths))
1030 | (if (derived-mode-p 'org-mode)
1031 | (org-display-inline-images)
1032 | (markdown-display-inline-images)))))
1033 | (lambda (err)
1034 | (message "Image create error: %s" err)
1035 | (aichat-bingai--chat-update-prompt chat nil))))))
1036 |
1037 | (defun aichat-bingai--chat-handle-reply-error (chat msg)
1038 | (aichat-bingai--chat-update-prompt chat nil)
1039 | (message "%s" msg))
1040 |
1041 | ;;;###autoload
1042 | (defun aichat-bingai-chat (said &optional style)
1043 | "Chat with Bing AI."
1044 | (interactive (list (completing-read "You say: " aichat-bingai--chat-suggestion nil nil)))
1045 | (when (and (car current-prefix-arg)
1046 | (= (car current-prefix-arg) 4))
1047 | (aichat-bingai-conversation-reset))
1048 |
1049 | (if (aichat-bingai-conversationing-p)
1050 | (message "Please wait for the conversation finished before saying.")
1051 | (let* ((chat-buffer (aichat-bingai--chat-get-buffer))
1052 | (chat (aichat-bingai--chat-new
1053 | :buffer chat-buffer
1054 | :said said)))
1055 | (if (and aichat-bingai-chat-display-function (functionp aichat-bingai-chat-display-function))
1056 | (funcall aichat-bingai-chat-display-function chat-buffer)
1057 | (switch-to-buffer chat-buffer))
1058 |
1059 | (aichat-bingai--chat-say chat (aichat-bingai-conversation-start-p))
1060 |
1061 | (aichat-bingai-conversation-stream said (lambda (msg)
1062 | (aichat-bingai--chat-handle-reply msg chat))
1063 | :style style
1064 | :allowed-message-types ["Chat"
1065 | "InternalSearchQuery"
1066 | "InternalSearchResult"
1067 | "InternalLoaderMessage"
1068 | "GenerateContentQuery"]
1069 | :on-success (lambda (msg)
1070 | (aichat-bingai--chat-handle-reply-finished chat msg))
1071 | :on-error (lambda (msg)
1072 | (aichat-bingai--chat-handle-reply-error chat msg))))))
1073 |
1074 |
1075 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Assistant ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1076 |
1077 | (defcustom aichat-bingai-assistant-buffer "*Aichat-BingAI-Assistant*"
1078 | "The buffer of show assistant message."
1079 | :group 'aichat-bingai
1080 | :type 'string)
1081 |
1082 | (defcustom aichat-bingai-assistant-display-function 'display-buffer
1083 | "The function of display `aichat-bingai-assistant-buffer'."
1084 | :group 'aichat-bingai
1085 | :type 'symbol)
1086 |
1087 | (defun aichat-bingai-assistant-get-buffer ()
1088 | (get-buffer-create aichat-bingai-assistant-buffer))
1089 |
1090 | (defun aichat-bingai-assistant (text &optional style)
1091 | "Send the region or input to Bing and display the returned result to `aichat-bingai-assistant-buffer'."
1092 | (interactive (list (aichat-read-region-or-input "Input text: ")))
1093 | (when (and text (not (string-empty-p text)))
1094 | (aichat-bingai-conversation text
1095 | :style style
1096 | :allowed-message-types ["Chat"
1097 | "GenerateContentQuery"]
1098 | :on-success (lambda (msg)
1099 | (when-let ((content (aichat-bingai-message-type-2-text msg))
1100 | (buffer (aichat-bingai-assistant-get-buffer)))
1101 | (with-current-buffer buffer
1102 | (goto-char (point-max))
1103 | (insert content)
1104 | (insert "\n\n"))
1105 | (let ((image-prompt (aichat-bingai-message-type-2-image-prompt msg)))
1106 | (if (not image-prompt)
1107 | (funcall aichat-bingai-assistant-display-function buffer)
1108 | (promise-then (aichat-bingai--image-create image-prompt)
1109 | (lambda (paths)
1110 | (when paths
1111 | (with-current-buffer buffer
1112 | (markdown-mode)
1113 | (goto-char (point-max))
1114 | (save-mark-and-excursion
1115 | (mapc (lambda (path)
1116 | (if (derived-mode-p 'org-mode)
1117 | (insert (format "\n[[file:%s]] \n" (cdr path)))
1118 | (insert (format "\n \n" (car path) (cdr path)))))
1119 | paths))
1120 | (if (derived-mode-p 'org-mode)
1121 | (org-display-inline-images)
1122 | (markdown-display-inline-images))))
1123 | (funcall aichat-bingai-assistant-display-function buffer))
1124 | (lambda (err)
1125 | (message "Image create error: %s" err)
1126 | (funcall aichat-bingai-assistant-display-function buffer)))))))
1127 | :on-error (lambda (err)
1128 | (message "Error: %s" err)))))
1129 |
1130 | (defun aichat-bingai-replace-or-insert (text &optional style)
1131 | "Send the region or input to Bing and replace the selected region or insert at the current position with the returned result."
1132 | (interactive (list (aichat-read-region-or-input "Input text: ")))
1133 | (when (and text (not (string-empty-p text)))
1134 | (let* ((cur-buf (current-buffer))
1135 | (cur-pos (with-current-buffer cur-buf (point)))
1136 | (reg-beg (when (use-region-p) (region-beginning)))
1137 | (reg-end (when (use-region-p) (region-end))))
1138 | (aichat-bingai-conversation text
1139 | :style style
1140 | :on-success (lambda (msg)
1141 | (when-let ((content (aichat-bingai-message-type-2-text msg)))
1142 | (with-current-buffer cur-buf
1143 | (if (and reg-beg reg-end)
1144 | (replace-region-contents reg-beg reg-end (lambda () content))
1145 | (goto-char cur-pos)
1146 | (insert content)))))
1147 | :on-error (lambda (err)
1148 | (message "Error: %s" err))))))
1149 |
1150 | (cl-defmacro aichat-bingai-prompt-create (name &rest args
1151 | &key
1152 | (input-prompt "Input text: ")
1153 | (text-format "%s")
1154 | (style nil)
1155 | (chat nil)
1156 | (assistant nil)
1157 | (replace-or-insert nil))
1158 | "This macro will generate three functions: aichat-bingai-chat-name, aichat-bingai-assistant-name or aichat-bingai-replace-or-insert-name.
1159 |
1160 | INPUT-PROMPT: The prompt before the user input in minibuffer.
1161 | TEXT-FORMAT: Formating string, %s is replaced by what the user input."
1162 | (let ((chat-func (intern (format "aichat-bingai-chat-%s" name)))
1163 | (assistant-func (intern (format "aichat-bingai-assistant-%s" name)))
1164 | (replace-func (intern (format "aichat-bingai-replace-or-insert-%s" name))))
1165 | `(progn
1166 | (when ,chat
1167 | (defun ,chat-func(text)
1168 | (interactive (list (aichat-read-region-or-input ,input-prompt)))
1169 | (when text
1170 | (let ((query (format ,text-format text)))
1171 | (aichat-bingai-chat query ,style)))))
1172 | (when ,assistant
1173 | (defun ,assistant-func(text)
1174 | (interactive (list (aichat-read-region-or-input ,input-prompt)))
1175 | (when text
1176 | (let ((query (format ,text-format text)))
1177 | (aichat-bingai-assistant query ,style)))))
1178 | (when ,replace-or-insert
1179 | (defun ,replace-func(text)
1180 | (interactive (list (aichat-read-region-or-input ,input-prompt)))
1181 | (when text
1182 | (let ((query (format ,text-format text)))
1183 | (aichat-bingai-replace-or-insert query ,style))))))))
1184 |
1185 | (provide 'aichat-bingai)
1186 |
1187 | ;;; aichat-bingai.el ends here
1188 |
--------------------------------------------------------------------------------