mirror of
https://github.com/Chanzhaoyu/chatgpt-web.git
synced 2025-07-29 09:53:44 +00:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ac9536ab87 | ||
![]() |
938c91f635 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,11 +1,21 @@
|
|||||||
|
## v2.5.2
|
||||||
|
|
||||||
|
`2023-02-21`
|
||||||
|
### Feature
|
||||||
|
- 增加对 `markdown` 格式的支持 [Demo](https://github.com/Chanzhaoyu/chatgpt-web/pull/77)
|
||||||
|
### BugFix
|
||||||
|
- 重载会话时滚动条保持
|
||||||
|
|
||||||
## v2.5.1
|
## v2.5.1
|
||||||
|
|
||||||
`2023-02-21`
|
`2023-02-21`
|
||||||
|
|
||||||
### Enhancement
|
### Enhancement
|
||||||
- 调整路由模式为 `hash`
|
- 调整路由模式为 `hash`
|
||||||
- 调整新增会话添加到列表最前
|
- 调整新增会话添加到
|
||||||
- 调整移动端样式
|
- 调整移动端样式
|
||||||
|
|
||||||
|
|
||||||
## v2.5.0
|
## v2.5.0
|
||||||
|
|
||||||
`2023-02-20`
|
`2023-02-20`
|
||||||
|
29
README.md
29
README.md
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
使用 express 和 vue3 搭建的 ChartGPT 演示网页
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
> 提示:目前 `OpenAI` 开放的模型最高只有 `GPT-3`,和现在网页所使用的 `GPT-3.5` 或 `GPT-4` 有很大差距,需要等官方开放最新的模型接口。
|
||||||
|
|
||||||
@@ -11,8 +12,6 @@
|
|||||||
|
|
||||||
[✓] 对代码等消息类型的格式化美化处理
|
[✓] 对代码等消息类型的格式化美化处理
|
||||||
|
|
||||||
[✗] 用户模块(注册、登录、个人中心)
|
|
||||||
|
|
||||||
[✗] 界面多语言
|
[✗] 界面多语言
|
||||||
|
|
||||||
[✗] 界面主题
|
[✗] 界面主题
|
||||||
@@ -61,7 +60,6 @@ pnpm install
|
|||||||
pnpm bootstrap
|
pnpm bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 运行
|
## 运行
|
||||||
### 后端服务
|
### 后端服务
|
||||||
|
|
||||||
@@ -78,15 +76,24 @@ pnpm dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 打包
|
## 打包
|
||||||
## Docker build
|
|
||||||
|
|
||||||
[参考信息](https://github.com/Chanzhaoyu/chatgpt-web/pull/42)
|
### 使用 Docker
|
||||||
|
### Docker build & Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t chatgpt-web .
|
docker build -t chatgpt-web .
|
||||||
|
|
||||||
|
# 前台运行
|
||||||
|
docker run --name chatgpt-web --rm -it -p 3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web
|
||||||
|
|
||||||
|
# 后台运行
|
||||||
|
docker run --name chatgpt-web -d -p 3002:3002 --env OPENAI_API_KEY=your_api_key chatgpt-web
|
||||||
|
|
||||||
|
# 运行地址
|
||||||
|
http://localhost:3002/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker compose
|
### Docker compose
|
||||||
|
|
||||||
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
[Hub 地址](https://hub.docker.com/repository/docker/chenzhaoyu94/chatgpt-web/general)
|
||||||
|
|
||||||
@@ -102,10 +109,12 @@ services:
|
|||||||
OPENAI_API_KEY: xxxxxx
|
OPENAI_API_KEY: xxxxxx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 手动打包
|
||||||
### 后端服务
|
### 后端服务
|
||||||
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
|
> 如果你不需要本项目的 `node` 接口,可以省略如下操作
|
||||||
|
|
||||||
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。(搜索关键字:`express部署`)
|
复制 `service` 文件夹到你有 `node` 服务环境的服务器上。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 安装
|
# 安装
|
||||||
@@ -122,7 +131,9 @@ PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可
|
|||||||
|
|
||||||
### 前端打包
|
### 前端打包
|
||||||
|
|
||||||
根目录下运行以下命令,然后将 `dist` 文件夹复制到你的托管服务器上
|
1、修改根目录下 `.env` 内 `VITE_APP_API_BASE_URL` 为你的实际后端接口地址
|
||||||
|
|
||||||
|
2、根目录下运行以下命令,然后将 `dist` 文件夹内的文件复制到你网站服务的根目录下
|
||||||
|
|
||||||
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
|
[参考信息](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)
|
||||||
|
|
||||||
|
BIN
docs/cover.png
BIN
docs/cover.png
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 96 KiB |
BIN
docs/cover2.png
Normal file
BIN
docs/cover2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 518 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chatgpt-web",
|
"name": "chatgpt-web",
|
||||||
"version": "2.5.1",
|
"version": "2.5.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "ChatGPT Web",
|
"description": "ChatGPT Web",
|
||||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^9.13.0",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
|
"marked": "^4.2.12",
|
||||||
"naive-ui": "^2.34.3",
|
"naive-ui": "^2.34.3",
|
||||||
"pinia": "^2.0.30",
|
"pinia": "^2.0.30",
|
||||||
"vue": "^3.2.47",
|
"vue": "^3.2.47",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"@commitlint/config-conventional": "^17.4.4",
|
"@commitlint/config-conventional": "^17.4.4",
|
||||||
"@iconify/vue": "^4.1.0",
|
"@iconify/vue": "^4.1.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
|
"@types/marked": "^4.0.8",
|
||||||
"@types/node": "^18.14.0",
|
"@types/node": "^18.14.0",
|
||||||
"@types/web-bluetooth": "^0.0.16",
|
"@types/web-bluetooth": "^0.0.16",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -6,6 +6,7 @@ specifiers:
|
|||||||
'@commitlint/config-conventional': ^17.4.4
|
'@commitlint/config-conventional': ^17.4.4
|
||||||
'@iconify/vue': ^4.1.0
|
'@iconify/vue': ^4.1.0
|
||||||
'@types/crypto-js': ^4.1.1
|
'@types/crypto-js': ^4.1.1
|
||||||
|
'@types/marked': ^4.0.8
|
||||||
'@types/node': ^18.14.0
|
'@types/node': ^18.14.0
|
||||||
'@types/web-bluetooth': ^0.0.16
|
'@types/web-bluetooth': ^0.0.16
|
||||||
'@vitejs/plugin-vue': ^4.0.0
|
'@vitejs/plugin-vue': ^4.0.0
|
||||||
@@ -18,6 +19,7 @@ specifiers:
|
|||||||
husky: ^8.0.3
|
husky: ^8.0.3
|
||||||
less: ^4.1.3
|
less: ^4.1.3
|
||||||
lint-staged: ^13.1.2
|
lint-staged: ^13.1.2
|
||||||
|
marked: ^4.2.12
|
||||||
naive-ui: ^2.34.3
|
naive-ui: ^2.34.3
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
pinia: ^2.0.30
|
pinia: ^2.0.30
|
||||||
@@ -33,6 +35,7 @@ specifiers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@vueuse/core': 9.13.0_vue@3.2.47
|
'@vueuse/core': 9.13.0_vue@3.2.47
|
||||||
highlight.js: 11.7.0
|
highlight.js: 11.7.0
|
||||||
|
marked: 4.2.12
|
||||||
naive-ui: 2.34.3_vue@3.2.47
|
naive-ui: 2.34.3_vue@3.2.47
|
||||||
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
|
pinia: 2.0.30_hmuptsblhheur2tugfgucj7gc4
|
||||||
vue: 3.2.47
|
vue: 3.2.47
|
||||||
@@ -44,6 +47,7 @@ devDependencies:
|
|||||||
'@commitlint/config-conventional': 17.4.4
|
'@commitlint/config-conventional': 17.4.4
|
||||||
'@iconify/vue': 4.1.0_vue@3.2.47
|
'@iconify/vue': 4.1.0_vue@3.2.47
|
||||||
'@types/crypto-js': 4.1.1
|
'@types/crypto-js': 4.1.1
|
||||||
|
'@types/marked': 4.0.8
|
||||||
'@types/node': 18.14.0
|
'@types/node': 18.14.0
|
||||||
'@types/web-bluetooth': 0.0.16
|
'@types/web-bluetooth': 0.0.16
|
||||||
'@vitejs/plugin-vue': 4.0.0_vite@4.1.2+vue@3.2.47
|
'@vitejs/plugin-vue': 4.0.0_vite@4.1.2+vue@3.2.47
|
||||||
@@ -735,6 +739,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/marked/4.0.8:
|
||||||
|
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/mdast/3.0.10:
|
/@types/mdast/3.0.10:
|
||||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3229,6 +3237,12 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/marked/4.2.12:
|
||||||
|
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mdast-util-from-markdown/0.8.5:
|
/mdast-util-from-markdown/0.8.5:
|
||||||
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import type { App, Directive } from 'vue'
|
import type { App, Directive } from 'vue'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
import includeCode from '@/utils/functions/includeCode'
|
||||||
|
|
||||||
function highlightCode(el: HTMLElement) {
|
function highlightCode(el: HTMLElement) {
|
||||||
const regexp = /^(?:\s{4}|\t).+/gm
|
if (includeCode(el.textContent))
|
||||||
if (el.textContent?.indexOf(' = ') !== -1 || el.textContent.match(regexp))
|
|
||||||
hljs.highlightBlock(el)
|
hljs.highlightBlock(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
src/utils/functions/includeCode.ts
Normal file
8
src/utils/functions/includeCode.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
function includeCode(text: string | null | undefined) {
|
||||||
|
const regexp = /^(?:\s{4}|\t).+/gm
|
||||||
|
if (text?.includes(' = ') || text?.match(regexp))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export default includeCode
|
@@ -1,28 +1,60 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import includeCode from '@/utils/functions/includeCode'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
inversion?: boolean
|
inversion?: boolean
|
||||||
error?: boolean
|
error?: boolean
|
||||||
|
text?: string
|
||||||
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const wrapClass = computed(() => {
|
||||||
|
return [
|
||||||
|
'text-wrap',
|
||||||
|
'p-2',
|
||||||
|
'min-w-[20px]',
|
||||||
|
'rounded-md',
|
||||||
|
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||||
|
{ 'text-red-500': props.error },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const text = computed(() => {
|
||||||
|
if (props.text) {
|
||||||
|
if (!includeCode(props.text))
|
||||||
|
return marked.parse(props.text)
|
||||||
|
return props.text
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="wrapClass">
|
||||||
class="min-w-[20px] p-2 rounded-md"
|
<template v-if="loading">
|
||||||
:class="[inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]', { 'text-red-500': error }]"
|
<span class="w-[3px] h-[20px] block animate-blink" />
|
||||||
>
|
</template>
|
||||||
<span
|
<template v-else>
|
||||||
v-highlight
|
<code v-if="includeCode(text)" v-highlight class="leading-relaxed" v-text="text" />
|
||||||
class="leading-relaxed whitespace-pre-wrap"
|
<div v-else class="leading-relaxed break-all" v-html="text" />
|
||||||
>
|
</template>
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style lang="less">
|
||||||
|
.text-wrap{
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
background-color: #fff0 !important;
|
background-color: #fff0 !important;
|
||||||
|
white-space: break-spaces;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -37,10 +37,7 @@ function handleRegenerate() {
|
|||||||
{{ dateTime }}
|
{{ dateTime }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-end mt-2">
|
<div class="flex items-end mt-2">
|
||||||
<Text :inversion="inversion" :error="error">
|
<Text :inversion="inversion" :error="error" :text="text" :loading="loading" />
|
||||||
<span v-if="loading" class="w-[3px] h-[20px] block animate-blink" />
|
|
||||||
<span v-else>{{ text }}</span>
|
|
||||||
</Text>
|
|
||||||
<button
|
<button
|
||||||
v-if="!inversion && !loading"
|
v-if="!inversion && !loading"
|
||||||
class="mb-2 ml-2 transition text-neutral-400 hover:text-neutral-800"
|
class="mb-2 ml-2 transition text-neutral-400 hover:text-neutral-800"
|
||||||
|
@@ -153,7 +153,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
const { data } = await fetchChatAPI<Chat.ConversationResponse>(message, options, controller.signal)
|
||||||
@@ -170,7 +169,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
let errorMessage = 'Something went wrong, please try again later.'
|
let errorMessage = 'Something went wrong, please try again later.'
|
||||||
@@ -191,7 +189,6 @@ async function onRegenerate(index: number) {
|
|||||||
requestOptions: { prompt: message, ...options },
|
requestOptions: { prompt: message, ...options },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -224,6 +221,13 @@ const buttonDisabled = computed(() => {
|
|||||||
return loading.value || !prompt.value || prompt.value.trim() === ''
|
return loading.value || !prompt.value || prompt.value.trim() === ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const wrapClass = computed(() => {
|
||||||
|
if (isMobile.value)
|
||||||
|
return ['pt-14', 'pb-14']
|
||||||
|
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
const footerClass = computed(() => {
|
const footerClass = computed(() => {
|
||||||
let classes = ['p-4']
|
let classes = ['p-4']
|
||||||
if (isMobile.value)
|
if (isMobile.value)
|
||||||
@@ -242,7 +246,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full pt-14 pb-14">
|
<div class="flex flex-col h-full" :class="wrapClass">
|
||||||
<main class="flex-1 overflow-hidden">
|
<main class="flex-1 overflow-hidden">
|
||||||
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]">
|
<div ref="scrollRef" class="h-full p-4 overflow-hidden overflow-y-auto" :class="[{ 'p-2': isMobile }]">
|
||||||
<template v-if="!dataSources.length">
|
<template v-if="!dataSources.length">
|
||||||
|
Reference in New Issue
Block a user