Compare commits

11 Commits

Author SHA1 Message Date
ChenZhaoYu
a5a67f688e chore: release v1.0.4 2023-02-11 15:38:57 +08:00
ChenZhaoYu
692d37ec35 chore: version 1.0.4 2023-02-11 15:38:44 +08:00
ChenZhaoYu
4b16560958 feat: 支持上下文联想 2023-02-11 15:20:58 +08:00
ChenZhaoYu
52bfd15ad8 chore: release v1.0.3 2023-02-11 09:17:03 +08:00
ChenZhaoYu
e0920bee4b chore: CHANGELOG 2023-02-11 09:14:11 +08:00
ChenZhaoYu
cb589e05e9 chore: update deps 2023-02-11 08:32:59 +08:00
ChenZhaoYu
6aa2591d9c chore: editor config 2023-02-11 08:17:48 +08:00
ChenZhaoYu
ad41dd21c6 chore: remove Message.vue 2023-02-10 11:51:30 +08:00
ChenZhaoYu
35da3f1d1f feat: 拆分以便扩展 2023-02-10 11:40:40 +08:00
ChenZhaoYu
31ea7c5529 chore: remove some config 2023-02-10 10:49:26 +08:00
ChenZhaoYu
cab7b0ec99 chore: CHANGELOG 2023-02-10 10:36:39 +08:00
11 changed files with 402 additions and 374 deletions

4
.npmrc
View File

@@ -1,4 +0,0 @@
registry=https://registry.npmmirror.com/
shamefully-hoist=true
strict-peer-dependencies=false
auto-install-peers=true

View File

@@ -1,3 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
"recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
}

15
.vscode/settings.json vendored
View File

@@ -1,8 +1,21 @@
{
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"json",
"jsonc",
"json5",
"yaml",
"yml",
"markdown"
],
"cSpell.words": [
"antfu",
"axios",

View File

@@ -0,0 +1,25 @@
## v1.0.4
`2023-02-11`
### Feature
- 支持上下文联想
## v1.0.3
`2023-02-11`
### Enhancement
- 拆分 `service` 文件以便扩展
- 调整 `Eslint` 相关验证
### BugFix
- 修复部份控制台报错
## v1.0.2
`2023-02-10`
### BugFix
- 修复新增信息容器不会自动滚动到问题
- 修复文本过长不换行到问题 [#1](https://github.com/Chanzhaoyu/chatgpt-web/issues/1)

View File

@@ -1,50 +1,49 @@
{
"name": "chatgpt-web",
"version": "1.0.2",
"description": "ChatGPT Web Bot",
"author": "ChenZhaoYu <chenzhaoyu1994@gami.com>",
"private": false,
"keywords": [
"chatgpt",
"chatbot",
"web",
"vue"
],
"scripts": {
"dev": "vite",
"service": "esno ./service/index.ts",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"release": "bumpp package.json --commit --push --tag"
},
"dependencies": {
"naive-ui": "^2.34.3",
"vue": "^3.2.45"
},
"devDependencies": {
"@antfu/eslint-config": "^0.35.2",
"@iconify/vue": "^4.1.0",
"@types/babel__core": "^7.20.0",
"@types/express": "^4.17.17",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"axios": "^1.3.2",
"bumpp": "^8.2.1",
"chatgpt": "^4.2.0",
"dotenv": "^16.0.3",
"eslint": "^8.22.0",
"esno": "^0.16.3",
"express": "^4.18.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"prettier": "^2.7.1",
"tailwindcss": "^3.2.6",
"typescript": "~4.7.4",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}
"name": "chatgpt-web",
"version": "1.0.4",
"private": false,
"description": "ChatGPT Web Bot",
"author": "ChenZhaoYu <chenzhaoyu1994@gami.com>",
"keywords": [
"chatgpt",
"chatbot",
"web",
"vue"
],
"scripts": {
"dev": "vite",
"service": "esno ./service/index.ts",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --fix --ignore-path .gitignore",
"release": "bumpp package.json --commit --push --tag"
},
"dependencies": {
"naive-ui": "^2.34.3",
"vue": "^3.2.47"
},
"devDependencies": {
"@antfu/eslint-config": "^0.35.2",
"@iconify/vue": "^4.1.0",
"@types/express": "^4.17.17",
"@types/node": "^18.13.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"axios": "^1.3.2",
"bumpp": "^8.2.1",
"chatgpt": "^4.2.0",
"dotenv": "^16.0.3",
"eslint": "^8.34.0",
"esno": "^0.16.3",
"express": "^4.18.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"tailwindcss": "^3.2.6",
"typescript": "~4.9.5",
"vite": "^4.1.1",
"vue-tsc": "^1.0.24"
}
}

479
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

54
service/chatgpt.ts Normal file
View File

@@ -0,0 +1,54 @@
import dotenv from 'dotenv'
import { ChatGPTAPI } from 'chatgpt'
interface ChatContext {
conversationId?: string
parentMessageId?: string
}
dotenv.config()
const apiKey = process.env.OPENAI_API_KEY
if (apiKey === undefined)
throw new Error('OPENAI_API_KEY is not defined')
const chatContext = new Set<ChatContext>()
/**
* More Info: https://github.com/transitive-bullshit/chatgpt-api
*/
const api = new ChatGPTAPI({ apiKey })
async function chatReply(message: string) {
if (!message)
return
// Get the last context from the chat context
// If there is a last context, add it to the options
let options = {}
const lastContext = Array.from(chatContext).pop()
if (lastContext) {
const { conversationId, parentMessageId } = lastContext
options = { conversationId, parentMessageId }
}
// Send the message to the API
const response = await api.sendMessage(message, { ...options })
const { conversationId, id } = response
// Add the new context to the chat context
if (conversationId && id)
chatContext.add({ conversationId, parentMessageId: id })
return response
}
async function clearChatContext() {
// Clear the chat context
chatContext.clear()
return Promise.resolve({ message: 'Chat context cleared' })
}
export { chatReply, clearChatContext }

View File

@@ -1,30 +1,10 @@
import dotenv from 'dotenv'
import { ChatGPTAPI } from 'chatgpt'
import express from 'express'
dotenv.config()
import { chatReply, clearChatContext } from './chatgpt'
const app = express()
app.use(express.json())
async function chatAPI(message: string) {
if (!message)
throw new Error('Message is not defined')
if (!process.env.OPENAI_API_KEY)
throw new Error('OPENAI_API_KEY is not defined in .env file')
try {
const api = new ChatGPTAPI({ apiKey: process.env.OPENAI_API_KEY })
const res = await api.sendMessage(message)
return res
}
catch (error) {
return error
}
}
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Content-Type')
@@ -36,6 +16,11 @@ app.listen(3002, () => globalThis.console.log('Server is running on port 3002'))
app.post('/chat', async (req, res) => {
const { message } = req.body
const response = await chatAPI(message)
const response = await chatReply(message)
res.send(response)
})
app.post('/clear', async (req, res) => {
const response = await clearChatContext()
res.send(response)
})

View File

@@ -1,34 +0,0 @@
<script setup lang='ts'>
import { Icon } from '@/components'
interface Props {
user?: boolean
date?: string
message?: string
error?: boolean
}
defineProps<Props>()
</script>
<template>
<div class="flex w-full mb-6" :class="[{ 'flex-row-reverse': user }]">
<div
class="flex items-center justify-center rounded-full overflow-hidden w-[32px] h-[32px]"
:class="[user ? 'ml-3' : 'mr-3']"
>
<img v-if="user" src="@/assets/avatar.jpg" class="object-cover w-full h-full " alt="avatar">
<Icon v-else local-icon="logo" class="text-[26px]" />
</div>
<div class="flex flex-col flex-1 text-sm" :class="[{ 'items-end': user }]">
<span class="text-xs text-[#b4bbc4]">
{{ date }}
</span>
<div class="p-2 mt-2 rounded-md" :class="[user ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]']">
<span class="leading-relaxed whitespace-pre" :class="[{ 'text-red-500': error }]">
{{ message }}
</span>
</div>
</div>
</div>
</template>

View File

@@ -2,7 +2,7 @@
import { nextTick, onMounted, ref } from 'vue'
import { NButton, NInput, NPopover, useMessage } from 'naive-ui'
import { Message } from './components'
import { fetchChatAPI } from './request'
import { clearChatContext, fetchChatAPI } from './request'
import { Icon } from '@/components'
interface ListProps {
@@ -27,9 +27,16 @@ function initChat() {
addMessage('Hi, I am ChatGPT, a chatbot based on GPT-3.', false)
}
function handleClear() {
list.value = []
setTimeout(initChat, 100)
async function handleClear() {
try {
const { message } = await clearChatContext()
ms.success(message)
list.value = []
setTimeout(initChat, 100)
}
catch (error) {
ms.error('Clear failed, please try again later.')
}
}
function handleEnter(event: KeyboardEvent) {
@@ -81,7 +88,7 @@ function addMessage(message: string, reversal = false) {
<Icon icon="ri:delete-bin-6-line" />
</button>
</template>
<span>Clear</span>
<span>Clear Context</span>
</NPopover>
</div>
</header>

View File

@@ -1,13 +1,13 @@
import axios from 'axios'
async function fetchChatAPI(message: string) {
const url = `${import.meta.env.VITE_GLOB_API_URL}/chat`
const BASE_URL = import.meta.env.VITE_GLOB_API_URL
async function fetchChatAPI(message: string) {
if (!message || message.trim() === '')
return
try {
const { status, data } = await axios.post(url, { message })
const { status, data } = await axios.post(`${BASE_URL}/chat`, { message })
if (status === 200) {
if (data.text)
@@ -24,4 +24,18 @@ async function fetchChatAPI(message: string) {
}
}
export { fetchChatAPI }
async function clearChatContext() {
try {
const { status, data } = await axios.post(`${BASE_URL}/clear`)
if (status === 200)
return Promise.resolve(data)
return Promise.reject(new Error('Request failed'))
}
catch (error) {
return Promise.reject(error)
}
}
export { fetchChatAPI, clearChatContext }