├── .github └── FUNDING.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── docs ├── FAQ.md └── electron-packaging-guide.md ├── electron ├── .npmrc ├── electron-builder.yml ├── main.js ├── package.json └── preload.js ├── env.d.ts ├── img ├── appreciation-qr.png └── preview.gif ├── index.html ├── package.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── assets │ └── tailwind.css ├── components │ ├── Copy.vue │ └── Loding.vue ├── libs │ ├── gpt.ts │ └── markdown.ts ├── main.ts ├── router │ └── index.ts ├── types │ ├── gpt.ts │ └── index.ts └── views │ └── home.vue ├── tailwind.config.js ├── tsconfig.config.json ├── tsconfig.json └── vite.config.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['https://github.com/lianginx/sponsor'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | node_modules 4 | package-lock.json 5 | dist 6 | electron-dist 7 | yarn.lock 8 | yarn-error.log 9 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | electron_mirror=https://npmmirror.com/mirrors/electron/ 3 | chromedriver_cdnurl=https://npmmirror.com/mirrors/chromedriver 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Liang INX 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 | # chatgpt-vue 2 | 3 | 使用 Vue3 + Typescript + Tailwind CSS 框架,调用 OpenAI 的 `gpt-3.5-turbo` 模型 API 实现的简单聊天对话,支持连续对话。 4 | 5 | ![preview](img/preview.gif) 6 | 7 | ## 快速开始 8 | 9 | >注意:本项目没有使用任何代理,API 在前端发送请求,能否连通基于你当前浏览器的所处的网络环境。如果你需要在服务端发送 API 请求的 ChatGPT,可以查看我的另一个项目 [chatgpt-nuxt](https://github.com/lianginx/chatgpt-nuxt),点击这里 [在线体验](http://ai.in-x.cc/)。 10 | 11 | 在开始之前,请确保您已正确安装 Node.js 运行时环境。如果您还没有安装 Node.js,请 [点击这里下载](https://nodejs.org/zh-cn/)。 12 | 13 | 使用 ChatGPT 需要先申请 API Key,已注册但还没有 API Key 的用户可以 [前往这里生成](https://platform.openai.com/account/api-keys)。 14 | 15 | 准备就绪后,进入项目根目录执行以下命令运行项目: 16 | 17 | ```bash 18 | npm i 19 | npm run dev 20 | ``` 21 | 22 | 或者 23 | 24 | ```bash 25 | yarn 26 | yarn dev 27 | ``` 28 | 29 | 运行成功时通常显示(请以具体为准): 30 | 31 | ```bash 32 | VITE v3.2.5 ready in 294 ms 33 | 34 | ➜ Local: http://localhost:5173/ 35 | ➜ Network: use --host to expose 36 | ``` 37 | 38 | 按住 `Ctrl` 或 `command` 点击 Local 链接,在浏览器中打开项目,然后在页面底部输入框中填入您的 API Key,然后点击保存,即可开始使用! 39 | 40 | 如果想要更改 API Key,点击页面右上角 `设置`,重新输入并保存即可。 41 | 42 | 如果你想要打包在本地运行,[查看这里](/docs/electron-packaging-guide.md)。 43 | 44 | 希望这能对您有所帮助。如果您还有其他问题,请随时在评论区提出,谢谢! 45 | 46 | ## 许可证 47 | 48 | 本项目使用 [MIT](LICENSE) 协议 49 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答 FAQ 2 | 3 | 以下收集了测试项目过程中遇到的问题,希望对你有所帮助: 4 | 5 | ## 此系统上禁止运行脚本 6 | 7 | 使用 Windows 终端工具 PowerShell 时,可能会遇到: 8 | 9 | ```bash 10 | 无法加载文件 C:\Users\DH\Desktop\cs\rename.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。 11 | 12 | + CategoryInfo : SecurityError: (:) [],ParentContainsErrorRecordException 13 | + FullyQualifiedErrorId : UnauthorizedAccess 14 | ``` 15 | 16 | 这是因为 PowerShell 当前的执行策略是 Restricted(默认设置),禁止运行任何脚本文件。 17 | 18 | **解决方案:** 19 | 20 | 使用管理员身份重新打开 PowerShell,执行以下命令修改策略即可: 21 | 22 | ```bash 23 | set-executionpolicy remotesigned 24 | ``` 25 | 26 | ## cp 不是内部或外部命令 27 | 28 | 在 Windows 中进行打包时可能会遇到命令行报错提示 `cp 不是内部或外部命令` 的问题而中断打包动作,这是因为 Windows 中复制文件的命令是 `copy`,与 Linux 中的 `cp` 不同。 29 | 30 | **解决方案:** 31 | 32 | 使用安装 Git 时附带的 `Git Bash` 替代当前正在使用的终端工具,如果你还没有安装 Git 工具,请 [点击这里下载](https://git-scm.com/downloads) 安装。 33 | 34 | --- 35 | 36 | 以上是目前已知可能遇到的问题,如果你还碰到了其他问题,请到 Issue 中提问。 37 | -------------------------------------------------------------------------------- /docs/electron-packaging-guide.md: -------------------------------------------------------------------------------- 1 | # 项目打包指南 2 | 3 | 使用 Electron + Electron-builder 进行打包,以下是项目打包指南: 4 | 5 | ## 快速开始 6 | 7 | 进入项目根目录开始调试/打包: 8 | 9 | ```bash 10 | yarn e:dev # 开发调试 11 | yarn e:build # 打包 12 | ``` 13 | 14 | 或者 15 | 16 | ```bash 17 | npm run ne:dev # 开发调试 18 | npm run ne:build # 打包 19 | ``` 20 | 21 | Electron-builer 会将项目打包成 dmg、exe 等可执行文件,输出到 `/electron-dist`。 22 | 23 | 如果你打包时遇到一些问题,请到 [常见问题解答 FAQ](FAQ.md) 查看是否已有解决方案,没有找到你的问题请到 Issue 提交。 24 | -------------------------------------------------------------------------------- /electron/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | electron_mirror=https://npmmirror.com/mirrors/electron/ 3 | chromedriver_cdnurl=https://npmmirror.com/mirrors/chromedriver 4 | -------------------------------------------------------------------------------- /electron/electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: com.chatgpt.app 2 | productName: ChatGPT 3 | directories: 4 | output: ../electron-dist 5 | mac: 6 | category: public.app-category.productivity 7 | target: 8 | - target: dmg 9 | arch: 10 | - arm64 11 | - x64 12 | win: 13 | target: 14 | - target: nsis 15 | arch: 16 | - x64 17 | - ia32 18 | -------------------------------------------------------------------------------- /electron/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const {app, BrowserWindow} = require('electron') 3 | const path = require('path') 4 | 5 | function createWindow () { 6 | // Create the browser window. 7 | const mainWindow = new BrowserWindow({ 8 | width: 800, 9 | height: 600, 10 | webPreferences: { 11 | preload: path.join(__dirname, 'preload.js') 12 | } 13 | }) 14 | 15 | // and load the index.html of the app. 16 | mainWindow.loadFile('index.html') 17 | 18 | // Open the DevTools. 19 | // mainWindow.webContents.openDevTools() 20 | } 21 | 22 | // This method will be called when Electron has finished 23 | // initialization and is ready to create browser windows. 24 | // Some APIs can only be used after this event occurs. 25 | app.whenReady().then(() => { 26 | createWindow() 27 | 28 | app.on('activate', function () { 29 | // On macOS it's common to re-create a window in the app when the 30 | // dock icon is clicked and there are no other windows open. 31 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 32 | }) 33 | }) 34 | 35 | // Quit when all windows are closed, except on macOS. There, it's common 36 | // for applications and their menu bar to stay active until the user quits 37 | // explicitly with Cmd + Q. 38 | app.on('window-all-closed', function () { 39 | if (process.platform !== 'darwin') app.quit() 40 | }) 41 | 42 | // In this file you can include the rest of your app's specific main process 43 | // code. You can also put them in separate files and require them here. 44 | -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "dev": "electron .", 7 | "build": "electron-builder" 8 | }, 9 | "devDependencies": { 10 | "electron": "^23.1.0", 11 | "electron-builder": "^23.6.0" 12 | } 13 | } -------------------------------------------------------------------------------- /electron/preload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The preload script runs before. It has access to web APIs 3 | * as well as Electron's renderer process modules and some 4 | * polyfilled Node.js functions. 5 | * 6 | * https://www.electronjs.org/docs/latest/tutorial/sandbox 7 | */ 8 | window.addEventListener('DOMContentLoaded', () => { 9 | const replaceText = (selector, text) => { 10 | const element = document.getElementById(selector) 11 | if (element) element.innerText = text 12 | } 13 | 14 | for (const type of ['chrome', 'node', 'electron']) { 15 | replaceText(`${type}-version`, process.versions[type]) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /img/appreciation-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lianginx/chatgpt-vue/ce6ee80d54f21d6fd7fe3d772086d66a43bd348c/img/appreciation-qr.png -------------------------------------------------------------------------------- /img/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lianginx/chatgpt-vue/ce6ee80d54f21d6fd7fe3d772086d66a43bd348c/img/preview.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ChatGPT 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-ts-vite-router-tailwindcss", 3 | "version": "0.0.1", 4 | "author": "LiangINX", 5 | "main": "index.html", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "run-p type-check build-only", 10 | "preview": "vite preview --port 4173", 11 | "build-only": "vite build", 12 | "type-check": "vue-tsc --noEmit", 13 | "e:dev": "yarn build && cp -r electron/. dist && cd dist && yarn && yarn dev", 14 | "e:build": "yarn build && cp -r electron/. dist && cd dist && yarn && yarn build", 15 | "ne:dev": "npm run build && cp -r electron/. dist && cd dist && npm i && npm run dev", 16 | "ne:build": "npm run build && cp -r electron/. dist && cd dist && npm i && npm run build" 17 | }, 18 | "dependencies": { 19 | "@icon-park/vue-next": "^1.4.2", 20 | "@tailwindcss/typography": "^0.5.9", 21 | "crypto-js": "^4.1.1", 22 | "highlight.js": "^11.7.0", 23 | "markdown-it": "^13.0.1", 24 | "vue": "^3.2.47", 25 | "vue-router": "^4.1.6" 26 | }, 27 | "devDependencies": { 28 | "@types/crypto-js": "^4.1.1", 29 | "@types/markdown-it": "^12.2.3", 30 | "@types/node": "^18.15.0", 31 | "@vitejs/plugin-vue": "^4.0.0", 32 | "@vue/tsconfig": "^0.1.3", 33 | "autoprefixer": "^10.4.12", 34 | "npm-run-all": "^4.1.5", 35 | "postcss": "^8.4.21", 36 | "tailwindcss": "^3.2.7", 37 | "typescript": "^4.9.5", 38 | "vite": "^4.1.4", 39 | "vue-tsc": "^1.2.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lianginx/chatgpt-vue/ce6ee80d54f21d6fd7fe3d772086d66a43bd348c/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .btn { 7 | @apply px-4 py-2 text-sm font-medium tracking-wide text-white capitalize transition-colors duration-300 transform bg-blue-700 rounded-md hover:bg-blue-600 focus:outline-none focus:bg-blue-600 whitespace-nowrap disabled:bg-blue-300; 8 | } 9 | .input { 10 | @apply px-4 py-2 text-gray-700 bg-white border rounded-md mr-2 sm:mr-4 focus:border-blue-400 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-40 flex-grow; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Copy.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 66 | 67 | 81 | -------------------------------------------------------------------------------- /src/components/Loding.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 114 | -------------------------------------------------------------------------------- /src/libs/gpt.ts: -------------------------------------------------------------------------------- 1 | import type { ChatMessage } from "@/types"; 2 | 3 | export async function chat(messageList: ChatMessage[], apiKey: string) { 4 | try { 5 | const result = await fetch("https://api.openai.com/v1/chat/completions", { 6 | method: "post", 7 | // signal: AbortSignal.timeout(8000), 8 | // 开启后到达设定时间会中断流式输出 9 | headers: { 10 | "Content-Type": "application/json", 11 | Authorization: `Bearer ${apiKey}`, 12 | }, 13 | body: JSON.stringify({ 14 | model: "gpt-3.5-turbo", 15 | stream: true, 16 | messages: messageList, 17 | }), 18 | }); 19 | return result; 20 | } catch (error) { 21 | throw error; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/libs/markdown.ts: -------------------------------------------------------------------------------- 1 | import Markdown from "markdown-it"; 2 | import highlight from "highlight.js"; 3 | 4 | const mdOptions: Markdown.Options = { 5 | linkify: true, 6 | typographer: true, 7 | breaks: true, 8 | langPrefix: "language-", 9 | // 代码高亮 10 | highlight(str, lang) { 11 | if (lang && highlight.getLanguage(lang)) { 12 | try { 13 | return ( 14 | '
' +
15 |           highlight.highlight(lang, str, true).value +
16 |           "
" 17 | ); 18 | } catch (__) {} 19 | } 20 | return ""; 21 | }, 22 | }; 23 | 24 | export const md = new Markdown(mdOptions); 25 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import "./assets/tailwind.css"; 5 | import "@icon-park/vue-next/styles/index.css"; 6 | import "highlight.js/styles/dark.css"; 7 | 8 | const app = createApp(App); 9 | 10 | app.use(router).mount("#app"); 11 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | 3 | const router = createRouter({ 4 | history: createWebHashHistory(import.meta.env.BASE_URL), 5 | routes: [ 6 | { 7 | path: "/", 8 | name: "home", 9 | component: () => import("@/views/home.vue"), 10 | }, 11 | ], 12 | }); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /src/types/gpt.ts: -------------------------------------------------------------------------------- 1 | export interface ChatMessage { 2 | role: "user" | "assistant" | "system"; 3 | content: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./gpt"; 2 | -------------------------------------------------------------------------------- /src/views/home.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 217 | 218 | 227 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [require("@tailwindcss/typography")], 8 | }; 9 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | }, 9 | "resolveJsonModule": true, 10 | "lib": ["ESNext", "DOM"] 11 | }, 12 | 13 | "references": [ 14 | { 15 | "path": "./tsconfig.config.json" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | base: "./", 10 | resolve: { 11 | alias: { 12 | "@": fileURLToPath(new URL("./src", import.meta.url)), 13 | }, 14 | }, 15 | }); 16 | --------------------------------------------------------------------------------