Compare commits

...

20 Commits

Author SHA1 Message Date
ChenZhaoYu
73a67b8f64 chore: 2.10.2_2 2023-03-10 00:47:56 +08:00
ChenZhaoYu
222b3eaa4c feat: 移动端下按钮收缩 2023-03-10 00:46:09 +08:00
ChenZhaoYu
c4baccdc48 chore: v2.10.2_1 2023-03-10 00:03:19 +08:00
ChenZhaoYu
7021a08ecf chore: reset scrollToBottom 2023-03-10 00:02:02 +08:00
ChenZhaoYu
e88b9bef13 chore: reset .env 2023-03-09 23:57:47 +08:00
ChenZhaoYu
c17cc16c0e chore: 异常打印和调整日志 2023-03-09 22:58:06 +08:00
ChenZhaoYu
d7d037618f chore: version 2.10.2 2023-03-09 22:49:15 +08:00
ChenZhaoYu
01edad7717 fix: 修复默认模型判断错误 2023-03-09 22:45:43 +08:00
ChenZhaoYu
eff787a2b7 chore: 移除两种报错 2023-03-09 20:16:18 +08:00
ChenZhaoYu
8616526136 fix: typo 2023-03-09 19:41:27 +08:00
ChenZhaoYu
32e3963390 chore: v2.10.1 2023-03-09 19:25:19 +08:00
ChenZhaoYu
7d52e6dd1e feat: model typo 2023-03-09 19:13:18 +08:00
ChenZhaoYu
ba41015df8 Merge branch 'feature' 2023-03-09 19:02:22 +08:00
ChenZhaoYu
f084460d1c fix: 深色模式导出图片的样式问题 2023-03-09 18:57:46 +08:00
Yige
a4ef23d603 chore: rename environment variables files (#395)
* chore: rename environment variables files

* docs: update README.md about .env file
2023-03-09 18:41:33 +08:00
xieccc
d3daa654a7 feat: 新增API模型配置项 (#404)
* chore: 更新文档

* Improve zh-TW locale (#379)

* fix: 移动端样式

* feat: typo

* fix: 调整滚动回原样

* feat: 新增API模型配置项

---------

Co-authored-by: ChenZhaoYu <790348264@qq.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
2023-03-09 18:38:30 +08:00
acongee
9576edf26f feat: 优化打字机模式显示新内容的逻辑 (#394)
* 添加为打字机模式优化的滚动至底部函数

* feat: 优化打字机模式始终显示最新内容的逻辑

* feat: 内容输出结束时也根据滚动条位置判断是否滚动至底部
2023-03-09 18:15:13 +08:00
CornerSkyless
5b74ac9cc6 fix: 修复导出图片会丢失头像的问题 (#392) 2023-03-09 17:59:26 +08:00
CornerSkyless
444e2ec2e8 feat: 增加了一个可以是否要发送历史上下文的开关 (#393)
* chore: 更新文档

* Improve zh-TW locale (#379)

* fix: 移动端样式

* feat: typo

* fix: 调整滚动回原样

* feat: 支持切换是否要发送前文信息

* style: 修复 lint

---------

Co-authored-by: ChenZhaoYu <790348264@qq.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
2023-03-09 17:56:11 +08:00
壳壳中的宇宙
60b7874f65 优化docker打包镜像文件过大的问题 (#415)
* chore: 更新文档

* Improve zh-TW locale (#379)

* fix: 移动端样式

* feat: typo

* fix: 调整滚动回原样

* 优化docker打包镜像文件过大

---------

Co-authored-by: ChenZhaoYu <790348264@qq.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
2023-03-09 17:52:39 +08:00
16 changed files with 154 additions and 33 deletions

View File

@@ -1,6 +1,6 @@
**/node_modules
*/node_modules
node_modules
Dockerfile
.git
.husky
.github
.vscode
.*
*/.*

3
.gitignore vendored
View File

@@ -27,3 +27,6 @@ coverage
*.njsproj
*.sln
*.sw?
# Environment variables files
/service/.env

View File

@@ -1,3 +1,42 @@
## v2.10.2
`2023-03-09`
衔接 `2.10.1` 版本[详情](https://github.com/Chanzhaoyu/chatgpt-web/releases/tag/v2.10.1)
## Enhancement
- 移动端下输入框获得焦点时左侧按钮隐藏
## BugFix
- 修复 `2.10.1` 中添加 `OPENAI_API_MODEL` 变量的判断错误,会导致默认模型指定失效,抱歉
- 回退 `2.10.1` 中前端变量影响 `Docker` 打包
## v2.10.1
`2023-03-09`
注意:删除了 `.env` 文件改用 `.env.example` 代替,如果是手动部署的同学现在需要手动创建 `.env` 文件并从 `.env.example` 中复制需要的变量,并且 `.env` 文件现在会在 `Git` 提交中被忽略,原因如下:
- 在项目中添加 `.env` 从一开始就是个错误的示范
- 如果是 `Fork` 项目进行修改测试总是会被 `Git` 修改提示给打扰
- 感谢 [yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/395) 的提醒和修改
这两天开始,官方已经开始对第三方代理进行了拉闸, `accessToken` 即将或已经开始可能会不可使用。异常 `API` 使用也开始封号,封号缘由不明,如果出现使用 `API` 提示错误,请查看后端控制台信息,或留意邮箱。
## Feature
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/393) 添加是否发送上下文开关功能
## Enhancement
- 感谢 [nagaame](https://github.com/Chanzhaoyu/chatgpt-web/pull/415) 优化`docker`打包镜像文件过大的问题
- 感谢 [xieccc](https://github.com/Chanzhaoyu/chatgpt-web/pull/404) 新增 `API` 模型配置变量 `OPENAI_API_MODEL`
- 感谢 [acongee](https://github.com/Chanzhaoyu/chatgpt-web/pull/394) 优化输出时滚动条问题
## BugFix
- 感谢 [CornerSkyless](https://github.com/Chanzhaoyu/chatgpt-web/pull/392) 修复导出图片会丢失头像的问题
- 修复深色模式导出图片的样式问题
## v2.10.0
`2023-03-07`

View File

@@ -4,7 +4,11 @@ FROM node:lts-alpine AS builder
COPY ./ /app
WORKDIR /app
RUN npm install pnpm -g && pnpm install && pnpm run build
RUN apk add --no-cache git \
&& npm install pnpm -g \
&& pnpm install \
&& pnpm run build \
&& rm -rf /root/.npm /root/.pnpm-store /usr/local/share/.cache /tmp/*
# service
FROM node:lts-alpine
@@ -13,8 +17,12 @@ COPY /service /app
COPY --from=builder /app/dist /app/public
WORKDIR /app
RUN npm install pnpm -g && pnpm install
RUN apk add --no-cache git \
&& npm install pnpm -g \
&& pnpm install --only=production \
&& rm -rf /root/.npm /root/.pnpm-store /usr/local/share/.cache /tmp/*
EXPOSE 3002
CMD ["pnpm", "run", "start"]
CMD ["pnpm", "run", "start"]

View File

@@ -55,7 +55,7 @@ Comparison:
[Details](https://github.com/Chanzhaoyu/chatgpt-web/issues/138)
Switching Methods:
1. Go to the `service/.env` file.
1. Go to the `service/.env.example` file and copy the contents to the `service/.env` file.
2. For `OpenAI API Key`, fill in the `OPENAI_API_KEY` field [(Get apiKey)](https://platform.openai.com/overview).
3. For `Web API`, fill in the `OPENAI_ACCESS_TOKEN` field [(Get accessToken)](https://chat.openai.com/api/auth/session).
4. When both are present, `OpenAI API Key` takes precedence.
@@ -168,6 +168,7 @@ pnpm dev
- `OPENAI_API_KEY` one of two
- `OPENAI_ACCESS_TOKEN` one of two, `OPENAI_API_KEY` takes precedence when both are present
- `OPENAI_API_BASE_URL` optional, available when `OPENAI_API_KEY` is set
- `OPENAI_API_MODEL` optional, available when `OPENAI_API_KEY` is set
- `API_REVERSE_PROXY` optional, available when `OPENAI_ACCESS_TOKEN` is set [Reference](#introduction)
- `AUTH_SECRET_KEY` Access Passwordoptional
- `TIMEOUT_MS` timeout, in milliseconds, optional
@@ -210,6 +211,8 @@ services:
OPENAI_ACCESS_TOKEN: xxxxxx
# api interface url, optional, available when OPENAI_API_KEY is set
OPENAI_API_BASE_URL: xxxx
# api model, optional, available when OPENAI_API_KEY is set
OPENAI_API_MODEL: xxxx
# reverse proxy, optional
API_REVERSE_PROXY: xxx
# access passwordoptional
@@ -222,6 +225,7 @@ services:
SOCKS_PROXY_PORT: xxxx
```
The `OPENAI_API_BASE_URL` is optional and only used when setting the `OPENAI_API_KEY`.
The `OPENAI_API_MODEL` is optional and only used when setting the `OPENAI_API_KEY`.
### Deployment with Railway
@@ -237,6 +241,7 @@ The `OPENAI_API_BASE_URL` is optional and only used when setting the `OPENAI_API
| `OPENAI_API_KEY` | Optional | Required for `OpenAI API`. `apiKey` can be obtained from [here](https://platform.openai.com/overview). |
| `OPENAI_ACCESS_TOKEN`| Optional | Required for `Web API`. `accessToken` can be obtained from [here](https://chat.openai.com/api/auth/session).|
| `OPENAI_API_BASE_URL` | Optional, only for `OpenAI API` | API endpoint. |
| `OPENAI_API_MODEL` | Optional, only for `OpenAI API` | API model. |
| `API_REVERSE_PROXY` | Optional, only for `Web API` | Reverse proxy address for `Web API`. [Details](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) |
| `SOCKS_PROXY_HOST` | Optional, effective with `SOCKS_PROXY_PORT` | Socks proxy. |
| `SOCKS_PROXY_PORT` | Optional, effective with `SOCKS_PROXY_HOST` | Socks proxy port. |
@@ -266,7 +271,7 @@ PS: You can also run `pnpm start` directly on the server without packaging.
#### Frontend webpage
1. Modify `VITE_APP_API_BASE_URL` in `.env` at the root directory to your actual backend interface address.
1. Refer to the root directory `.env.example` file content to create `.env` file, modify `VITE_APP_API_BASE_URL` in `.env` at the root directory to your actual backend interface address.
2. Run the following command in the root directory and then copy the files in the `dist` folder to the root directory of your website service.
[Reference information](https://cn.vitejs.dev/guide/static-deploy.html#building-the-app)

View File

@@ -54,7 +54,7 @@
[查看详情](https://github.com/Chanzhaoyu/chatgpt-web/issues/138)
切换方式:
1. 进入 `service/.env` 文件
1. 进入 `service/.env.example` 文件,复制内容到 `service/.env` 文件
2. 使用 `OpenAI API Key` 请填写 `OPENAI_API_KEY` 字段 [(获取 apiKey)](https://platform.openai.com/overview)
3. 使用 `Web API` 请填写 `OPENAI_ACCESS_TOKEN` 字段 [(获取 accessToken)](https://chat.openai.com/api/auth/session)
4. 同时存在时以 `OpenAI API Key` 优先
@@ -166,6 +166,7 @@ pnpm dev
- `OPENAI_API_KEY` 二选一
- `OPENAI_ACCESS_TOKEN` 二选一,同时存在时,`OPENAI_API_KEY` 优先
- `OPENAI_API_BASE_URL` 可选,设置 `OPENAI_API_KEY` 时可用
- `OPENAI_API_MODEL` 可选,设置 `OPENAI_API_KEY` 时可用
- `API_REVERSE_PROXY` 可选,设置 `OPENAI_ACCESS_TOKEN` 时可用 [参考](#介绍)
- `AUTH_SECRET_KEY` 访问权限密钥,可选
- `TIMEOUT_MS` 超时,单位毫秒,可选
@@ -208,6 +209,8 @@ services:
OPENAI_ACCESS_TOKEN: xxxxxx
# API接口地址可选设置 OPENAI_API_KEY 时可用
OPENAI_API_BASE_URL: xxxx
# API模型可选设置 OPENAI_API_KEY 时可用
OPENAI_API_MODEL: xxxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
# 访问权限密钥,可选
@@ -220,6 +223,7 @@ services:
SOCKS_PROXY_PORT: xxxx
```
- `OPENAI_API_BASE_URL` 可选,设置 `OPENAI_API_KEY` 时可用
- `OPENAI_API_MODEL` 可选,设置 `OPENAI_API_KEY` 时可用
### 使用 Railway 部署
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/yytmgc)
@@ -234,6 +238,7 @@ services:
| `OPENAI_API_KEY` | `OpenAI API` 二选一 | 使用 `OpenAI API` 所需的 `apiKey` [(获取 apiKey)](https://platform.openai.com/overview) |
| `OPENAI_ACCESS_TOKEN` | `Web API` 二选一 | 使用 `Web API` 所需的 `accessToken` [(获取 accessToken)](https://chat.openai.com/api/auth/session) |
| `OPENAI_API_BASE_URL` | 可选,`OpenAI API` 时可用 | `API`接口地址 |
| `OPENAI_API_MODEL` | 可选,`OpenAI API` 时可用 | `API`模型 |
| `API_REVERSE_PROXY` | 可选,`Web API` 时可用 | `Web API` 反向代理地址 [详情](https://github.com/transitive-bullshit/chatgpt-api#reverse-proxy) |
| `SOCKS_PROXY_HOST` | 可选,和 `SOCKS_PROXY_PORT` 一起时生效 | Socks代理 |
| `SOCKS_PROXY_PORT` | 可选,和 `SOCKS_PROXY_HOST` 一起时生效 | Socks代理端口 |
@@ -261,7 +266,7 @@ PS: 不进行打包,直接在服务器上运行 `pnpm start` 也可
#### 前端网页
1、修改根目录下 `.env` `VITE_APP_API_BASE_URL` 为你的实际后端接口地址
1、修改根目录下 `.env` 文件中的 `VITE_APP_API_BASE_URL` 为你的实际后端接口地址
2、根目录下运行以下命令然后将 `dist` 文件夹内的文件复制到你网站服务的根目录下

View File

@@ -12,6 +12,8 @@ services:
OPENAI_ACCESS_TOKEN: xxxxxx
# API接口地址可选设置 OPENAI_API_KEY 时可用
OPENAI_API_BASE_URL: xxxx
# API模型可选设置 OPENAI_API_KEY 时可用
OPENAI_API_MODEL: xxxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
# 访问权限密钥,可选

View File

@@ -1,6 +1,6 @@
{
"name": "chatgpt-web",
"version": "2.10.0",
"version": "2.10.2",
"private": false,
"description": "ChatGPT Web",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",

View File

@@ -7,6 +7,9 @@ OPENAI_ACCESS_TOKEN=
# OpenAI API Base URL - https://api.openai.com
OPENAI_API_BASE_URL=
# OpenAI API Model - https://platform.openai.com/docs/models
OPENAI_API_MODEL=
# Reverse Proxy
API_REVERSE_PROXY=

View File

@@ -8,10 +8,8 @@ import { sendResponse } from '../utils'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
const ErrorCodeMessage: Record<string, string> = {
400: '[OpenAI] 模型的最大上下文长度是4096个令牌请减少信息的长度。| This model\'s maximum context length is 4096 tokens.',
401: '[OpenAI] 提供错误的API密钥 | Incorrect API key provided',
403: '[OpenAI] 服务器拒绝访问,请稍后再试 | Server refused to access, please try again later',
429: '[OpenAI] 服务器限流,请稍后再试 | Server was limited, please try again later',
502: '[OpenAI] 错误的网关 | Bad Gateway',
503: '[OpenAI] 服务器繁忙,请稍后再试 | Server is busy, please try again later',
504: '[OpenAI] 网关超时 | Gateway Time-out',
@@ -33,11 +31,14 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
// More Info: https://github.com/transitive-bullshit/chatgpt-api
if (process.env.OPENAI_API_KEY) {
const OPENAI_API_MODEL = process.env.OPENAI_API_MODEL
const model = (typeof OPENAI_API_MODEL === 'string' && OPENAI_API_MODEL.length > 0)
? OPENAI_API_MODEL
: 'gpt-3.5-turbo'
const options: ChatGPTAPIOptions = {
apiKey: process.env.OPENAI_API_KEY,
completionParams: {
model: 'gpt-3.5-turbo',
},
completionParams: { model },
debug: false,
}

View File

@@ -27,6 +27,9 @@ export default {
exportImageConfirm: 'Are you sure to export this chat to png?',
exportSuccess: 'Export Success',
exportFailed: 'Export Failed',
usingContext: 'Context Mode',
turnOnContext: 'In the current mode, sending messages will carry previous chat records.',
turnOffContext: 'In the current mode, sending messages will not carry previous chat records.',
deleteMessage: 'Delete Message',
deleteMessageConfirm: 'Are you sure to delete this message?',
deleteHistoryConfirm: 'Are you sure to clear this history?',

View File

@@ -27,6 +27,9 @@ export default {
exportImageConfirm: '是否将会话保存为图片?',
exportSuccess: '保存成功',
exportFailed: '保存失败',
usingContext: '上下文模式',
turnOnContext: '当前模式下, 发送消息会携带之前的聊天记录',
turnOffContext: '当前模式下, 发送消息不会携带之前的聊天记录',
deleteMessage: '删除消息',
deleteMessageConfirm: '是否删除此消息?',
deleteHistoryConfirm: '确定删除此记录?',

View File

@@ -27,6 +27,9 @@ export default {
exportImageConfirm: '是否將對話儲存為圖片?',
exportSuccess: '儲存成功',
exportFailed: '儲存失敗',
usingContext: '上下文模式',
turnOnContext: '在當前模式下, 發送訊息會攜帶之前的聊天記錄。',
turnOffContext: '在當前模式下, 發送訊息不會攜帶之前的聊天記錄。',
deleteMessage: '刪除訊息',
deleteMessageConfirm: '是否刪除此訊息?',
deleteHistoryConfirm: '確定刪除此紀錄?',

View File

@@ -38,7 +38,7 @@ const wrapClass = computed(() => {
'text-wrap',
'min-w-[20px]',
'rounded-md',
isMobile.value ? 'p-2' : 'p-3',
isMobile.value ? 'p-2' : 'px-3 py-2',
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
{ 'text-red-500': props.error },

View File

@@ -7,6 +7,7 @@ interface ScrollReturn {
scrollRef: Ref<ScrollElement>
scrollToBottom: () => Promise<void>
scrollToTop: () => Promise<void>
scrollToBottomIfAtBottom: () => Promise<void>
}
export function useScroll(): ScrollReturn {
@@ -24,9 +25,20 @@ export function useScroll(): ScrollReturn {
scrollRef.value.scrollTop = 0
}
const scrollToBottomIfAtBottom = async () => {
await nextTick()
if (scrollRef.value) {
const threshold = 50 // 阈值,表示滚动条到底部的距离阈值
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
if (distanceToBottom <= threshold)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
return {
scrollRef,
scrollToBottom,
scrollToTop,
scrollToBottomIfAtBottom,
}
}

View File

@@ -33,6 +33,8 @@ const conversationList = computed(() => dataSources.value.filter(item => (!item.
const prompt = ref<string>('')
const loading = ref<boolean>(false)
const usingContext = ref<boolean>(true)
const actionVisible = ref<boolean>(true)
function handleSubmit() {
onConversation()
@@ -68,7 +70,7 @@ async function onConversation() {
let options: Chat.ConversationRequest = {}
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
if (lastContext)
if (lastContext && usingContext.value)
options = { ...lastContext }
addChat(
@@ -120,7 +122,6 @@ async function onConversation() {
}
},
})
scrollToBottom()
}
catch (error: any) {
const errorMessage = error?.message ?? t('common.wrong')
@@ -284,7 +285,9 @@ function handleExport() {
try {
d.loading = true
const ele = document.getElementById('image-wrapper')
const canvas = await html2canvas(ele as HTMLDivElement)
const canvas = await html2canvas(ele as HTMLDivElement, {
useCORS: true,
})
const imgUrl = canvas.toDataURL('image/png')
const tempLink = document.createElement('a')
tempLink.style.display = 'none'
@@ -363,6 +366,24 @@ function handleStop() {
}
}
function toggleUsingContext() {
usingContext.value = !usingContext.value
if (usingContext.value)
ms.success(t('chat.turnOnContext'))
else
ms.warning(t('chat.turnOffContext'))
}
function onInputFocus() {
if (isMobile.value)
actionVisible.value = false
}
function onInputBlur() {
if (isMobile.value)
actionVisible.value = true
}
const placeholder = computed(() => {
if (isMobile.value)
return t('chat.placeholderMobile')
@@ -382,7 +403,7 @@ const wrapClass = computed(() => {
const footerClass = computed(() => {
let classes = ['p-4']
if (isMobile.value)
classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-4', 'overflow-hidden']
classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'overflow-hidden']
return classes
})
@@ -404,7 +425,11 @@ onUnmounted(() => {
ref="scrollRef"
class="h-full overflow-hidden overflow-y-auto"
>
<div id="image-wrapper" class="w-full max-w-screen-xl m-auto" :class="[isMobile ? 'p-2' : 'p-4']">
<div
id="image-wrapper"
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
:class="[isMobile ? 'p-2' : 'p-4']"
>
<template v-if="!dataSources.length">
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
@@ -440,21 +465,30 @@ onUnmounted(() => {
<footer :class="footerClass">
<div class="w-full max-w-screen-xl m-auto">
<div class="flex items-center justify-between space-x-2">
<HoverButton @click="handleClear">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" />
</span>
</HoverButton>
<HoverButton @click="handleExport">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
<div v-if="actionVisible" class="flex items-center space-x-2">
<HoverButton @click="handleClear">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" />
</span>
</HoverButton>
<HoverButton @click="handleExport">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
<HoverButton @click="toggleUsingContext">
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
<SvgIcon icon="ri:chat-history-line" />
</span>
</HoverButton>
</div>
<NInput
v-model:value="prompt"
type="textarea"
:autosize="{ minRows: 1, maxRows: 2 }"
:placeholder="placeholder"
@focus="onInputFocus"
@blur="onInputBlur"
@keypress="handleEnter"
/>
<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">