Compare commits

...

6 Commits

Author SHA1 Message Date
Rafi
55279def0d Add Dockerfile and workflow for static hosting image. 2023-04-11 15:32:06 +08:00
Rafi
fa14276d0a Fix: Resending message when the visibility of the browser page changes , which causes slowdown or failure to receive messages 2023-04-11 10:30:12 +08:00
Rafi
8718dc4ed1 using SERVER_DOMAIN at proxy target 2023-04-10 18:15:18 +08:00
Rafi
fe814acfd9 using http-proxy-middleware 2023-04-10 18:05:07 +08:00
Rafi
1e4f14c9b7 add user guide to readme 2023-04-07 19:39:14 +08:00
Rafi
137ca5ae1a Support Frugal Mode 2023-04-06 18:00:24 +08:00
17 changed files with 152 additions and 41 deletions

View File

@@ -1,4 +1,6 @@
node_modules node_modules
database.sqlite dist
.idea .idea
.output
.nuxt
.env .env

View File

@@ -0,0 +1,36 @@
name: Docker Image CI - static
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: static.Dockerfile
push: true
tags: wongsaang/chatgpt-ui-client:latest-static,wongsaang/chatgpt-ui-client:${{ github.ref_name }}-static

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@ node_modules
.env .env
.idea .idea
dist dist
database.sqlite

View File

@@ -4,6 +4,8 @@
[English](./README.md) | [中文](./docs/zh/README.md) [English](./README.md) | [中文](./docs/zh/README.md)
User guide: [https://wongsaang.github.io/chatgpt-ui-docs/](https://wongsaang.github.io/chatgpt-ui-docs/)
A ChatGPT web client that supports multiple users, multiple database connections for persistent data storage, supports i18n. Provides Docker images and quick deployment scripts. A ChatGPT web client that supports multiple users, multiple database connections for persistent data storage, supports i18n. Provides Docker images and quick deployment scripts.
The server of this project[https://github.com/WongSaang/chatgpt-ui-server](https://github.com/WongSaang/chatgpt-ui-server) The server of this project[https://github.com/WongSaang/chatgpt-ui-server](https://github.com/WongSaang/chatgpt-ui-server)

View File

@@ -7,6 +7,7 @@ const currentModel = useCurrentModel()
const openaiApiKey = useApiKey() const openaiApiKey = useApiKey()
const fetchingResponse = ref(false) const fetchingResponse = ref(false)
const messageQueue = [] const messageQueue = []
const frugalMode = ref(true)
let isProcessingQueue = false let isProcessingQueue = false
const props = defineProps({ const props = defineProps({
@@ -64,7 +65,8 @@ const fetchReply = async (message) => {
const data = Object.assign({}, currentModel.value, { const data = Object.assign({}, currentModel.value, {
openaiApiKey: enableCustomApiKey.value ? openaiApiKey.value : null, openaiApiKey: enableCustomApiKey.value ? openaiApiKey.value : null,
message: message, message: message,
conversationId: props.conversation.id conversationId: props.conversation.id,
frugalMode: frugalMode.value
}, webSearchParams) }, webSearchParams)
try { try {
@@ -76,6 +78,7 @@ const fetchReply = async (message) => {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
openWhenHidden: true,
onopen(response) { onopen(response) {
if (response.ok && response.headers.get('content-type') === EventStreamContentType) { if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
return; return;
@@ -259,11 +262,42 @@ onNuxtReady(() => {
<v-switch <v-switch
v-if="showWebSearchToggle" v-if="showWebSearchToggle"
v-model="enableWebSearch" v-model="enableWebSearch"
inline
hide-details hide-details
color="primary" color="primary"
:label="$t('webSearch')" :label="$t('webSearch')"
></v-switch> ></v-switch>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-switch
v-model="frugalMode"
inline
hide-details
color="primary"
:label="$t('frugalMode')"
></v-switch>
<v-dialog
transition="dialog-bottom-transition"
width="auto"
>
<template v-slot:activator="{ props }">
<v-icon
color="grey"
v-bind="props"
icon="help_outline"
></v-icon>
</template>
<template v-slot:default="{ isActive }">
<v-card>
<v-toolbar
color="primary"
:title="$t('frugalMode')"
></v-toolbar>
<v-card-text>
{{ $t('frugalModeTip') }}
</v-card-text>
</v-card>
</template>
</v-dialog>
</v-toolbar> </v-toolbar>
</div> </div>
</v-footer> </v-footer>

View File

@@ -51,7 +51,6 @@ const bindCopyCodeToButtons = () => {
} }
onMounted(() => { onMounted(() => {
console.log('mounted')
bindCopyCodeToButtons() bindCopyCodeToButtons()
}) })

View File

@@ -1,8 +1,7 @@
export const useMyFetch = (url, options = {}) => { export const useMyFetch = (url, options = {}) => {
let defaultOptions = { let defaultOptions = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json'
'Content-Type': 'application/json',
} }
} }
if (process.server) { if (process.server) {

View File

@@ -4,6 +4,8 @@
[English](../../README.md) | [中文](./docs/zh/README.md) [English](../../README.md) | [中文](./docs/zh/README.md)
用户指南: [https://wongsaang.github.io/chatgpt-ui-docs/zh/](https://wongsaang.github.io/chatgpt-ui-docs/zh/)
ChatGPT Web 客户端,支持多用户,支持 Mysql、PostgreSQL 等多种数据库连接进行数据持久化存储,支持多语言。提供 Docker 镜像和快速部署脚本。 ChatGPT Web 客户端,支持多用户,支持 Mysql、PostgreSQL 等多种数据库连接进行数据持久化存储,支持多语言。提供 Docker 镜像和快速部署脚本。
本项目的服务端:[https://github.com/WongSaang/chatgpt-ui-server](https://github.com/WongSaang/chatgpt-ui-server) 本项目的服务端:[https://github.com/WongSaang/chatgpt-ui-server](https://github.com/WongSaang/chatgpt-ui-server)

View File

@@ -52,6 +52,8 @@
"genTitlePrompt": "Generate a short title for the following content, no more than 10 words. \n\nContent: ", "genTitlePrompt": "Generate a short title for the following content, no more than 10 words. \n\nContent: ",
"maxTokenTips1": "The maximum context length of the current model is", "maxTokenTips1": "The maximum context length of the current model is",
"maxTokenTips2": "token, which includes the length of the prompt and the length of the generated text. The `Max Tokens` here refers to the length of the generated text. Therefore, you should leave some space for your prompt and not set it too large or to the maximum.", "maxTokenTips2": "token, which includes the length of the prompt and the length of the generated text. The `Max Tokens` here refers to the length of the generated text. Therefore, you should leave some space for your prompt and not set it too large or to the maximum.",
"frugalMode": "Frugal mode",
"frugalModeTip": "Activate frugal mode, the client will not send historical messages to ChatGPT, which can save token consumption. If you want ChatGPT to understand the context of the conversation, please turn off frugal mode.",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.", "introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.",
"introduction2": "You will need an OpenAI API Key before you can use this client.", "introduction2": "You will need an OpenAI API Key before you can use this client.",

View File

@@ -52,6 +52,8 @@
"genTitlePrompt": "Придумайте короткий заголовок для следующего содержания, не более 10 слов. \n\nСодержание: ", "genTitlePrompt": "Придумайте короткий заголовок для следующего содержания, не более 10 слов. \n\nСодержание: ",
"maxTokenTips1": "The maximum context length of the current model is", "maxTokenTips1": "The maximum context length of the current model is",
"maxTokenTips2": "token, which includes the length of the prompt and the length of the generated text. The `Max Tokens` here refers to the length of the generated text. Therefore, you should leave some space for your prompt and not set it too large or to the maximum.", "maxTokenTips2": "token, which includes the length of the prompt and the length of the generated text. The `Max Tokens` here refers to the length of the generated text. Therefore, you should leave some space for your prompt and not set it too large or to the maximum.",
"frugalMode": "Frugal mode",
"frugalModeTip": "Activate frugal mode, the client will not send historical messages to ChatGPT, which can save token consumption. If you want ChatGPT to understand the context of the conversation, please turn off frugal mode.",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "является неофициальным клиентом для ChatGPT, но использует официальный API OpenAI.", "introduction1": "является неофициальным клиентом для ChatGPT, но использует официальный API OpenAI.",
"introduction2": "Вам понадобится ключ API OpenAI, прежде чем вы сможете использовать этот клиент.", "introduction2": "Вам понадобится ключ API OpenAI, прежде чем вы сможете использовать этот клиент.",

View File

@@ -52,6 +52,8 @@
"genTitlePrompt": "为以下内容生成一个不超过10个字的简短标题。 \n\n内容: ", "genTitlePrompt": "为以下内容生成一个不超过10个字的简短标题。 \n\n内容: ",
"maxTokenTips1": "当前模型的最大上下文长度为", "maxTokenTips1": "当前模型的最大上下文长度为",
"maxTokenTips2": "个 token它包括了指令的长度和生成的文本长度。此处的最大 token 数量是指生成的文本长度。所以您应该为您的指令预留一些空间,不宜设置过大或拉满。", "maxTokenTips2": "个 token它包括了指令的长度和生成的文本长度。此处的最大 token 数量是指生成的文本长度。所以您应该为您的指令预留一些空间,不宜设置过大或拉满。",
"frugalMode": "节俭模式",
"frugalModeTip": "开启节俭模式客户端不会把历史消息发送给ChatGPT可以节省 token 的消耗。如果你想让 ChatGPT 了解对话的上下文,请关闭节俭模式。",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API", "introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API",
"introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。", "introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。",

View File

@@ -2,9 +2,9 @@ server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name localhost; server_name localhost;
root /app;
location / { location / {
root /app;
index index.html; index index.html;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;

View File

@@ -1,9 +1,8 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
const appName = process.env.NUXT_PUBLIC_APP_NAME ?? 'ChatGPT UI' const appName = process.env.NUXT_PUBLIC_APP_NAME ?? 'ChatGPT UI'
export default defineNuxtConfig({ export default defineNuxtConfig({
debug: process.env.NODE_ENV !== 'production', debug: process.env.NODE_ENV !== 'production',
ssr: true, ssr: process.env.SSR !== 'false',
app: { app: {
head: { head: {
title: appName, title: appName,

View File

@@ -18,6 +18,7 @@
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"http-proxy-middleware": "3.0.0-beta.1",
"is-mobile": "^3.1.1", "is-mobile": "^3.1.1",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",

View File

@@ -1,33 +1,14 @@
import { createProxyMiddleware } from 'http-proxy-middleware'
const PayloadMethods = new Set(["PATCH", "POST", "PUT", "DELETE"]);
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
// @ts-ignore await new Promise((resolve, reject) => {
if (event.node.req.url.startsWith('/api/')) { createProxyMiddleware({
// TODO: fix fetch failed target: process.env.SERVER_DOMAIN,
const target = (process.env.SERVER_DOMAIN || 'http://localhost:8000') + event.node.req.url pathFilter: '/api',
// Method })(event.node.req, event.node.res, (err) => {
const method = getMethod(event) if (err)
// Body reject(err)
let body; else
if (PayloadMethods.has(method)) { resolve(true)
body = await readRawBody(event).catch(() => undefined); })
} })
// Headers
const headers = getProxyRequestHeaders(event);
if (method === 'DELETE') {
delete headers['content-length']
}
return sendProxy(event, target, {
sendStream: event.node.req.url === '/api/conversation/',
fetchOptions: {
headers,
method,
body,
},
});
}
}) })

22
static.Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM node:18-alpine3.16 as builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN rm -r server && SSR=false yarn generate
FROM nginx:1.22-alpine
WORKDIR /app
COPY --from=builder /app/.output/public .
COPY nginx.conf /etc/nginx/templates/default.conf.template
EXPOSE 80

View File

@@ -848,6 +848,18 @@
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
"@types/http-proxy@^1.17.10":
version "1.17.10"
resolved "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.10.tgz#e576c8e4a0cc5c6a138819025a88e167ebb38d6c"
integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==
dependencies:
"@types/node" "*"
"@types/node@*":
version "18.15.11"
resolved "https://registry.npmmirror.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
"@types/resolve@1.20.2": "@types/resolve@1.20.2":
version "1.20.2" version "1.20.2"
resolved "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" resolved "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
@@ -2363,6 +2375,18 @@ http-errors@2.0.0:
statuses "2.0.1" statuses "2.0.1"
toidentifier "1.0.1" toidentifier "1.0.1"
http-proxy-middleware@3.0.0-beta.1:
version "3.0.0-beta.1"
resolved "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-3.0.0-beta.1.tgz#aa5800c01d3cf340eeff89bb2de381ce67a8385f"
integrity sha512-hdiTlVVoaxncf239csnEpG5ew2lRWnoNR1PMWOO6kYulSphlrfLs5JFZtFVH3R5EUWSZNMkeUqvkvfctuWaK8A==
dependencies:
"@types/http-proxy" "^1.17.10"
debug "^4.3.4"
http-proxy "^1.18.1"
is-glob "^4.0.1"
is-plain-obj "^3.0.0"
micromatch "^4.0.5"
http-proxy@^1.18.1: http-proxy@^1.18.1:
version "1.18.1" version "1.18.1"
resolved "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" resolved "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
@@ -2549,6 +2573,11 @@ is-number@^7.0.0:
resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-plain-obj@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
is-primitive@^3.0.1: is-primitive@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.npmmirror.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" resolved "https://registry.npmmirror.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05"
@@ -2884,7 +2913,7 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.2, micromatch@^4.0.4: micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5" version "4.0.5"
resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==