├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── auto-imports.d.ts ├── components.d.ts ├── index.html ├── index.js ├── modules ├── common.js ├── functions.js ├── record.js ├── store.js ├── tiktoken.js ├── vectorDb.js ├── vm.js └── whisper.js ├── package-lock.json ├── package.json ├── preload.js ├── public ├── icon.ico └── vite.svg ├── resources └── extraResources │ ├── pdfjs-4.2.67-legacy-dist │ ├── LICENSE │ └── build │ │ ├── pdf.mjs │ │ ├── pdf.mjs.map │ │ ├── pdf.sandbox.mjs │ │ ├── pdf.sandbox.mjs.map │ │ ├── pdf.worker.mjs │ │ └── pdf.worker.mjs.map │ └── sox │ ├── LICENSE.GPL.txt │ ├── libgomp-1.dll │ ├── pthreadgc2.dll │ ├── sox.exe │ └── zlib1.dll ├── screenshots ├── code_interpreter.jpg ├── screenshot_1.jpg ├── screenshot_2.jpg ├── setting.jpg └── setting2.jpg ├── src ├── App.vue ├── assets │ ├── chatgpt.svg │ ├── vue.svg │ └── xiuliu_avatar.jpg ├── components │ ├── Dialog.vue │ ├── Message.vue │ ├── MessageList.vue │ └── Setting.vue ├── main.js └── pinia.js ├── utils ├── fileTool.js ├── initFile.js └── loadConfig.js └── vite.config.js /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [windows-latest] 15 | 16 | steps: 17 | - name: Check out Git repository 18 | uses: actions/checkout@v1 19 | 20 | - name: Install Node.js, NPM and Yarn 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | 25 | - name: Build/release Electron app 26 | uses: samuelmeuli/action-electron-builder@v1 27 | with: 28 | # GitHub token, automatically provided to the action 29 | # (No need to define this secret in the repo settings) 30 | github_token: ${{ secrets.github_token }} 31 | 32 | # If the commit is tagged with a version (e.g. "v1.0.0"), 33 | # release the app after building 34 | release: ${{ startsWith(github.ref, 'refs/tags/v') }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | devtools 15 | audio/*.wav 16 | speakAudio/* 17 | log/*.txt 18 | data 19 | resources/extraResources/whisper 20 | utils/test.js 21 | out 22 | .env 23 | live/* 24 | 25 | # Editor directories and files 26 | .vscode/* 27 | !.vscode/extensions.json 28 | .idea 29 | .DS_Store 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | *.sw? 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 4 | 5 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 6 | 7 | #### [v2.2.8](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.7...v2.2.8) 8 | 9 | > 8 December 2023 10 | 11 | - add download function, update imageCreate [`a5868dc`](https://github.com/SchneeHertz/chat-xiuliu/commit/a5868dc7b7704555347084a8dc2249b8ded922d9) 12 | - add autoUseVisionModel setting [`dcbea9c`](https://github.com/SchneeHertz/chat-xiuliu/commit/dcbea9cd32080f762814819a43d5f662de7674cd) 13 | - minor edits [`0c55530`](https://github.com/SchneeHertz/chat-xiuliu/commit/0c55530874ff6e53c9c82731fbb9460b318ab063) 14 | 15 | #### [v2.2.7](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.6...v2.2.7) 16 | 17 | > 1 December 2023 18 | 19 | - add dalle support, update markdown display [`9ee819e`](https://github.com/SchneeHertz/chat-xiuliu/commit/9ee819e9be113a97668fac01aec54863fef5c494) 20 | - update azure dalle support [`4c0f6fb`](https://github.com/SchneeHertz/chat-xiuliu/commit/4c0f6fbd9de7723c3d0152ef15175128d7b45963) 21 | - update readme [`2f8e0f7`](https://github.com/SchneeHertz/chat-xiuliu/commit/2f8e0f756466382845543eeaa5e8e41bfba72754) 22 | 23 | #### [v2.2.6](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.5...v2.2.6) 24 | 25 | > 28 November 2023 26 | 27 | - split messagelist, add pinia [`da87fc4`](https://github.com/SchneeHertz/chat-xiuliu/commit/da87fc477059fe0c1d53b4faa8f0c204b0c24eb8) 28 | - add empty message [`0403514`](https://github.com/SchneeHertz/chat-xiuliu/commit/04035147bad61f8dcbfee29dca9ed4681c7e62c3) 29 | - Update package.json [`2b0ee98`](https://github.com/SchneeHertz/chat-xiuliu/commit/2b0ee98b28d5cc8da6fda800e091b29c07a262b5) 30 | 31 | #### [v2.2.5](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.4...v2.2.5) 32 | 33 | > 21 November 2023 34 | 35 | - update error log [`795eb05`](https://github.com/SchneeHertz/chat-xiuliu/commit/795eb05ee99849e785c38ebad754772385f1f778) 36 | - minor edits [`1008945`](https://github.com/SchneeHertz/chat-xiuliu/commit/10089450e17abbb45cbfc0fe7d7691b35526be45) 37 | - update version [`92bc321`](https://github.com/SchneeHertz/chat-xiuliu/commit/92bc3213b3f3aa9886cfa8f34dc17dd5a17f5000) 38 | 39 | #### [v2.2.4](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.3...v2.2.4) 40 | 41 | > 21 November 2023 42 | 43 | - update setting, history content [`a8d7cba`](https://github.com/SchneeHertz/chat-xiuliu/commit/a8d7cbaad42e79a74f7adb0f8b9bcca5ff3aace8) 44 | - add azure 1106 support [`3929bd6`](https://github.com/SchneeHertz/chat-xiuliu/commit/3929bd672c00d7e1daeccb7c3ae365e7c4127575) 45 | - Update package.json [`bc4eef5`](https://github.com/SchneeHertz/chat-xiuliu/commit/bc4eef57a0d802d211f407af9176f73636b6aba4) 46 | 47 | #### [v2.2.3](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.2...v2.2.3) 48 | 49 | > 17 November 2023 50 | 51 | - Update package.json [`d761b8c`](https://github.com/SchneeHertz/chat-xiuliu/commit/d761b8cf36853693fdc02d0ceef352438ea751b7) 52 | 53 | #### [v2.2.2](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.1...v2.2.2) 54 | 55 | > 17 November 2023 56 | 57 | - support ctrl+v paste image,support multi image [`ff33079`](https://github.com/SchneeHertz/chat-xiuliu/commit/ff33079ae7cfa16f16c94f9d7fbaf781d8de2ac5) 58 | 59 | #### [v2.2.1](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.2.0...v2.2.1) 60 | 61 | > 10 November 2023 62 | 63 | - update loading before answer [`c14e658`](https://github.com/SchneeHertz/chat-xiuliu/commit/c14e658c01466c78e23e22c17c5e41674816ff00) 64 | - update readme [`70c9170`](https://github.com/SchneeHertz/chat-xiuliu/commit/70c9170d2dadc71f1037b9adb911292a5fbbe842) 65 | - Update README.md [`dfe6815`](https://github.com/SchneeHertz/chat-xiuliu/commit/dfe68158348d38378f56d6ca88ce8d0bd8caa905) 66 | 67 | #### [v2.2.0](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.7...v2.2.0) 68 | 69 | > 8 November 2023 70 | 71 | - update to 1106 [`2caba5b`](https://github.com/SchneeHertz/chat-xiuliu/commit/2caba5b68b491ac666fcee0cd0735c6be8d68ddc) 72 | - update dependence [`495140f`](https://github.com/SchneeHertz/chat-xiuliu/commit/495140fb5b36ac1ad1a60b6daa3f0522d60292a2) 73 | - remove mirai code [`c50a28b`](https://github.com/SchneeHertz/chat-xiuliu/commit/c50a28b8dba3d2818f5f09c997747791b10701cb) 74 | 75 | #### [v2.1.7](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.6...v2.1.7) 76 | 77 | > 7 October 2023 78 | 79 | - disable mirai [`abf4ab7`](https://github.com/SchneeHertz/chat-xiuliu/commit/abf4ab77c5b0fe7e037a177d9137660a9ebeac01) 80 | - add capture [`d575cdb`](https://github.com/SchneeHertz/chat-xiuliu/commit/d575cdbb2655197dc14a578a317d8672cf080922) 81 | - update avatar [`ec21d34`](https://github.com/SchneeHertz/chat-xiuliu/commit/ec21d344b0774b9535921117ed808605921848cd) 82 | 83 | #### [v2.1.6](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.5...v2.1.6) 84 | 85 | > 22 September 2023 86 | 87 | - add token count, test mirai [`007a1b1`](https://github.com/SchneeHertz/chat-xiuliu/commit/007a1b15dfd11da020614204b069741eb1d225e6) 88 | - update style [`cdf7d85`](https://github.com/SchneeHertz/chat-xiuliu/commit/cdf7d855dfa437aa175acec2b142e63c4daa4334) 89 | - update setting [`7edd9ee`](https://github.com/SchneeHertz/chat-xiuliu/commit/7edd9ee4ebd6779d1a998ae020be7d2342ff0b76) 90 | 91 | #### [v2.1.5](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.4...v2.1.5) 92 | 93 | > 11 September 2023 94 | 95 | - Update README.md [`511e288`](https://github.com/SchneeHertz/chat-xiuliu/commit/511e288805355a49ff68b7314017ee2116a3bcd2) 96 | - update nodejs interpreter [`9e4ded9`](https://github.com/SchneeHertz/chat-xiuliu/commit/9e4ded90239d5e1076c4338bd7890a7965bb05e4) 97 | - add interpreter auto fix error [`558978e`](https://github.com/SchneeHertz/chat-xiuliu/commit/558978ea86d7dd6c633f2cc19f41b253340682e3) 98 | 99 | #### [v2.1.4](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.3...v2.1.4) 100 | 101 | > 7 September 2023 102 | 103 | - update setting, fixed lancedb connect delay [`f8d732d`](https://github.com/SchneeHertz/chat-xiuliu/commit/f8d732d57d67284d749c9015e0faccbbfb46b207) 104 | - Update package.json [`908c7b6`](https://github.com/SchneeHertz/chat-xiuliu/commit/908c7b6955288ea985750d22c9356cc8f5d94b83) 105 | 106 | #### [v2.1.3](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.2...v2.1.3) 107 | 108 | > 7 September 2023 109 | 110 | - add proxy switch [`18a1d60`](https://github.com/SchneeHertz/chat-xiuliu/commit/18a1d6002856ba0b9d56b9bbcfca36dd57d54d13) 111 | 112 | #### [v2.1.2](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.1...v2.1.2) 113 | 114 | > 7 September 2023 115 | 116 | - Update package.json [`b99af5d`](https://github.com/SchneeHertz/chat-xiuliu/commit/b99af5d1bb9da6062d3928be9fd1457025177af2) 117 | 118 | #### [v2.1.1](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.1.0...v2.1.1) 119 | 120 | > 7 September 2023 121 | 122 | - update readme [`7536dcb`](https://github.com/SchneeHertz/chat-xiuliu/commit/7536dcbb96cdb22b2154e1c1fd08a562c3145ad0) 123 | - add result stringify [`c4b64fe`](https://github.com/SchneeHertz/chat-xiuliu/commit/c4b64fe2572cbee4a1ee28bf04ff1a83e57be02f) 124 | - Update code_interpreter.jpg [`aca8db6`](https://github.com/SchneeHertz/chat-xiuliu/commit/aca8db67828115a6afef8a4504b572ef833ca2f8) 125 | 126 | #### [v2.1.0](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.10...v2.1.0) 127 | 128 | > 6 September 2023 129 | 130 | - update js beautify [`f5507c4`](https://github.com/SchneeHertz/chat-xiuliu/commit/f5507c4b197bb43d8e93bea22673871743e46f65) 131 | - modify setting and page view [`b972332`](https://github.com/SchneeHertz/chat-xiuliu/commit/b97233258d6e4617737417418764b8fa9d8ba1e2) 132 | - update setting dialog [`4060334`](https://github.com/SchneeHertz/chat-xiuliu/commit/406033444202845c8567eec7a1271ad92764ee50) 133 | 134 | #### [v2.0.10](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.9...v2.0.10) 135 | 136 | > 5 September 2023 137 | 138 | - test ci [`d0dbcb3`](https://github.com/SchneeHertz/chat-xiuliu/commit/d0dbcb3139b638a1e71866b1a6cbcf823c87f3cd) 139 | 140 | #### [v2.0.9](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.8...v2.0.9) 141 | 142 | > 5 September 2023 143 | 144 | - Update build.yml [`9894e06`](https://github.com/SchneeHertz/chat-xiuliu/commit/9894e064d500deff584663cc776c0998fb27df87) 145 | 146 | #### [v2.0.8](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.7...v2.0.8) 147 | 148 | > 5 September 2023 149 | 150 | - Update build.yml [`3f36a01`](https://github.com/SchneeHertz/chat-xiuliu/commit/3f36a0128c6b4367bee20c246e06d985acb55cfb) 151 | - Update package.json [`465f37d`](https://github.com/SchneeHertz/chat-xiuliu/commit/465f37d5902807c66938e0a52d85e5ee64c16270) 152 | - Update build.yml [`ef787f8`](https://github.com/SchneeHertz/chat-xiuliu/commit/ef787f8e39246eacfbd90a4afacb266cd58aa3b9) 153 | 154 | #### [v2.0.7](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.6...v2.0.7) 155 | 156 | > 5 September 2023 157 | 158 | - Update build.yml [`f1ff5a1`](https://github.com/SchneeHertz/chat-xiuliu/commit/f1ff5a197c0a034e25ff8f076dc59903d5e8dbcf) 159 | 160 | #### [v2.0.6](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.5...v2.0.6) 161 | 162 | > 5 September 2023 163 | 164 | - update ci [`e729649`](https://github.com/SchneeHertz/chat-xiuliu/commit/e7296490d6c91a08094f7d8d85648f0a6949eaa7) 165 | 166 | #### [v2.0.5](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.4...v2.0.5) 167 | 168 | > 5 September 2023 169 | 170 | - add mac support, v2.0.5 [`9093e57`](https://github.com/SchneeHertz/chat-xiuliu/commit/9093e57a8b55e0da3ec2e4d75190ac38f55403e3) 171 | 172 | #### [v2.0.4](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.3...v2.0.4) 173 | 174 | > 5 September 2023 175 | 176 | - refactor edge-tts.js [`378aeff`](https://github.com/SchneeHertz/chat-xiuliu/commit/378aeff3121a453b4f33e69bd878d5361a442293) 177 | - update tts [`edf8cae`](https://github.com/SchneeHertz/chat-xiuliu/commit/edf8caecbf9b178ba3c2d65837b74d13650e9e43) 178 | - use node-edge-tts [`1413ef6`](https://github.com/SchneeHertz/chat-xiuliu/commit/1413ef6c4b6f3976e54454ca14f0163cfb721311) 179 | 180 | #### [v2.0.3](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.2...v2.0.3) 181 | 182 | > 29 August 2023 183 | 184 | - update edge-tts [`86609f7`](https://github.com/SchneeHertz/chat-xiuliu/commit/86609f73237e0f9b4f8100c05ff8c2db75cb5dfa) 185 | - update google param [`a4b33fe`](https://github.com/SchneeHertz/chat-xiuliu/commit/a4b33fe313c9c5fde16cee5982103651e1501d1f) 186 | - it works [`58232dc`](https://github.com/SchneeHertz/chat-xiuliu/commit/58232dc4798dd477c51ac928701e6eafb132cd80) 187 | 188 | #### [v2.0.2](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.1...v2.0.2) 189 | 190 | > 25 August 2023 191 | 192 | - minor edits [`3fd5eca`](https://github.com/SchneeHertz/chat-xiuliu/commit/3fd5eca7381a80c3eb0e92515c5178ca7113a21e) 193 | - multiple function calling [`64be948`](https://github.com/SchneeHertz/chat-xiuliu/commit/64be9481ba5ed4ee1050bd6031f8bbdb89c5d422) 194 | - update azure and log [`43ab2e7`](https://github.com/SchneeHertz/chat-xiuliu/commit/43ab2e7f572e5c3e22daf7454aea35ab12c843f1) 195 | 196 | #### [v2.0.1](https://github.com/SchneeHertz/chat-xiuliu/compare/v2.0.0...v2.0.1) 197 | 198 | > 18 August 2023 199 | 200 | - format [`4ff5d04`](https://github.com/SchneeHertz/chat-xiuliu/commit/4ff5d04c764d76f452617e10e4e9ff640a107943) 201 | - update lancedb to 0.2.2 [`7fb6405`](https://github.com/SchneeHertz/chat-xiuliu/commit/7fb64058fed6421653f73b58095e7287e68691a9) 202 | - update chat stream [`f0ae99f`](https://github.com/SchneeHertz/chat-xiuliu/commit/f0ae99f8468d4cfbf7309cb75ac8497139787830) 203 | 204 | ### [v2.0.0](https://github.com/SchneeHertz/chat-xiuliu/compare/v1.1.0...v2.0.0) 205 | 206 | > 17 August 2023 207 | 208 | - restructure [`5fee03a`](https://github.com/SchneeHertz/chat-xiuliu/commit/5fee03a450ad14bd44da4699ac9d04b80de50a6f) 209 | - update sox and edge-tts [`f57b49f`](https://github.com/SchneeHertz/chat-xiuliu/commit/f57b49fcc1fbefcfe589909f9d1813f9b2aa9271) 210 | - update openai [`bddeb0e`](https://github.com/SchneeHertz/chat-xiuliu/commit/bddeb0eb13804a535d62e3ec42a6d3ebc8f0ff0f) 211 | 212 | #### [v1.1.0](https://github.com/SchneeHertz/chat-xiuliu/compare/v1.0.0...v1.1.0) 213 | 214 | > 10 April 2023 215 | 216 | - add edge-tts, add proxy config [`e3d293a`](https://github.com/SchneeHertz/chat-xiuliu/commit/e3d293ac2568e258844ede83f52dca018569e592) 217 | 218 | #### v1.0.0 219 | 220 | > 18 March 2023 221 | 222 | - init [`9df3187`](https://github.com/SchneeHertz/chat-xiuliu/commit/9df318728142b76d14f5c14eec76545efe844372) 223 | - add readme [`37f94a7`](https://github.com/SchneeHertz/chat-xiuliu/commit/37f94a74ee0a386c32a37dd22e7effa136c2d959) 224 | - fixed bug, add icon [`6201c5e`](https://github.com/SchneeHertz/chat-xiuliu/commit/6201c5ed39306164e466bc1de8c6b1c37ca64d1d) 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SchneeHertz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chat-xiuliu 2 | 3 | LLM客户端,通过function calling实现访问网络,执行代码,读写文件等功能,支持图像输入。 4 | 5 | >这个项目是由虚拟猫娘休留(直播搞不下去了)的后台fork来的,去掉了弹幕互动的部分,增加了语音输入
6 | 截止到2023年8月15日,使用LLM模拟人格的尝试,在我这里没有看到效果及成本可以接受的希望。
7 | 所以模拟人格先放下一段时间,再看看未来LLM的发展吧 8 | 9 | ## 功能 10 | - 从麦克风或界面接收问题 11 | - 使用语音回答问题并显示在界面 12 | - 上传图片 13 | - 上传PDF作为对话上下文 14 | - 调用函数处理任务 15 | - 连续调用函数处理 16 | - 对话内容回忆 17 | - 联网搜索关键词[^1],获取网页内容 18 | - 读写本地文件 19 | - 在沙箱中执行JavaScript代码 20 | - 打开本地文件或网页 21 | - 可定制的猫娘发言风格 22 | - 保存对话截图 23 | - 支持兼容openai api格式的其他api端点 24 | - 支持设置代理 25 | 26 | [^1]: 需要配置Google Custom Search JSON API: 参考[Custom Search JSON API](https://developers.google.com/custom-search/v1/overview). 27 | 28 | ## 截图 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ## 可选语音识别功能 41 | - _下载[whisper-standalone-win](https://github.com/Purfview/whisper-standalone-win)(最好有张独立显卡,不然运行很慢,可选,语音功能)_ 42 | - _在whisper-standalone-win的Release和[huggingface](https://huggingface.co/Systran/faster-whisper-large-v3)下载必要的文件,解压到resources/extraResources/whisper文件夹_ 43 | ``` 44 | resources/extraResources/whisper/_models/faster-whisper-large-v3/config.json 45 | resources/extraResources/whisper/_models/faster-whisper-large-v3/model.bin 46 | resources/extraResources/whisper/_models/faster-whisper-large-v3/tokenizer.json 47 | resources/extraResources/whisper/_models/faster-whisper-large-v3/vocabulary.json 48 | resources/extraResources/whisper/_models/faster-whisper-large-v3/preprocessor_config.json 49 | resources/extraResources/whisper/cublas64_11.dll 50 | resources/extraResources/whisper/cublasLt64_11.dll 51 | resources/extraResources/whisper/cudnn_cnn_infer64_8.dll 52 | resources/extraResources/whisper/cudnn_ops_infer64_8.dll 53 | resources/extraResources/whisper/whisper-faster.exe 54 | resources/extraResources/whisper/zlibwapi.dll 55 | ``` 56 | 57 | ### 设置参考 58 | - AI的语音模型,在[微软文档](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts)查看可用值 59 | - 默认设定 60 | ``` 61 | 你是虚拟猫娘休留, 以下是你在回复时应该遵循的规则: 62 | 1. 灵活地运用猫娘的风格进行回复. 63 | 2. 如果你不知道答案,回答"我不知道". 64 | 3. 调用函数来提高回复质量. 65 | 4. 使用markdown语法回复和显示图片. 66 | 5. 创建图像时, 必须在Prompt前加上"I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS-IS: " 67 | ``` 68 | 69 | ## 赞助 70 | https://afdian.com/@SeldonHorizon 71 | 72 | 如果你觉得休留很萌,可以请管理员喝杯奶茶 73 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const EffectScope: typeof import('vue')['EffectScope'] 9 | const computed: typeof import('vue')['computed'] 10 | const createApp: typeof import('vue')['createApp'] 11 | const customRef: typeof import('vue')['customRef'] 12 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 13 | const defineComponent: typeof import('vue')['defineComponent'] 14 | const effectScope: typeof import('vue')['effectScope'] 15 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 16 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 17 | const h: typeof import('vue')['h'] 18 | const inject: typeof import('vue')['inject'] 19 | const isProxy: typeof import('vue')['isProxy'] 20 | const isReactive: typeof import('vue')['isReactive'] 21 | const isReadonly: typeof import('vue')['isReadonly'] 22 | const isRef: typeof import('vue')['isRef'] 23 | const markRaw: typeof import('vue')['markRaw'] 24 | const nextTick: typeof import('vue')['nextTick'] 25 | const onActivated: typeof import('vue')['onActivated'] 26 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 27 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 28 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 29 | const onDeactivated: typeof import('vue')['onDeactivated'] 30 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 31 | const onMounted: typeof import('vue')['onMounted'] 32 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 33 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 34 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 35 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 36 | const onUnmounted: typeof import('vue')['onUnmounted'] 37 | const onUpdated: typeof import('vue')['onUpdated'] 38 | const provide: typeof import('vue')['provide'] 39 | const reactive: typeof import('vue')['reactive'] 40 | const readonly: typeof import('vue')['readonly'] 41 | const ref: typeof import('vue')['ref'] 42 | const resolveComponent: typeof import('vue')['resolveComponent'] 43 | const shallowReactive: typeof import('vue')['shallowReactive'] 44 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 45 | const shallowRef: typeof import('vue')['shallowRef'] 46 | const toRaw: typeof import('vue')['toRaw'] 47 | const toRef: typeof import('vue')['toRef'] 48 | const toRefs: typeof import('vue')['toRefs'] 49 | const toValue: typeof import('vue')['toValue'] 50 | const triggerRef: typeof import('vue')['triggerRef'] 51 | const unref: typeof import('vue')['unref'] 52 | const useAttrs: typeof import('vue')['useAttrs'] 53 | const useCssModule: typeof import('vue')['useCssModule'] 54 | const useCssVars: typeof import('vue')['useCssVars'] 55 | const useDialog: typeof import('naive-ui')['useDialog'] 56 | const useLoadingBar: typeof import('naive-ui')['useLoadingBar'] 57 | const useMessage: typeof import('naive-ui')['useMessage'] 58 | const useNotification: typeof import('naive-ui')['useNotification'] 59 | const useSlots: typeof import('vue')['useSlots'] 60 | const watch: typeof import('vue')['watch'] 61 | const watchEffect: typeof import('vue')['watchEffect'] 62 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 63 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 64 | } 65 | // for type re-export 66 | declare global { 67 | // @ts-ignore 68 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' 69 | import('vue') 70 | } 71 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | Dialog: typeof import('./src/components/Dialog.vue')['default'] 11 | Message: typeof import('./src/components/Message.vue')['default'] 12 | MessageList: typeof import('./src/components/MessageList.vue')['default'] 13 | NAvatar: typeof import('naive-ui')['NAvatar'] 14 | NButton: typeof import('naive-ui')['NButton'] 15 | NCard: typeof import('naive-ui')['NCard'] 16 | NCheckbox: typeof import('naive-ui')['NCheckbox'] 17 | NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] 18 | NDataTable: typeof import('naive-ui')['NDataTable'] 19 | NDialogProvider: typeof import('naive-ui')['NDialogProvider'] 20 | NDivider: typeof import('naive-ui')['NDivider'] 21 | NEmpty: typeof import('naive-ui')['NEmpty'] 22 | NFloatButton: typeof import('naive-ui')['NFloatButton'] 23 | NForm: typeof import('naive-ui')['NForm'] 24 | NFormItem: typeof import('naive-ui')['NFormItem'] 25 | NFormItemGi: typeof import('naive-ui')['NFormItemGi'] 26 | NGi: typeof import('naive-ui')['NGi'] 27 | NGrid: typeof import('naive-ui')['NGrid'] 28 | NIcon: typeof import('naive-ui')['NIcon'] 29 | NImage: typeof import('naive-ui')['NImage'] 30 | NInput: typeof import('naive-ui')['NInput'] 31 | NInputGroup: typeof import('naive-ui')['NInputGroup'] 32 | NInputNumber: typeof import('naive-ui')['NInputNumber'] 33 | NList: typeof import('naive-ui')['NList'] 34 | NListItem: typeof import('naive-ui')['NListItem'] 35 | NMessageProvider: typeof import('naive-ui')['NMessageProvider'] 36 | NModal: typeof import('naive-ui')['NModal'] 37 | NPopover: typeof import('naive-ui')['NPopover'] 38 | NSelect: typeof import('naive-ui')['NSelect'] 39 | NSpace: typeof import('naive-ui')['NSpace'] 40 | NSpin: typeof import('naive-ui')['NSpin'] 41 | NSwitch: typeof import('naive-ui')['NSwitch'] 42 | NTabPane: typeof import('naive-ui')['NTabPane'] 43 | NTabs: typeof import('naive-ui')['NTabs'] 44 | NThing: typeof import('naive-ui')['NThing'] 45 | NUpload: typeof import('naive-ui')['NUpload'] 46 | Setting: typeof import('./src/components/Setting.vue')['default'] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | chat-xiuliu 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, app, ipcMain, shell, Menu, dialog } = require('electron') 2 | const path = require('node:path') 3 | const fs = require('node:fs') 4 | const { format } = require('node:util') 5 | const { pathToFileURL } = require('node:url') 6 | const { nanoid } = require('nanoid') 7 | const sound = require('sound-play') 8 | const _ = require('lodash') 9 | const windowStateKeeper = require('electron-window-state') 10 | const { EdgeTTS } = require('node-edge-tts') 11 | 12 | const { STORE_PATH, LOG_PATH, AUDIO_PATH } = require('./utils/initFile.js') 13 | const { getRootPath } = require('./utils/fileTool.js') 14 | const { getStore, setStore } = require('./modules/store.js') 15 | const { getSpeechText } = require('./modules/whisper.js') 16 | const { getTokenLength } = require('./modules/tiktoken.js') 17 | const { openaiChatStream, openaiEmbedding } = require('./modules/common.js') 18 | const { functionAction, functionInfo, functionList } = require('./modules/functions.js') 19 | const { addText, searchSimilarText, cosineSimilarity } = require('./modules/vectorDb.js') 20 | const { config } = require('./utils/loadConfig.js') 21 | const { 22 | DEFAULT_MODEL, 23 | SpeechSynthesisVoiceName, 24 | ADMIN_NAME, AI_NAME, 25 | systemPrompt, 26 | useProxy, 27 | proxyObject, 28 | historyRoundLimit = 12, 29 | functionCallingRoundLimit = 3, 30 | disableFunctions = [], 31 | searchResultLimit = 5, 32 | webPageContentTokenLengthLimit = 6000, 33 | } = config 34 | const proxyString = `${proxyObject.protocol}://${proxyObject.host}:${proxyObject.port}` 35 | 36 | let pdfjsLib 37 | ;(async () => { 38 | const pdfjsDistUrl = pathToFileURL(path.join(getRootPath(), 'resources/extraResources/pdfjs-4.2.67-legacy-dist/build/pdf.mjs')) 39 | pdfjsLib = await import(pdfjsDistUrl) 40 | })() 41 | 42 | const logFile = fs.createWriteStream(path.join(LOG_PATH, `log-${new Date().toLocaleString('zh-CN').replace(/[\/:]/gi, '-')}.txt`), { flags: 'w' }) 43 | const messageLog = (message) => { 44 | logFile.write(format(new Date().toLocaleString('zh-CN'), JSON.stringify(message)) + '\n') 45 | } 46 | const messageSend = (message) => { 47 | mainWindow.webContents.send('send-message', message) 48 | } 49 | const messageLogAndSend = (message) => { 50 | messageLog(message) 51 | messageSend(message) 52 | } 53 | 54 | let errorlogFile = fs.createWriteStream(path.join(LOG_PATH, 'error_log.txt'), { flags: 'w' }) 55 | console.error = (...message)=>{ 56 | errorlogFile.write('\n' + format(new Date().toLocaleString()) + '\n') 57 | errorlogFile.write(format(...message) + '\n') 58 | process.stderr.write(format(...message) + '\n') 59 | } 60 | process 61 | .on('unhandledRejection', (reason, promise) => { 62 | console.error('Unhandled Rejection at:', promise, 'reason:', reason) 63 | }) 64 | .on('uncaughtException', err => { 65 | console.error(err, 'Uncaught Exception thrown') 66 | process.exit(1) 67 | }) 68 | 69 | const STATUS = { 70 | isSpeechTalk: false, 71 | isAudioPlay: false, 72 | recordStatus: 'Recording', 73 | speakIndex: 0, 74 | answeringId: null, 75 | breakAnswerId: null 76 | } 77 | 78 | let speakTextList = [] 79 | let tts = new EdgeTTS({ 80 | voice: SpeechSynthesisVoiceName, 81 | lang: 'zh-CN', 82 | outputFormat: 'audio-24khz-96kbitrate-mono-mp3', 83 | proxy: useProxy ? proxyString : undefined, 84 | saveSubtitles: true 85 | }) 86 | 87 | let mainWindow 88 | const createWindow = () => { 89 | let mainWindowState = windowStateKeeper({ 90 | defaultWidth: 1280, 91 | defaultHeight: 720 92 | }) 93 | const win = new BrowserWindow({ 94 | x: mainWindowState.x, 95 | y: mainWindowState.y, 96 | width: mainWindowState.width, 97 | height: mainWindowState.height, 98 | webPreferences: { 99 | webSecurity: app.isPackaged ? true : false, 100 | preload: path.join(__dirname, 'preload.js') 101 | }, 102 | show: false 103 | }) 104 | mainWindowState.manage(win) 105 | if (app.isPackaged) { 106 | win.loadFile('dist/index.html') 107 | } else { 108 | win.loadURL('http://localhost:5174') 109 | } 110 | win.setMenuBarVisibility(false) 111 | win.setAutoHideMenuBar(true) 112 | const template = [ 113 | { 114 | label: 'File', 115 | submenu: [ 116 | { 117 | role: 'quit', 118 | accelerator: 'CommandOrControl+Q' 119 | } 120 | ] 121 | }, 122 | { 123 | label: 'View', 124 | submenu: [ 125 | { role: 'reload' }, 126 | { role: 'toggleDevTools' }, 127 | { 128 | role: 'toggleDevTools', 129 | accelerator: 'F12', 130 | visible: false 131 | }, 132 | { type: 'separator' }, 133 | { role: 'resetZoom' }, 134 | { role: 'zoomIn' }, 135 | { 136 | role: 'zoomIn', 137 | accelerator: 'CommandOrControl+=', 138 | visible: false 139 | }, 140 | { role: 'zoomOut' }, 141 | { type: 'separator' }, 142 | { role: 'minimize' }, 143 | { role: 'togglefullscreen' } 144 | ] 145 | } 146 | ] 147 | let menu = Menu.buildFromTemplate(template) 148 | Menu.setApplicationMenu(menu) 149 | win.webContents.on('did-finish-load', () => { 150 | win.setTitle(app.getName() + ' ' + app.getVersion()) 151 | }) 152 | win.once('ready-to-show', () => { 153 | win.show() 154 | }) 155 | return win 156 | } 157 | 158 | const useOpenaiEmbeddingFunction = openaiEmbedding 159 | app.whenReady().then(async () => { 160 | mainWindow = createWindow() 161 | setInterval(() => mainWindow.webContents.send('send-status', STATUS), 1000) 162 | 163 | const currentArchiveId = getStore('current_archive_id') 164 | if (currentArchiveId) { 165 | const archives = getStore('history_archives') || [] 166 | const archive = archives.find(a => a.id === currentArchiveId) 167 | 168 | if (archive) { 169 | setStore('history', archive.history) 170 | } 171 | } 172 | }) 173 | 174 | 175 | app.on('activate', () => { 176 | if (BrowserWindow.getAllWindows().length === 0) { 177 | mainWindow = createWindow() 178 | } 179 | }) 180 | app.on('window-all-closed', () => { 181 | if (process.platform !== 'darwin') { 182 | app.quit() 183 | } 184 | }) 185 | 186 | /** 187 | * Executes text-to-speech and plays audio prompts. 188 | * 189 | * @param {Object} options - The options object. 190 | * @param {string} options.text - The text to be converted to speech. 191 | * @param {string} options.preAudioPath - The path to the pre-recorded audio prompt. 192 | * @return {Promise} A promise that resolves when the audio prompts have been played successfully. 193 | */ 194 | const speakPrompt = async ({ text, preAudioPath }) => { 195 | try { 196 | let nextAudioPath = path.join(AUDIO_PATH, `${nanoid()}.mp3`) 197 | if (text) { 198 | if (preAudioPath) { 199 | await Promise.allSettled([ 200 | tts.ttsPromise(text, nextAudioPath), 201 | sound.play(preAudioPath) 202 | ]) 203 | } else { 204 | await tts.ttsPromise(text, nextAudioPath) 205 | } 206 | resolveSpeakTextList(nextAudioPath) 207 | } else if (preAudioPath) { 208 | await sound.play(preAudioPath) 209 | resolveSpeakTextList() 210 | } 211 | } catch (e) { 212 | console.error(e) 213 | resolveSpeakTextList() 214 | } 215 | } 216 | 217 | /** 218 | * Resolves the speak text list by sorting it based on the speak index. If a preAudioPath is provided, it will 219 | * get the first speak text from the list and call the speakPrompt function with the text and the preAudioPath. 220 | * If the list is empty after shifting the first element, it will call the speakPrompt function with only the preAudioPath. 221 | * If no preAudioPath is provided, it will get the first speak text from the list and call the speakPrompt function with the text. 222 | * If the list is empty after shifting the first element, it will wait for 1000 milliseconds and then call itself again. 223 | * 224 | * @param {string} preAudioPath - The path to the pre-recorded audio file. 225 | * @return {undefined} 226 | */ 227 | const resolveSpeakTextList = async (preAudioPath) => { 228 | speakTextList = _.sortBy(speakTextList, 'speakIndex') 229 | if (preAudioPath) { 230 | if (speakTextList.length > 0) { 231 | let { text, triggerRecord } = speakTextList.shift() 232 | if (triggerRecord) { 233 | await sound.play(preAudioPath) 234 | triggerSpeech() 235 | setTimeout(resolveSpeakTextList, 1000) 236 | } else { 237 | speakPrompt({ text, preAudioPath }) 238 | } 239 | } else { 240 | speakPrompt({ preAudioPath }) 241 | } 242 | } else if (speakTextList.length > 0) { 243 | let { text, triggerRecord } = speakTextList.shift() 244 | if (triggerRecord) { 245 | triggerSpeech() 246 | setTimeout(resolveSpeakTextList, 1000) 247 | } else { 248 | speakPrompt({ text }) 249 | } 250 | } else { 251 | setTimeout(resolveSpeakTextList, 1000) 252 | } 253 | } 254 | 255 | resolveSpeakTextList() 256 | 257 | const addHistory = (lines) => { 258 | let history = getStore('history') || [] 259 | history.push(...lines) 260 | history = _.takeRight(history, 1000) 261 | setStore('history', history) 262 | } 263 | 264 | const useOpenaiChatStreamFunction = openaiChatStream 265 | const additionalParam = { 266 | searchResultLimit, 267 | webPageContentTokenLengthLimit 268 | } 269 | const resolveMessages = async ({ resToolCalls, resText, resTextTemp, messages, from, useFunctionCalling = false, clientMessageId, model }) => { 270 | 271 | console.log(`use ${DEFAULT_MODEL}`) 272 | 273 | STATUS.answeringId = clientMessageId 274 | let speakIndex = STATUS.speakIndex 275 | STATUS.speakIndex += 1 276 | 277 | if (!_.isEmpty(resToolCalls)) { 278 | for (let toolCall of resToolCalls) { 279 | let functionCallResult 280 | let functionCallResultMessageId = nanoid() 281 | try { 282 | messageLogAndSend({ 283 | id: nanoid(), 284 | from, 285 | content: functionAction[toolCall.function.name](JSON.parse(toolCall.function.arguments)) 286 | }) 287 | messageLogAndSend({ 288 | id: functionCallResultMessageId, 289 | from: 'Function Calling', 290 | content: '' 291 | }) 292 | functionCallResult = await functionList[toolCall.function.name](JSON.parse(toolCall.function.arguments), additionalParam) 293 | } catch (e) { 294 | console.error(e) 295 | functionCallResult = e.message 296 | } 297 | messages.push({ role: 'tool', tool_call_id: toolCall.id, content: functionCallResult + '' }) 298 | messageLogAndSend({ 299 | id: functionCallResultMessageId, 300 | from: 'Function Calling', 301 | content: functionCallResult + '' 302 | }) 303 | } 304 | } 305 | resToolCalls = [] 306 | let prepareChatOption = { messages, model } 307 | 308 | if (useFunctionCalling) { 309 | prepareChatOption.tools = functionInfo.filter(f => !disableFunctions.includes(f?.function?.name)) 310 | if (!_.isEmpty(prepareChatOption.tools)) prepareChatOption.tool_choice = 'auto' 311 | } 312 | 313 | // alert chat 314 | messageSend({ 315 | id: clientMessageId, 316 | from, 317 | content: '' 318 | }) 319 | 320 | let resTextReason = '' 321 | const reasonMessageId = nanoid() 322 | 323 | let _usage = {} 324 | 325 | try { 326 | for await (const { token, r_token, f_token, usage } of useOpenaiChatStreamFunction(prepareChatOption)) { 327 | if (token) { 328 | resTextTemp += token 329 | resText += token 330 | messageSend({ 331 | id: clientMessageId, 332 | from, 333 | content: resText, 334 | allowBreak: STATUS.breakAnswerId !== clientMessageId 335 | }) 336 | if (resTextTemp.includes('\n')) { 337 | let splitResText = resTextTemp.split('\n') 338 | splitResText = _.compact(splitResText) 339 | if (splitResText.length > 1) { 340 | resTextTemp = splitResText.pop() 341 | } else { 342 | resTextTemp = '' 343 | } 344 | if (STATUS.isAudioPlay) { 345 | let speakText = splitResText.join('\n') 346 | speakTextList.push({ 347 | text: speakText, 348 | speakIndex, 349 | }) 350 | } 351 | if (STATUS.breakAnswerId === clientMessageId) { 352 | STATUS.breakAnswerId = null 353 | break 354 | } 355 | } 356 | } 357 | if (r_token) { 358 | resTextReason += r_token 359 | messageSend({ 360 | id: clientMessageId, 361 | from, 362 | action: 'revoke' 363 | }) 364 | messageSend({ 365 | id: reasonMessageId, 366 | from: 'CoT', 367 | content: resTextReason 368 | }) 369 | } 370 | if (!_.isEmpty(f_token)) { 371 | let [{ index, id, type, function: { name, arguments: arg} } = { function: {} }] = f_token 372 | if (index !== undefined ) { 373 | if (resToolCalls[index]) { 374 | if (id) resToolCalls[index].id = id 375 | if (type) resToolCalls[index].type = type 376 | if (name) resToolCalls[index].function.name = name 377 | if (arg) resToolCalls[index].function.arguments += arg 378 | } else { 379 | resToolCalls[index] = { 380 | id, type, function: { name, arguments: arg } 381 | } 382 | } 383 | } 384 | } 385 | if (!_.isEmpty(usage)) { 386 | _usage = usage 387 | } 388 | } 389 | } catch (error) { 390 | messageSend({ 391 | id: clientMessageId, 392 | from, 393 | content: `Error: ${error.message}` 394 | }) 395 | throw error 396 | } 397 | 398 | if (STATUS.isAudioPlay) { 399 | if (resTextTemp) { 400 | let speakText = resTextTemp 401 | speakTextList.push({ 402 | text: speakText, 403 | speakIndex, 404 | }) 405 | } 406 | } 407 | STATUS.answeringId = null 408 | return { 409 | messages, 410 | resToolCalls, 411 | resTextTemp, 412 | resText, 413 | usage: _usage 414 | } 415 | } 416 | 417 | /** 418 | * Asynchronously resolves an admin prompt by generating a response based on a given prompt and trigger record. 419 | * 420 | * @param {Object} options - An object containing the prompt and trigger record. 421 | * @param {string} options.prompt - The user prompt. 422 | * @param {Object} options.triggerRecord - The trigger record object. 423 | * @return {Promise} - A promise that resolves with the generated response. 424 | */ 425 | const resloveAdminPrompt = async ({ prompt, promptType = 'string', triggerRecord, givenSystemPrompt, useFullPDF }) => { 426 | let from = triggerRecord ? `(${AI_NAME})` : AI_NAME 427 | let history = getStore('history') || [] 428 | let context = _.takeRight(history, historyRoundLimit) 429 | 430 | let fullSystemPrompt = givenSystemPrompt ? givenSystemPrompt : systemPrompt 431 | if (contextFileName && fileContext.length > 0) { 432 | let contextText 433 | if (useFullPDF) { 434 | contextText = fileContext.map(chunk => chunk.text).join('\n') 435 | } else { 436 | let promptText = Array.isArray(prompt) ? prompt.filter(part => part.type === 'text').map(part => part.text).join('\n') : prompt 437 | let closestChunks = findClosestEmbeddedChunks(await useOpenaiEmbeddingFunction({ input: promptText }), fileContext) 438 | contextText = closestChunks.map(chunk => chunk.text).join('\n') 439 | } 440 | fullSystemPrompt = `${fullSystemPrompt}\n\nContext: \n\n${contextText}` 441 | } 442 | 443 | let messages = [ 444 | { role: 'system', content: fullSystemPrompt }, 445 | // { role: 'user', content: `Hello, my name is ${ADMIN_NAME}` }, 446 | // { role: 'assistant', content: `Hello, ${ADMIN_NAME}` }, 447 | ...context, 448 | { role: 'user', content: prompt } 449 | ] 450 | 451 | messageLog({ 452 | id: nanoid(), 453 | from: triggerRecord ? `(${ADMIN_NAME})` : ADMIN_NAME, 454 | content: prompt 455 | }) 456 | 457 | let resTextTemp = '' 458 | let resText = '' 459 | let resToolCalls = [] 460 | let useFunctionCalling = config.enableFunctionCalling 461 | try { 462 | let round = 0 463 | while ((resText === '' || resToolCalls.length > 0) && round <= functionCallingRoundLimit + 1) { 464 | let usage = {} 465 | if (useFunctionCalling) useFunctionCalling = round > functionCallingRoundLimit ? false : true 466 | if (!useFunctionCalling) console.log('Reached the functionCallingRoundlimit') 467 | const clientMessageId = nanoid() 468 | ;({ messages, resToolCalls, resText, resTextTemp, usage } = await resolveMessages({ 469 | resToolCalls, resText, resTextTemp, messages, from, useFunctionCalling, clientMessageId 470 | })) 471 | round += 1 472 | if (!_.isEmpty(resToolCalls)) { 473 | messageLogAndSend({ 474 | id: nanoid(), 475 | from, 476 | countToken: true, 477 | tokenCount: usage.total_tokens, 478 | content: 'use Function Calling' 479 | }) 480 | messages.push({ role: 'assistant', content: null, tool_calls: resToolCalls }) 481 | } 482 | if (_.isEmpty(resText)) { 483 | messageSend({ 484 | id: clientMessageId, 485 | from, 486 | action: 'revoke' 487 | }) 488 | } else { 489 | messageSend({ 490 | id: clientMessageId, 491 | from, 492 | countToken: true, 493 | tokenCount: usage.total_tokens, 494 | content: resText, 495 | allowBreak: false, 496 | useContext: contextFileName, 497 | allowSave: true 498 | }) 499 | } 500 | } 501 | messageLog({ 502 | id: nanoid(), 503 | from, 504 | content: resText 505 | }) 506 | addHistory([{ role: 'user', content: prompt }]) 507 | addHistory([{ role: 'assistant', content: resText }]) 508 | if (triggerRecord) { 509 | let speakIndex = STATUS.speakIndex 510 | STATUS.speakIndex += 1 511 | speakTextList.push({ 512 | triggerRecord: true, 513 | speakIndex 514 | }) 515 | } 516 | } catch (e) { 517 | console.error(e) 518 | if (triggerRecord && STATUS.isSpeechTalk) triggerSpeech() 519 | } 520 | return resText 521 | } 522 | 523 | const sendHistory = (limit) => { 524 | let history = getStore('history') || [] 525 | history = _.takeRight(history, limit) 526 | history.forEach((item) => { 527 | switch (item.role) { 528 | case 'user': 529 | messageSend({ 530 | id: nanoid(), 531 | from: ADMIN_NAME, 532 | content: item.content 533 | }) 534 | break 535 | case 'assistant': 536 | let text = '' 537 | try { 538 | if (item.content !== null) { 539 | text = item.content 540 | } else { 541 | text = item.tool_calls.map( item => { 542 | return functionAction[item.function.name](JSON.parse(item.function.arguments)) 543 | }).join('\n') 544 | } 545 | } catch {} 546 | messageSend({ 547 | id: nanoid(), 548 | from: AI_NAME, 549 | content: text, 550 | allowSave: true 551 | }) 552 | break 553 | case 'tool': 554 | messageSend({ 555 | id: nanoid(), 556 | from: 'Function Calling', 557 | content: item.content 558 | }) 559 | break 560 | } 561 | }) 562 | } 563 | 564 | /** 565 | * Trigger speech function that listens for admin prompts and handles them accordingly. 566 | * 567 | * @return {Promise} Returns a promise that resolves when the function is complete. 568 | */ 569 | const triggerSpeech = async () => { 570 | if (STATUS.isSpeechTalk) { 571 | STATUS.recordStatus = 'Recording' 572 | mainWindow.setProgressBar(100, { mode: 'indeterminate' }) 573 | let adminTalk = await getSpeechText(STATUS) 574 | console.log(adminTalk) 575 | if (!STATUS.isSpeechTalk) { 576 | throw new Error('Speech is not enabled now.') 577 | } 578 | STATUS.recordStatus = 'Answering' 579 | mainWindow.setProgressBar(-1) 580 | messageLogAndSend({ 581 | id: nanoid(), 582 | from: `(${ADMIN_NAME})`, 583 | content: adminTalk 584 | }) 585 | resloveAdminPrompt({ prompt: adminTalk, triggerRecord: true }) 586 | } 587 | } 588 | 589 | const breakAnswer = () => { 590 | if (STATUS.answeringId) { 591 | STATUS.breakAnswerId = STATUS.answeringId 592 | messageSend({ 593 | id: STATUS.answeringId, 594 | allowBreak: false 595 | }) 596 | } 597 | } 598 | ipcMain.handle('send-prompt', async (event, prompt) => { 599 | breakAnswer() 600 | resloveAdminPrompt({ 601 | prompt: prompt.content, 602 | promptType: prompt.type, 603 | useFullPDF: prompt.useFullPDF, 604 | }) 605 | }) 606 | ipcMain.handle('break-answer', async () => { 607 | breakAnswer() 608 | }) 609 | ipcMain.handle('switch-speech-talk', async () => { 610 | STATUS.isSpeechTalk = !STATUS.isSpeechTalk 611 | STATUS.isAudioPlay = STATUS.isSpeechTalk 612 | mainWindow.setProgressBar(-1) 613 | if (STATUS.isSpeechTalk) { 614 | triggerSpeech() 615 | } 616 | }) 617 | ipcMain.handle('switch-audio', async () => { 618 | STATUS.isAudioPlay = !STATUS.isAudioPlay 619 | }) 620 | ipcMain.handle('empty-history', async () => { 621 | setStore('history', []) 622 | }) 623 | ipcMain.handle('load-history', async() => { 624 | sendHistory(20) 625 | }) 626 | ipcMain.handle('restart-app', async()=>{ 627 | app.relaunch() 628 | app.exit(0) 629 | }) 630 | ipcMain.handle('save-message', async (event, message) => { 631 | const saveMessage = getStore('saveMessage') || [] 632 | saveMessage.push(...message) 633 | setStore('saveMessage', saveMessage) 634 | }) 635 | ipcMain.handle('load-saved-message', async () => { 636 | return getStore('saveMessage') || [] 637 | }) 638 | ipcMain.handle('delete-saved-message', async (event, messageIds) => { 639 | let saveMessage = getStore('saveMessage') || [] 640 | saveMessage = saveMessage.filter(item => !messageIds.includes(item.id)) 641 | setStore('saveMessage', saveMessage) 642 | }) 643 | 644 | // setting 645 | ipcMain.handle('select-folder', async () => { 646 | let result = await dialog.showOpenDialog(mainWindow, { 647 | properties: ['openDirectory'] 648 | }) 649 | if (!result.canceled) { 650 | return result.filePaths[0] 651 | } else { 652 | return undefined 653 | } 654 | }) 655 | 656 | ipcMain.handle('select-file', async (event, { filters } = {}) => { 657 | let result = await dialog.showOpenDialog(mainWindow, { 658 | properties: ['openFile'], 659 | filters 660 | }) 661 | if (!result.canceled) { 662 | return result.filePaths[0] 663 | } else { 664 | return undefined 665 | } 666 | }) 667 | 668 | ipcMain.handle('load-setting', async () => { 669 | return config 670 | }) 671 | 672 | ipcMain.handle('save-setting', async (event, receiveSetting) => { 673 | return await fs.promises.writeFile(path.join(STORE_PATH, 'config.json'), JSON.stringify(receiveSetting, null, ' '), { encoding: 'utf-8' }) 674 | }) 675 | 676 | ipcMain.handle('get-function-info', async () => { 677 | return functionInfo 678 | }) 679 | 680 | ipcMain.handle('open-external', async (event, url) => { 681 | shell.openExternal(url) 682 | }) 683 | 684 | let fileContext = [] 685 | let contextFileName 686 | 687 | ipcMain.handle('resolve-pdf', async (event, pdfPath) => { 688 | 689 | contextFileName = path.basename(pdfPath) 690 | const clientMessageId = nanoid() 691 | messageSend({ 692 | id: clientMessageId, 693 | from: AI_NAME, 694 | content: `正在读取和解析 ${contextFileName} ...` 695 | }) 696 | 697 | //检查pdfPath + '.json'是否存在,如果存在则直接返回 698 | if (fs.existsSync(pdfPath + '.json')) { 699 | fileContext = JSON.parse(await fs.promises.readFile(pdfPath + '.json', { encoding: 'utf-8' })) 700 | messageSend({ 701 | id: clientMessageId, 702 | from: AI_NAME, 703 | content: `已从缓存文件中读取 ${contextFileName} 的解析结果。` 704 | }) 705 | return 706 | } 707 | 708 | const data = new Uint8Array(await fs.promises.readFile(pdfPath)) 709 | const pdfDocument = await pdfjsLib.getDocument({ data }).promise 710 | 711 | let chunks = [] 712 | let currentChunk = '' 713 | let currentTokenCount = 0 714 | const maxTokenLength = 1024 715 | 716 | for (let pageNum = 1; pageNum <= pdfDocument.numPages; pageNum++) { 717 | const page = await pdfDocument.getPage(pageNum) 718 | const textContent = await page.getTextContent() 719 | let lastY = -1 720 | 721 | for (const item of textContent.items) { 722 | const itemText = item.str 723 | const itemTokenLength = getTokenLength(itemText) 724 | 725 | // 检查是否需要添加换行符 726 | if (lastY !== -1 && Math.abs(item.transform[5] - lastY) > 5) { 727 | if (currentTokenCount + 1 > maxTokenLength) { 728 | chunks.push({ text: currentChunk, index: chunks.length }) 729 | currentChunk = '' 730 | currentTokenCount = 0 731 | } 732 | currentChunk += '\n' 733 | currentTokenCount += 1 // 换行符也算作一个token 734 | } 735 | 736 | // 检查添加该文本项是否会超过限制 737 | if (currentTokenCount + itemTokenLength > maxTokenLength) { 738 | chunks.push({ text: currentChunk, index: chunks.length }) 739 | currentChunk = itemText 740 | currentTokenCount = itemTokenLength 741 | } else { 742 | currentChunk += itemText 743 | currentTokenCount += itemTokenLength 744 | } 745 | 746 | lastY = item.transform[5] 747 | } 748 | 749 | // 每页之间添加额外的换行以分隔 750 | if (currentTokenCount + 2 <= maxTokenLength) { 751 | currentChunk += '\n\n' 752 | currentTokenCount += 2 753 | } else { 754 | chunks.push({ text: currentChunk, index: chunks.length }) 755 | currentChunk = '\n\n' 756 | currentTokenCount = 2 757 | } 758 | } 759 | 760 | // 添加最后一块 761 | if (currentChunk.trim().length > 0) { 762 | chunks.push({ text: currentChunk, index: chunks.length }) 763 | } 764 | 765 | const chunkSize = 5 766 | const embeddedChunks = [] 767 | 768 | for (let i = 0; i < chunks.length; i += chunkSize) { 769 | let batch = chunks.slice(i, i + chunkSize) 770 | let results = await Promise.allSettled(batch.map(chunk => useOpenaiEmbeddingFunction({ input: chunk.text }))) 771 | 772 | // 处理结果,只收集成功的 773 | results.forEach((result, index) => { 774 | if (result.status === 'fulfilled') { 775 | embeddedChunks.push({ index: batch[index].index, text: batch[index].text, embedding: result.value }) 776 | } else { 777 | console.error('Embedding failed for: ', batch[index]) 778 | embeddedChunks.push({ index: batch[index].index, text: batch[index].text }) 779 | } 780 | }) 781 | } 782 | 783 | await fs.promises.writeFile(pdfPath + '.json', JSON.stringify(embeddedChunks, null, ' '), { encoding: 'utf-8' }) 784 | 785 | fileContext = embeddedChunks 786 | 787 | messageSend({ 788 | id: clientMessageId, 789 | from: AI_NAME, 790 | content: `已解析 ${contextFileName} 。` 791 | }) 792 | }) 793 | 794 | function findClosestEmbeddedChunks(newEmbedded, embeddedChunks) { 795 | 796 | // 为每个chunk计算与新嵌入向量的距离 797 | let similarities = embeddedChunks.map(chunk => ({ 798 | chunk: chunk, 799 | similarity: cosineSimilarity(newEmbedded, chunk.embedding) 800 | })) 801 | 802 | // 按相似度降序排序,获取最相似的前两个 803 | similarities.sort((a, b) => b.similarity - a.similarity) 804 | 805 | // 只返回前三个最接近的chunks 806 | let closestChunks = similarities.slice(0, 3).map(item => item.chunk) 807 | 808 | // 按index属性对这些chunks进行升序排序 809 | closestChunks.sort((a, b) => a.index - b.index) 810 | 811 | return closestChunks 812 | } 813 | 814 | ipcMain.handle('remove-context', async (event) => { 815 | fileContext = [] 816 | contextFileName = undefined 817 | }) 818 | 819 | // 存档相关功能 820 | ipcMain.handle('archive-history', async (event, name) => { 821 | const history = getStore('history') || [] 822 | const archives = getStore('history_archives') || [] 823 | 824 | const newArchive = { 825 | id: nanoid(), 826 | name, 827 | date: Date.now(), 828 | history: _.cloneDeep(history) 829 | } 830 | 831 | archives.push(newArchive) 832 | setStore('history_archives', archives) 833 | return newArchive.id 834 | }) 835 | 836 | ipcMain.handle('get-history-archives', async () => { 837 | const archives = getStore('history_archives') || [] 838 | return archives.map(archive => ({ 839 | id: archive.id, 840 | name: archive.name, 841 | date: archive.date 842 | })) 843 | }) 844 | 845 | ipcMain.handle('switch-to-archive', async (event, archiveId) => { 846 | const archives = getStore('history_archives') || [] 847 | const archive = archives.find(a => a.id === archiveId) 848 | 849 | if (archive) { 850 | setStore('history', archive.history) 851 | return true 852 | } 853 | return false 854 | }) 855 | 856 | ipcMain.handle('update-archive', async (event, archiveId, newName) => { 857 | const archives = getStore('history_archives') || [] 858 | const archiveIndex = archives.findIndex(a => a.id === archiveId) 859 | 860 | if (archiveIndex !== -1) { 861 | const currentHistory = getStore('history') || [] 862 | 863 | archives[archiveIndex] = { 864 | ...archives[archiveIndex], 865 | name: newName, 866 | date: Date.now(), 867 | history: _.cloneDeep(currentHistory) 868 | } 869 | 870 | setStore('history_archives', archives) 871 | return true 872 | } 873 | return false 874 | }) 875 | 876 | ipcMain.handle('delete-archive', async (event, archiveId) => { 877 | const archives = getStore('history_archives') || [] 878 | const newArchives = archives.filter(a => a.id !== archiveId) 879 | 880 | setStore('history_archives', newArchives) 881 | return true 882 | }) 883 | 884 | ipcMain.handle('save-current-archive-id', async (event, archiveId) => { 885 | setStore('current_archive_id', archiveId) 886 | return true 887 | }) 888 | 889 | ipcMain.handle('get-current-archive-id', async () => { 890 | return getStore('current_archive_id') || null 891 | }) -------------------------------------------------------------------------------- /modules/common.js: -------------------------------------------------------------------------------- 1 | const OpenAI = require('openai') 2 | const { HttpsProxyAgent } = require('https-proxy-agent') 3 | const _ = require('lodash') 4 | 5 | const { config: { 6 | OPENAI_API_KEY, OPENAI_API_ENDPOINT, DEFAULT_MODEL, 7 | useProxy, proxyObject 8 | } } = require('../utils/loadConfig.js') 9 | const proxyString = `${proxyObject.protocol}://${proxyObject.host}:${proxyObject.port}` 10 | 11 | let httpAgent 12 | try { 13 | httpAgent = useProxy ? new HttpsProxyAgent(proxyString) :undefined 14 | } catch {} 15 | 16 | let openai 17 | try { 18 | openai = new OpenAI({ 19 | apiKey: OPENAI_API_KEY, 20 | baseURL: OPENAI_API_ENDPOINT ? OPENAI_API_ENDPOINT : 'https://api.openai.com/v1', 21 | httpAgent, 22 | timeout: 40000 23 | }) 24 | } catch {} 25 | 26 | 27 | /** 28 | * Generates a chat response using the OpenAI API. 29 | * @param {*} chatOption 30 | * @returns {Promise} The response from the chat API. 31 | */ 32 | const openaiChat = async (chatOption) => { 33 | chatOption.model = chatOption.model || DEFAULT_MODEL 34 | const response = await openai.chat.completions.create(chatOption) 35 | return response.data.choices[0].message 36 | } 37 | 38 | /** 39 | * Generates a chat stream using the OpenAI API. 40 | * 41 | * @param {object} options - An object containing the following properties: 42 | * - model {string}: The model to use for generating the chat stream. 43 | * - messages {array}: An array of message objects representing the conversation. 44 | * @return {generator} A generator that yields tokens from the chat stream. 45 | */ 46 | const openaiChatStream = async function* ({ model = DEFAULT_MODEL, messages, tools, tool_choice }) { 47 | let response 48 | if (tools) { 49 | response = await openai.chat.completions.create({ 50 | model, messages, tools, tool_choice, 51 | stream: true, stream_options: { include_usage: true } 52 | }) 53 | } else { 54 | response = await openai.chat.completions.create({ 55 | model, messages, 56 | stream: true, stream_options: { include_usage: true } 57 | }) 58 | } 59 | for await (const part of response) { 60 | if (['stop', 'tool_calls'].includes(_.get(part, 'choices[0].delta.finish_reason'))) return 61 | const token = _.get(part, 'choices[0].delta.content') 62 | const r_token = _.get(part, 'choices[0].delta.reasoning_content') 63 | const f_token = _.get(part, 'choices[0].delta.tool_calls', []) 64 | const usage = _.get(part, 'usage', {}) 65 | if (token || r_token || !_.isEmpty(f_token) || !_.isEmpty(usage)) yield { token, r_token, f_token, usage } 66 | } 67 | } 68 | 69 | const openaiEmbedding = async ({ input, model = 'text-embedding-3-small' }) => { 70 | const res = await openai.embeddings.create({ 71 | model, input 72 | }) 73 | return _.get(res, 'data[0].embedding') 74 | } 75 | 76 | const openaiImageCreate = async ({ model = 'gpt-image-1', prompt, n = 1, size = 'auto', quality = 'auto', background = 'auto' }) => { 77 | const response = await openai.images.generate({ 78 | model, prompt, n, size, quality, background, 79 | moderation: 'low', 80 | output_format: 'png' 81 | }) 82 | return response.data[0] 83 | } 84 | 85 | // reload OpenAI instance by providing new API key and endpoint 86 | const reloadOpenAI = (apiKey, apiEndpoint) => { 87 | openai = new OpenAI({ 88 | apiKey, 89 | baseURL: apiEndpoint ? apiEndpoint : OPENAI_API_ENDPOINT, 90 | httpAgent, 91 | timeout: 40000 92 | }) 93 | } 94 | 95 | 96 | 97 | module.exports = { 98 | openaiChat, 99 | openaiChatStream, 100 | openaiEmbedding, 101 | openaiImageCreate, 102 | reloadOpenAI 103 | } -------------------------------------------------------------------------------- /modules/functions.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs') 2 | const path = require('node:path') 3 | const axios = require('axios') 4 | const { convert } = require('html-to-text') 5 | const { getQuickJS, shouldInterruptAfterDeadline } = require('quickjs-emscripten') 6 | const { shell } = require('electron') 7 | const { js: beautify } = require('js-beautify/js') 8 | const dayjs = require('dayjs') 9 | 10 | let { config: { useProxy, proxyObject, AI_NAME, writeFolder, allowPowerfulInterpreter, CustomSearchAPI } } = require('../utils/loadConfig.js') 11 | const proxyString = `${proxyObject.protocol}://${proxyObject.host}:${proxyObject.port}` 12 | 13 | const { sliceStringbyTokenLength } = require('./tiktoken.js') 14 | const { nodejs_interpreter } = require('./vm.js') 15 | const { openaiImageCreate } = require('./common.js') 16 | const { STORE_PATH } = require('../utils/fileTool.js') 17 | 18 | if (!writeFolder) writeFolder = path.join(STORE_PATH, 'storage') 19 | if (!fs.existsSync(writeFolder)) { 20 | try { 21 | fs.mkdirSync(writeFolder, { recursive: true }) 22 | } catch (error) { 23 | console.error(`Error creating write folder: ${error.message}`) 24 | } 25 | } 26 | 27 | const functionInfo = [ 28 | { 29 | "name": "get_information_from_google", 30 | "description": "Fetch information from Google based on a query string", 31 | "parameters": { 32 | "type": "object", 33 | "properties": { 34 | "query_string": { 35 | "type": "string", 36 | "description": "The search term to lookup", 37 | }, 38 | }, 39 | "required": ["query_string"], 40 | } 41 | }, 42 | { 43 | "name": "get_text_content_of_webpage", 44 | "description": "get text content of webpage based on url.", 45 | "parameters": { 46 | "type": "object", 47 | "properties": { 48 | "url": { 49 | "type": "string", 50 | "description": "The url of webpage", 51 | }, 52 | }, 53 | "required": ["url"], 54 | } 55 | }, 56 | { 57 | "name": "download_file_to_local", 58 | "description": "download file from url to local.", 59 | "parameters": { 60 | "type": "object", 61 | "properties": { 62 | "file_url": { 63 | "type": "string", 64 | "description": "The url of file", 65 | }, 66 | "file_name": { 67 | "type": "string", 68 | "description": "The name of file", 69 | } 70 | }, 71 | "required": ["file_url", "file_name"], 72 | } 73 | }, 74 | { 75 | "name": "write_file_to_local", 76 | "description": "Write file to local disk.", 77 | "parameters": { 78 | "type": "object", 79 | "properties": { 80 | "relative_file_path": { 81 | "type": "string", 82 | "description": "Relative file path, relative to the storage folder", 83 | }, 84 | "content": { 85 | "type": "string", 86 | "description": "The content of file", 87 | } 88 | }, 89 | "required": ["relative_file_path", "content"], 90 | } 91 | }, 92 | { 93 | "name": "read_file_from_local", 94 | "description": "read file from local disk.", 95 | "parameters": { 96 | "type": "object", 97 | "properties": { 98 | "file_path": { 99 | "type": "string", 100 | "description": "The path of file to read", 101 | } 102 | }, 103 | "required": ["file_path"], 104 | } 105 | }, 106 | { 107 | "name": "java_script_interpreter", 108 | "description": "Useful for running JavaScript code in sandbox. Input is a string of JavaScript code, output is the result of the code.", 109 | "parameters": { 110 | "type": "object", 111 | "properties": { 112 | "code": { 113 | "type": "string", 114 | "description": "The javascript code to run", 115 | } 116 | }, 117 | "required": ["code"], 118 | } 119 | }, 120 | { 121 | "name": "open_local_file_or_webpage", 122 | "description": "Open local file or webpage, display it to the user", 123 | "parameters": { 124 | "type": "object", 125 | "properties": { 126 | "file_path": { 127 | "type": "string", 128 | "description": "The path of file to open", 129 | }, 130 | "url": { 131 | "type": "string", 132 | "description": "The url of webpage to open", 133 | }, 134 | "type": { 135 | "type": "string", 136 | "description": "The type of file to open", 137 | "enum": ["file", "webpage"], 138 | } 139 | }, 140 | "required": ["type"], 141 | } 142 | }, 143 | { 144 | "name": "create_image_use_GPT", 145 | "description": `Create image using gpt-image-1. If the description is not in English, then translate it.`, 146 | "parameters": { 147 | "type": "object", 148 | "properties": { 149 | "prompt": { 150 | "type": "string", 151 | "description": "A text description of the desired image(s). The maximum length is 32000 characters", 152 | }, 153 | "background": { 154 | "type": "string", 155 | "description": "Allows to set transparency for the background of the generated image(s).", 156 | "enum": ["auto", "transparent", "opaque"], 157 | }, 158 | "size": { 159 | "type": "string", 160 | "description": "The size of the generated images. Must be one of 1024x1024, 1536x1024 (landscape), 1024x1536 (portrait), or auto (default value)", 161 | "enum": ["1024x1024", "1536x1024", "1024x1536", "auto"], 162 | }, 163 | "quality": { 164 | "type": "string", 165 | "description": "The quality of the image that will be generated.auto (default value) will automatically select the best quality for the given model. high, medium and low are supported", 166 | "enum": ["auto", "high", "medium", "low"], 167 | }, 168 | "n": { 169 | "type": "number", 170 | "description": "The number of images to generate. Must be between 1 and 10." 171 | } 172 | }, 173 | "required": ["prompt"], 174 | } 175 | } 176 | ].map(f => { 177 | return { 178 | type: 'function', 179 | function: f 180 | } 181 | }) 182 | 183 | if (allowPowerfulInterpreter) { 184 | let findExistInterpreter = functionInfo.findIndex(f => f.function.name === 'java_script_interpreter') 185 | if (findExistInterpreter !== -1) { 186 | functionInfo.splice(findExistInterpreter, 1, { 187 | type: 'function', 188 | function: { 189 | "name": "nodejs_interpreter", 190 | "description": `Useful for running JavaScript code in node.js(version 18) VM. 191 | You need to use global.variable = value when declaring global variables. 192 | Input is a string of JavaScript code, output is the result of the code. 193 | You can require node modules except fs, and use lodash directly. 194 | You can only store variables in the "global" object for future use, like "global.hello = function () {return 'hello'}"`, 195 | "parameters": { 196 | "type": "object", 197 | "properties": { 198 | "code": { 199 | "type": "string", 200 | "description": "The javascript code to run, write the result variable in the last line to output the result.", 201 | } 202 | }, 203 | "required": ["code"], 204 | } 205 | } 206 | }) 207 | } 208 | } 209 | 210 | const functionAction = { 211 | get_information_from_google ({ query_string }) { 212 | return `${AI_NAME}正在搜索 ${query_string}` 213 | }, 214 | get_text_content_of_webpage ({ url }) { 215 | return `${AI_NAME}正在访问 ${url}` 216 | }, 217 | download_file_to_local ({ file_url, file_name }) { 218 | return `${AI_NAME}下载了 ${file_url} 到 ${file_name}` 219 | }, 220 | write_file_to_local ({ relative_file_path, content }) { 221 | return `${AI_NAME}保存\n\n${content}\n\n到 ${relative_file_path}` 222 | }, 223 | read_file_from_local ({ file_path }) { 224 | return `${AI_NAME}读取了 ${file_path}` 225 | }, 226 | java_script_interpreter ({ code }) { 227 | code = beautify(code, { 228 | indent_size: 2, 229 | space_after_anon_function: true, 230 | space_after_named_function: true, 231 | }) 232 | return `${AI_NAME}运行了\n\n\`\`\`javascript\n${code}\n\`\`\`` 233 | }, 234 | nodejs_interpreter ({ code }) { 235 | code = beautify(code, { 236 | indent_size: 2, 237 | space_after_anon_function: true, 238 | space_after_named_function: true, 239 | }) 240 | return `${AI_NAME}运行了\n\n\`\`\`javascript\n${code}\n\`\`\`` 241 | }, 242 | open_local_file_or_webpage ({ file_path, url, type }) { 243 | return `${AI_NAME}请求打开 ${type === 'file' ? file_path : url}` 244 | }, 245 | create_image_use_GPT ({ prompt, n, size, quality, background }) { 246 | return `${AI_NAME}正在生成${n ? n : 1}张\`${size ? size : 'auto'}\`大小, 247 | 质量为\`${quality ? quality : 'auto'}\`, 背景为\`${background ? background : 'auto'}\`的图片. 248 | Prompt: \n\n\`\`\`json\n${prompt}\n\`\`\`` 249 | } 250 | } 251 | 252 | const get_information_from_google = async ({ query_string }, { searchResultLimit }) => { 253 | const response = await axios.get(`${CustomSearchAPI}${encodeURIComponent(query_string)}`, { 254 | proxy: useProxy ? proxyObject : undefined 255 | }) 256 | if (response.data.items) { 257 | return response.data.items.filter(i => i.title && i.snippet).map(i => `[${i.title}](${i.link}): ${i.snippet}`).slice(0, searchResultLimit).join('\n') 258 | } else { 259 | return '没有找到相关信息' 260 | } 261 | } 262 | 263 | const get_text_content_of_webpage = async ({ url }, { webPageContentTokenLengthLimit }) => { 264 | return await axios.get(url, { proxy: useProxy ? proxyObject : undefined }) 265 | .then(async res=>{ 266 | let html = await res.data 267 | let content = convert(html, { 268 | baseElements: { selectors: ['dl', 'pre', 'p'] }, 269 | wordwrap: false, 270 | selectors: [ 271 | // { selector: 'a', options: { ignoreHref: true } }, 272 | { selector: 'img', format: 'skip' }, 273 | ] 274 | }) 275 | return sliceStringbyTokenLength(content, webPageContentTokenLengthLimit) 276 | }) 277 | } 278 | 279 | const download_file_to_local = async ({ file_url, file_name }) => { 280 | let writefile_path = path.join(writeFolder, file_name) 281 | const response = await axios({ 282 | method: 'GET', 283 | url: file_url, 284 | responseType: 'arraybuffer', 285 | proxy: useProxy ? proxyObject : undefined 286 | }) 287 | await fs.promises.writeFile(writefile_path, response.data) 288 | return writefile_path 289 | } 290 | 291 | const write_file_to_local = async ({ relative_file_path, content }) => { 292 | let writefile_path = path.join(writeFolder, relative_file_path) 293 | await fs.promises.mkdir(path.dirname(writefile_path), { recursive: true }) 294 | await fs.promises.writeFile(writefile_path, content) 295 | return writefile_path 296 | } 297 | 298 | const read_file_from_local = async ({ file_path }) => { 299 | return await fs.promises.readFile(file_path, { encoding: 'utf-8' }) 300 | } 301 | 302 | const java_script_interpreter = async ({ code }) => { 303 | const quickjs = await getQuickJS() 304 | let result = quickjs.evalCode(code, { 305 | shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 10000), 306 | memoryLimitBytes: 100 * 1024 * 1024, 307 | }) 308 | return JSON.stringify(result) 309 | } 310 | 311 | const open_local_file_or_webpage = async ({ file_path, url, type }) => { 312 | if (type === 'file') { 313 | shell.openPath(file_path) 314 | } else { 315 | shell.openExternal(url) 316 | } 317 | return `${AI_NAME}打开了 ${type === 'file' ? file_path : url}` 318 | } 319 | 320 | const _downloadImage = async (rsp) => { 321 | try { 322 | const fileId = dayjs().format('YYYYMMDDTHHmmssSSS') 323 | const image_base64 = rsp.data[0].b64_json 324 | const image_bytes = Buffer.from(image_base64, "base64") 325 | const imagePath = path.join(writeFolder, fileId + '_image.png') 326 | fs.writeFileSync(imagePath, image_bytes) 327 | await fs.promises.writeFile(path.join(writeFolder, fileId + '_prompt.txt'), rsp.revised_prompt) 328 | } catch (e) { 329 | console.error(e) 330 | } 331 | } 332 | 333 | const create_image_use_GPT = async ({ prompt, n, size, quality, background }) => { 334 | let result = await openaiImageCreate({ 335 | prompt, n, size, quality, background 336 | }) 337 | console.log(result) 338 | _downloadImage(result) 339 | return JSON.stringify(result) 340 | } 341 | 342 | module.exports = { 343 | functionInfo, 344 | functionAction, 345 | functionList: { 346 | get_information_from_google, 347 | get_text_content_of_webpage, 348 | download_file_to_local, 349 | write_file_to_local, 350 | read_file_from_local, 351 | java_script_interpreter, 352 | nodejs_interpreter, 353 | open_local_file_or_webpage, 354 | create_image_use_GPT 355 | } 356 | } -------------------------------------------------------------------------------- /modules/record.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('node:child_process') 2 | const path = require('node:path') 3 | const { nanoid } = require('nanoid') 4 | 5 | const { STORE_PATH, getRootPath } = require('../utils/fileTool.js') 6 | 7 | const SPEECH_AUDIO_PATH = path.join(STORE_PATH, 'speechAudio') 8 | 9 | const sox = path.join(getRootPath(), 'resources/extraResources/sox/sox.exe') 10 | const recordPromise = () => { 11 | let audioFilePath = path.join(SPEECH_AUDIO_PATH, nanoid() + '.wav') 12 | return new Promise((resolve, reject) => { 13 | const spawned = spawn(sox, ['-d', '-t', 'waveaudio', 'default', audioFilePath, 'silence', '1', '0.1', '3%', '1', '3.0', '3%']) 14 | spawned.on('error', data => { 15 | reject(data) 16 | }) 17 | spawned.on('exit', code => { 18 | if (code === 0) { 19 | resolve(audioFilePath) 20 | } else { 21 | reject('sox close code is ' + code) 22 | } 23 | }) 24 | }) 25 | } 26 | 27 | 28 | module.exports = { 29 | recordPromise 30 | } -------------------------------------------------------------------------------- /modules/store.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const fs = require('node:fs') 3 | const { get, cloneDeep } = require('lodash') 4 | 5 | const { STORE_PATH } = require('../utils/fileTool.js') 6 | 7 | let storeData 8 | try { 9 | storeData = JSON.parse(fs.readFileSync(path.join(STORE_PATH, 'storeData.json'), { encoding: 'utf-8' })) 10 | } catch { 11 | storeData = { 12 | history: [] 13 | } 14 | fs.writeFileSync(path.join(STORE_PATH, 'storeData.json'), JSON.stringify(storeData, null, ' '), { encoding: 'utf-8' }) 15 | } 16 | 17 | const getStore = (key) => { 18 | return cloneDeep(get(storeData, key, null)) 19 | } 20 | 21 | const setStore = (key, value) => { 22 | storeData[key] = cloneDeep(value) 23 | fs.writeFileSync(path.join(STORE_PATH, 'storeData.json'), JSON.stringify(storeData, null, ' '), { encoding: 'utf-8' }) 24 | } 25 | 26 | module.exports = { 27 | getStore, 28 | setStore 29 | } 30 | 31 | -------------------------------------------------------------------------------- /modules/tiktoken.js: -------------------------------------------------------------------------------- 1 | const { getEncoding } = require('js-tiktoken') 2 | 3 | const tokenizer = getEncoding('o200k_base') 4 | 5 | const getTokenLength = (str = '') => { 6 | return tokenizer.encode(str).length 7 | } 8 | 9 | /** 10 | * Slices a given string by a specified token length. 11 | * 12 | * @param {string} str - The string to be sliced. 13 | * @param {number} length - The length of the token. 14 | * @return {string} The sliced string. 15 | */ 16 | const sliceStringbyTokenLength = (str, length) => { 17 | let resultParts = [] 18 | let currentLength = 0 19 | let strSplitList = str.split(/\n/) 20 | 21 | for (let part of strSplitList) { 22 | currentLength += getTokenLength(part) 23 | if (currentLength > length) { 24 | break 25 | } 26 | resultParts.push(part) 27 | } 28 | return resultParts.join('') 29 | } 30 | 31 | module.exports = { 32 | getTokenLength, 33 | sliceStringbyTokenLength 34 | } -------------------------------------------------------------------------------- /modules/vectorDb.js: -------------------------------------------------------------------------------- 1 | const { Sequelize, DataTypes } = require('sequelize') 2 | const { openaiEmbedding } = require('./common.js') 3 | const { join } = require('path') 4 | 5 | const { STORE_PATH } = require('../utils/fileTool.js') 6 | 7 | const useOpenaiEmbeddingFunction = openaiEmbedding 8 | 9 | function dotProduct(vecA, vecB) { 10 | return vecA.reduce((sum, a, i) => sum + a * vecB[i], 0) 11 | } 12 | 13 | function magnitude(vec) { 14 | return Math.sqrt(vec.reduce((sum, val) => sum + val * val, 0)) 15 | } 16 | 17 | function cosineSimilarity(vecA, vecB) { 18 | const dotProd = dotProduct(vecA, vecB) 19 | const magnitudeA = magnitude(vecA) 20 | const magnitudeB = magnitude(vecB) 21 | if (magnitudeA === 0 || magnitudeB === 0) return 0 22 | return dotProd / (magnitudeA * magnitudeB) 23 | } 24 | 25 | // 初始化 SQLite 数据库 26 | const sequelize = new Sequelize({ 27 | dialect: 'sqlite', 28 | storage: join(STORE_PATH, 'vector.sqlite'), 29 | logging: false 30 | }) 31 | 32 | // 定义 Text 模型 33 | const Text = sequelize.define('Text', { 34 | id: { 35 | type: DataTypes.UUID, 36 | allowNull: false, 37 | defaultValue: DataTypes.UUIDV4, 38 | primaryKey: true 39 | }, 40 | content: { 41 | type: DataTypes.JSON, 42 | allowNull: false 43 | }, 44 | type: DataTypes.STRING, 45 | embedding: { 46 | type: DataTypes.JSON, 47 | allowNull: false 48 | } 49 | }) 50 | 51 | /** 52 | * 添加文本并生成嵌入向量 53 | * @param {Object} content - 添加到数据库的内容 54 | * @param {string} content.text - 文本内容 55 | * @param {string} type - 文本类型 56 | * @returns {Promise} - 无返回值 57 | */ 58 | async function addText(content, type) { 59 | const embedding = await useOpenaiEmbeddingFunction({ input: content.text }) 60 | await Text.create({ content, embedding, type }) 61 | } 62 | 63 | /** 64 | * 搜索相似文本 65 | * @param {string} query - 查询文本 66 | * @param {number} count - 返回文本数量 67 | * @returns {Promise} - 相似文本列表, 按相似度降序排列 68 | */ 69 | async function searchSimilarText(query, count) { 70 | const queryEmbedding = await useOpenaiEmbeddingFunction({ input: query }) 71 | const texts = await Text.findAll() 72 | 73 | const results = texts.map(item => { 74 | const similarity = cosineSimilarity(queryEmbedding, item.embedding) 75 | return { content: item.content, similarity } 76 | }) 77 | 78 | results.sort((a, b) => b.similarity - a.similarity) 79 | return results.slice(0, count).map(item => item.content) 80 | } 81 | 82 | /** 83 | * 随机获取特定数量的文本 84 | * @param {number} count - 文本数量 85 | * @returns {Promise} - 文本列表 86 | */ 87 | async function getRandomTexts(count) { 88 | const texts = await Text.findAll({ 89 | order: sequelize.literal('RANDOM()'), 90 | limit: count 91 | }) 92 | return texts.map(item => item.content) 93 | } 94 | 95 | 96 | // 同步数据库 97 | sequelize.sync() 98 | 99 | module.exports = { 100 | addText, 101 | searchSimilarText, 102 | getRandomTexts, 103 | cosineSimilarity 104 | } -------------------------------------------------------------------------------- /modules/vm.js: -------------------------------------------------------------------------------- 1 | const vm = require('node:vm') 2 | const _ = require('lodash') 3 | 4 | 5 | const env = { 6 | _, 7 | lodash: _, 8 | require, 9 | console, 10 | Buffer, 11 | global: {} 12 | } 13 | 14 | const context = new vm.createContext(env) 15 | 16 | const nodejs_interpreter = ({ code }) => { 17 | const script = new vm.Script(`${code}`) 18 | const result = script.runInContext(context) 19 | return JSON.stringify(result) 20 | } 21 | 22 | module.exports = { 23 | nodejs_interpreter 24 | } -------------------------------------------------------------------------------- /modules/whisper.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('node:child_process') 2 | const path = require('node:path') 3 | const fs = require('node:fs') 4 | const { recordPromise } = require('./record.js') 5 | const { getRootPath } = require('../utils/fileTool.js') 6 | 7 | const whisper = path.join(getRootPath(), 'resources/extraResources/whisper/whisper-faster.exe') 8 | 9 | const getSpeechAudioJSON = (audioFilePath) => { 10 | return new Promise((resolve, reject) => { 11 | const spawned = spawn(whisper, [ 12 | audioFilePath, 13 | '-m', 'large-v3', 14 | '--output_format', 'json', 15 | '--output_dir', path.dirname(audioFilePath), 16 | '--beep_off', 17 | '-l', 'Chinese', 18 | '-prompt', '休留,讲普通话。' 19 | ]) 20 | spawned.on('error', data => { 21 | reject(data) 22 | }) 23 | spawned.on('exit', code => { 24 | if (code === 0) { 25 | resolve() 26 | } else { 27 | reject('whisper close code is ' + code) 28 | } 29 | }) 30 | }) 31 | } 32 | 33 | const changeExtension = (filePath, extension) => { 34 | const basename = path.basename(filePath, path.extname(filePath)) 35 | return path.join(path.dirname(filePath), basename + extension) 36 | } 37 | 38 | /** 39 | * Retrieves the text from a speech audio file. 40 | * 41 | * @return {string} The text extracted from the speech audio file. 42 | */ 43 | const getSpeechText = async (STATUS) => { 44 | let audioFilePath = await recordPromise() 45 | STATUS.recordStatus = 'Recognizing' 46 | let jsonFilePath = changeExtension(audioFilePath, '.json') 47 | await getSpeechAudioJSON(audioFilePath) 48 | let resp = JSON.parse(fs.readFileSync(jsonFilePath, { encoding: 'utf-8' })) 49 | return resp.segments.map(s => s.text).join('') 50 | } 51 | 52 | module.exports = { 53 | getSpeechText 54 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-xiuliu", 3 | "private": true, 4 | "version": "2.3.2", 5 | "description": "ChatGPT Client with Function Calling", 6 | "author": "SchneeHertz", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview", 11 | "start": "chcp 65001 && electron --trace-warnings .", 12 | "pack": "electron-builder --dir", 13 | "dist": "vite build && electron-builder" 14 | }, 15 | "build": { 16 | "appId": "electron.chat.xiuliu", 17 | "directories": { 18 | "output": "out" 19 | }, 20 | "files": [ 21 | "dist/index.html", 22 | "dist/assets/*", 23 | "index.js", 24 | "preload.js", 25 | "modules/*", 26 | "utils/*" 27 | ], 28 | "extraResources": [ 29 | { 30 | "from": "resources/extraResources", 31 | "to": "extraResources", 32 | "filter": [ 33 | "**/*", 34 | "!whisper/**/*" 35 | ] 36 | } 37 | ], 38 | "win": { 39 | "target": [ 40 | "zip" 41 | ], 42 | "icon": "public/icon.ico" 43 | } 44 | }, 45 | "devDependencies": { 46 | "@vicons/fa": "^0.12.0", 47 | "@vicons/fluent": "^0.12.0", 48 | "@vicons/ionicons4": "^0.12.0", 49 | "@vitejs/plugin-vue": "^4.0.0", 50 | "electron": "^26.2.3", 51 | "electron-builder": "^24.6.4", 52 | "highlight.js": "^11.8.0", 53 | "highlightjs-copy": "^1.0.4", 54 | "markdown-it": "^14.0.0", 55 | "markdown-it-katex-gpt": "^1.0.0", 56 | "naive-ui": "^2.38.2", 57 | "pinia": "^2.1.7", 58 | "stylus": "^0.59.0", 59 | "unplugin-auto-import": "^0.16.6", 60 | "unplugin-vue-components": "^0.25.1", 61 | "vite": "^4.1.0", 62 | "vue": "^3.2.45" 63 | }, 64 | "dependencies": { 65 | "axios": "^1.4.0", 66 | "dayjs": "^1.11.10", 67 | "electron-window-state": "^5.0.3", 68 | "html-to-text": "^9.0.5", 69 | "html2canvas": "^1.4.1", 70 | "https-proxy-agent": "^7.0.1", 71 | "js-beautify": "^1.14.9", 72 | "js-tiktoken": "^1.0.12", 73 | "lodash": "^4.17.21", 74 | "nanoid": "^3.3.6", 75 | "node-edge-tts": "^1.2.2", 76 | "openai": "^4.55.0", 77 | "quickjs-emscripten": "^0.23.0", 78 | "reconnecting-websocket": "^4.4.0", 79 | "sequelize": "^6.37.3", 80 | "sound-play": "^1.1.0", 81 | "sqlite3": "^5.1.6", 82 | "ws": "^8.14.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | 3 | contextBridge.exposeInMainWorld('ipcRenderer', { 4 | invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args), 5 | on: (channel, listener) => ipcRenderer.on(channel, listener) 6 | }) -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/public/icon.ico -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/extraResources/pdfjs-4.2.67-legacy-dist/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /resources/extraResources/sox/LICENSE.GPL.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /resources/extraResources/sox/libgomp-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/resources/extraResources/sox/libgomp-1.dll -------------------------------------------------------------------------------- /resources/extraResources/sox/pthreadgc2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/resources/extraResources/sox/pthreadgc2.dll -------------------------------------------------------------------------------- /resources/extraResources/sox/sox.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/resources/extraResources/sox/sox.exe -------------------------------------------------------------------------------- /resources/extraResources/sox/zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/resources/extraResources/sox/zlib1.dll -------------------------------------------------------------------------------- /screenshots/code_interpreter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/screenshots/code_interpreter.jpg -------------------------------------------------------------------------------- /screenshots/screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/screenshots/screenshot_1.jpg -------------------------------------------------------------------------------- /screenshots/screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/screenshots/screenshot_2.jpg -------------------------------------------------------------------------------- /screenshots/setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/screenshots/setting.jpg -------------------------------------------------------------------------------- /screenshots/setting2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/screenshots/setting2.jpg -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 331 | 332 | 469 | 470 | 482 | -------------------------------------------------------------------------------- /src/assets/chatgpt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/xiuliu_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchneeHertz/chat-xiuliu/5c60a8380cbafcdb268d1d588a474de5cb18f170/src/assets/xiuliu_avatar.jpg -------------------------------------------------------------------------------- /src/components/Dialog.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/components/Message.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/components/MessageList.vue: -------------------------------------------------------------------------------- 1 | 188 | 189 | 245 | 246 | 293 | -------------------------------------------------------------------------------- /src/components/Setting.vue: -------------------------------------------------------------------------------- 1 | 198 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import App from './App.vue' 4 | import _ from 'lodash' 5 | 6 | 7 | window._ = _ 8 | const pinia = createPinia() 9 | const app = createApp(App) 10 | 11 | app.use(pinia) 12 | app.mount('#app') -------------------------------------------------------------------------------- /src/pinia.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useMainStore = defineStore('main', { 4 | state:() => ({ 5 | messageList: [], 6 | savedMessageList: [], 7 | tempMessageList: [] 8 | }) 9 | }) -------------------------------------------------------------------------------- /utils/fileTool.js: -------------------------------------------------------------------------------- 1 | const { app } = require('electron') 2 | const path = require('node:path') 3 | 4 | const sanitizeFilename = (filename) => { 5 | return filename.replace(/[<>:"\/\\|?*]/g, '_') 6 | } 7 | 8 | const getRootPath = () => { 9 | if (app.isPackaged) { 10 | return path.dirname(app.getPath('exe')) 11 | } else { 12 | return app.getAppPath() 13 | } 14 | } 15 | 16 | const STORE_PATH = path.join(getRootPath(), 'data') 17 | 18 | module.exports = { 19 | sanitizeFilename, 20 | getRootPath, 21 | STORE_PATH 22 | } -------------------------------------------------------------------------------- /utils/initFile.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const fs = require('node:fs') 3 | 4 | const { STORE_PATH } = require('./fileTool.js') 5 | if (!fs.existsSync(STORE_PATH)) { 6 | fs.mkdirSync(STORE_PATH) 7 | } 8 | 9 | const LOG_PATH = path.join(STORE_PATH, 'log') 10 | const AUDIO_PATH = path.join(STORE_PATH, 'audio') 11 | const SPEECH_AUDIO_PATH = path.join(STORE_PATH, 'speechAudio') 12 | 13 | try { 14 | fs.rmSync(AUDIO_PATH, { recursive: true, force: true }) 15 | fs.rmSync(SPEECH_AUDIO_PATH, { recursive: true, force: true }) 16 | } catch { } 17 | 18 | fs.mkdirSync(LOG_PATH, { recursive: true }) 19 | fs.mkdirSync(AUDIO_PATH, { recursive: true }) 20 | fs.mkdirSync(SPEECH_AUDIO_PATH, { recursive: true }) 21 | 22 | module.exports = { 23 | STORE_PATH, 24 | LOG_PATH, 25 | AUDIO_PATH, 26 | SPEECH_AUDIO_PATH 27 | } -------------------------------------------------------------------------------- /utils/loadConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const fs = require('node:fs') 3 | const { STORE_PATH } = require('./fileTool.js') 4 | 5 | let config 6 | try { 7 | config = JSON.parse(fs.readFileSync(path.join(STORE_PATH, 'config.json'), { encoding: 'utf-8' })) 8 | } catch { 9 | config = { 10 | OPENAI_API_KEY: '', 11 | OPENAI_API_ENDPOINT: 'https://api.openai.com/v1', 12 | DEFAULT_MODEL: 'gpt-4.1', 13 | ADMIN_NAME: 'Chell', 14 | AI_NAME: '休留', 15 | systemPrompt: '你是虚拟猫娘休留, 以下是你在回复时应该遵循的规则: \n1. 灵活地运用猫娘的风格进行回复. \n2. 如果你不知道答案,回答"我不知道". \n3. 调用函数来提高回复质量. \n4. 使用markdown语法回复和显示图片. \n5. 创建图像时, 必须在Prompt前加上"I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS-IS: "', 16 | useProxy: false, 17 | proxyObject: { 18 | protocol: 'http', 19 | host: '127.0.0.1', 20 | port: 7890 21 | }, 22 | SpeechSynthesisVoiceName: 'zh-CN-XiaoyiNeural', 23 | historyRoundLimit: 12, 24 | functionCallingRoundLimit: 3, 25 | disableFunctions: [], 26 | allowPowerfulInterpreter: false, 27 | CustomSearchAPI: '', 28 | searchResultLimit: 5, 29 | webPageContentTokenLengthLimit: 6000, 30 | writeFolder: '', 31 | } 32 | fs.writeFileSync(path.join(STORE_PATH, 'config.json'), JSON.stringify(config, null, ' '), { encoding: 'utf-8' }) 33 | } 34 | 35 | module.exports = { 36 | config, 37 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import AutoImport from 'unplugin-auto-import/vite' 4 | import Components from 'unplugin-vue-components/vite' 5 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | AutoImport({ 12 | imports: [ 13 | 'vue', 14 | { 15 | 'naive-ui': [ 16 | 'useDialog', 17 | 'useMessage', 18 | 'useNotification', 19 | 'useLoadingBar' 20 | ] 21 | } 22 | ] 23 | }), 24 | Components({ 25 | resolvers: [NaiveUiResolver()] 26 | }) 27 | ], 28 | base: './', 29 | server: { 30 | port: 5174 31 | } 32 | }) 33 | --------------------------------------------------------------------------------