Compare commits

..

17 Commits

Author SHA1 Message Date
Rafi
323f10844b Modify the placeholder of the default prompt for web search to solve the problem of not providing web search results to ChatGPT 2023-03-24 11:22:51 +08:00
Rafi
ee035390db add default prompt to web search 2023-03-23 18:48:08 +08:00
Wong Saang
be743bf799 Update README.md 2023-03-23 17:13:34 +08:00
Wong Saang
a59f84f2bf Update README.md 2023-03-23 17:12:59 +08:00
Rafi
ed0cf2997d update readme 2023-03-23 17:07:49 +08:00
Rafi
7f00c74097 Added wsgi-server environment variable EMAIL_FROM to docker-compose.yml and readme 2023-03-23 15:31:26 +08:00
Rafi
f007417fa4 add update info to readme 2023-03-23 12:53:08 +08:00
Rafi
27c5e2a3ac Get settings from backend, added web search functionality 2023-03-23 11:45:56 +08:00
Rafi
e90dc0c12b web_search toolbar 2023-03-22 23:29:58 +08:00
Rafi
837fd8c9ff update readme 2023-03-22 17:26:22 +08:00
Rafi
ce0b1004f3 Remove the parent_message_id constraint 2023-03-22 16:17:46 +08:00
Rafi
1ff1c46e37 Fix the bug of being unable to delete messages. 2023-03-22 15:55:06 +08:00
Rafi
afa3e499dc add DEBUT_PWA env variable 2023-03-22 14:12:49 +08:00
Rafi
70ce5746bc Merge remote-tracking branch 'origin/main' into main
# Conflicts:
#	nuxt.config.ts
#	yarn.lock
2023-03-22 13:50:53 +08:00
Rafi
8bbc44e7bf update nuxt.config.ts 2023-03-21 18:48:35 +08:00
Rafi
3dcb4be6e4 add robots.txt 2023-03-21 18:06:44 +08:00
Rafi
83f8072625 mv @vite-pwa/nuxt to devDependencies 2023-03-21 13:46:02 +08:00
18 changed files with 2152 additions and 84 deletions

View File

@@ -1,14 +1,22 @@
<p align="center">
<img alt="demo" src="./demos/demo.gif?v=1">
</p>
<div align="center">
<h1>ChatGPT UI</h1>
</div>
[English](./README.md) | [中文](./docs/zh/README.md)
# ChatGPT UI
A ChatGPT web client that supports multiple users, multiple database connections for persistent data storage, supports i18n. Provides Docker images and quick deployment scripts.
https://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
## 📢Updates
<details open>
<summary><strong>2023-03-23</strong></summary>
Added web search capability to generate more relevant and up-to-date answers from ChatGPT!
This feature is off by default, you can turn it on in `Chat->Settings` in the admin panel, there is a record `open_web_search` in Settings, set its value to True.
</details>
<details open>
<summary><strong>2023-03-15</strong></summary>
@@ -115,6 +123,7 @@ services:
# - EMAIL_HOST_USER=
# - EMAIL_HOST_PASSWORD=
# - EMAIL_USE_TLS=True
# - EMAIL_FROM=no-reply@example.com #Default sender email address
ports:
- '8000:8000'
networks:
@@ -156,6 +165,16 @@ Before you can start chatting, you need to add an OpenAI API key. In the Setting
Now you can access the web client at `http(s)://your.domain` or `http://123.123.123.123` to start chatting.
## Donation
> If it is helpful to you, it is also helping me.
If you want to support me, Buy me a coffee ❤️ [https://www.buymeacoffee.com/WongSaang](https://www.buymeacoffee.com/WongSaang)
<p align="center">
<img height="150" src="https://github.com/WongSaang/chatgpt-ui/blob/main/demos/bmc_qr.png?raw=true"/>
</p>
## Development
### Setup
@@ -181,4 +200,4 @@ Build the application for production:
```bash
yarn build
```
```

View File

@@ -41,7 +41,7 @@ const deleteMessage = async () => {
method: 'DELETE'
})
if (!error.value) {
this.$emit('deleteMessage', props.messageIndex)
props.deleteMessage(props.messageIndex)
showSnackbar('Deleted!')
}
showSnackbar('Delete failed')

View File

@@ -12,14 +12,19 @@ const md = new MarkdownIt({
},
})
const props = defineProps(['content'])
const props = defineProps({
message: {
type: Object,
required: true
}
})
const contentHtml = ref('')
const contentElm = ref(null)
watchEffect(() => {
contentHtml.value = props.content ? md.render(props.content) : ''
contentHtml.value = props.message.message ? md.render(props.message.message) : ''
})
const bindCopyCodeToButtons = () => {
@@ -54,11 +59,19 @@ onUpdated(() => {
</script>
<template>
<div
ref="contentElm"
v-html="contentHtml"
class="chat-msg-content"
></div>
<v-card
:color="message.is_bot ? '' : 'primary'"
rounded="lg"
elevation="2"
>
<v-card-text>
<div
ref="contentElm"
v-html="contentHtml"
class="chat-msg-content"
></div>
</v-card-text>
</v-card>
</template>
<style>

View File

@@ -96,10 +96,12 @@ onMounted( () => {
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon="speaker_notes"
title="Common prompts"
class="mr-3"
></v-btn>
icon
>
<v-icon
icon="speaker_notes"
></v-icon>
</v-btn>
</template>
<v-container>

View File

@@ -7,4 +7,6 @@ export const useApiKey = () => useState('apiKey', () => getStoredApiKey())
export const useConversion = () => useState('conversion', () => getDefaultConversionData())
export const useConversions = () => useState('conversions', () => [])
export const useConversions = () => useState('conversions', () => [])
export const useSettings = () => useState('settings', () => {})

BIN
demos/bmc_qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
demos/demo.mp4 Normal file

Binary file not shown.

View File

@@ -29,6 +29,7 @@ services:
# - EMAIL_HOST_USER=
# - EMAIL_HOST_PASSWORD=
# - EMAIL_USE_TLS=True
# - EMAIL_FROM=no-reply@example.com #Default sender email address
ports:
- '${WSGI_PORT:-8000}:8000'
networks:

View File

@@ -1,14 +1,21 @@
<p align="center">
<img alt="demo" src="../../demos/demo.gif?v=1">
</p>
<div align="center">
<h1>ChatGPT UI</h1>
</div>
[English](../../README.md) | [中文](./docs/zh/README.md)
# ChatGPT UI
ChatGPT Web 客户端,支持多用户,支持 Mysql、PostgreSQL 等多种数据库连接进行数据持久化存储,支持多语言。提供 Docker 镜像和快速部署脚本。
https://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
## 📢 更新
<details open>
<summary><strong>2023-03-23</strong></summary>
增加网页搜索能力,使得 ChatGPT 生成的回答更与时俱进!
该功能默认处于关闭状态,你可以在管理后台的 `Chat->Settings` 中开启它,在 Settings 中有一个 `open_web_search` 的记录,把它的值设置为 True。
</details>
<details open>
<summary><strong>2023-03-15</strong></summary>
@@ -113,6 +120,7 @@ services:
# - EMAIL_HOST_USER=
# - EMAIL_HOST_PASSWORD=
# - EMAIL_USE_TLS=True
# - EMAIL_FROM=no-reply@example.com #默认发件邮箱地址
ports:
- '8000:8000'
networks:
@@ -154,6 +162,16 @@ networks:
现在可以访问客户端地址 `http(s)://your.domain` / `http://123.123.123.123` 开始聊天。
## 续杯咖啡
> 如果对您有帮助,也是在帮助我自己.
如果你想支持我,给我续杯咖啡吧 ❤️ [https://www.buymeacoffee.com/WongSaang](https://www.buymeacoffee.com/WongSaang)
<p align="center">
<img height="150" src="https://github.com/WongSaang/chatgpt-ui/blob/main/demos/bmc_qr.png?raw=true"/>
</p>
## Development
### Setup
@@ -179,4 +197,4 @@ Build the application for production:
```bash
yarn build
```
```

View File

@@ -34,6 +34,8 @@
"copied": "Copied",
"delete": "Delete",
"signOut": "Sign out",
"webSearch": "Web Search",
"webSearchDefaultPrompt": "Web search results:\n\n[web_results]\nCurrent date: [current_date]\n\nInstructions: Using the provided web search results, write a comprehensive reply to the given query. Make sure to cite results using [[number](URL)] notation after the reference. If the provided search results refer to multiple subjects with the same name, write separate answers for each subject.\nQuery: [query]",
"welcomeScreen": {
"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.",

View File

@@ -34,6 +34,8 @@
"copied": "已复制",
"delete": "删除",
"signOut": "退出登录",
"webSearch": "网页搜索",
"webSearchDefaultPrompt": "网络搜索结果:\n\n[web_results]\n当前日期[current_date]\n\n说明使用提供的网络搜索结果对给定的查询写出全面的回复。确保在引用参考文献后使用 [[number](URL)] 符号进行引用结果. 如果提供的搜索结果涉及到多个具有相同名称的主题,请针对每个主题编写单独的答案。\n查询[query]",
"welcomeScreen": {
"introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API",
"introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。",

View File

@@ -84,6 +84,7 @@ const loadConversations = async () => {
const {mdAndUp} = useDisplay()
const drawerPermanent = computed(() => {
return mdAndUp.value
})
@@ -97,8 +98,9 @@ const signOut = async () => {
}
}
onNuxtReady(async () => {
onMounted(async () => {
loadConversations()
loadSettings()
})
</script>

View File

@@ -36,7 +36,7 @@ export default defineNuxtConfig({
description: 'A ChatGPT web Client'
},
workbox: {
enabled: true
enabled: process.env.DEBUT_PWA === 'true',
}
},
i18n: {

View File

@@ -11,6 +11,7 @@
"@kevinmarrec/nuxt-pwa": "^0.17.0",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/i18n": "^8.0.0-beta.9",
"@vite-pwa/nuxt": "^0.0.7",
"material-design-icons-iconfont": "^6.7.0",
"nuxt": "^3.2.0"
},

View File

@@ -5,8 +5,6 @@ definePageMeta({
middleware: ["auth"]
})
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
import { nextTick } from 'vue'
import MessageActions from "~/components/MessageActions.vue";
const { $i18n, $auth } = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
@@ -50,15 +48,22 @@ const abortFetch = () => {
}
fetchingResponse.value = false
}
const fetchReply = async (message, parentMessageId) => {
const fetchReply = async (message) => {
ctrl = new AbortController()
let webSearchParams = {}
if (enableWebSearch.value) {
webSearchParams['web_search'] = {
ua: navigator.userAgent,
default_prompt: $i18n.t('webSearchDefaultPrompt')
}
}
const data = Object.assign({}, currentModel.value, {
openaiApiKey: openaiApiKey.value,
message: message,
parentMessageId: parentMessageId,
conversationId: currentConversation.value.id
})
}, webSearchParams)
try {
await fetchEventSource('/api/conversation/', {
@@ -93,6 +98,11 @@ const fetchReply = async (message, parentMessageId) => {
throw new Error(data.error);
}
if (event === 'userMessageId') {
currentConversation.value.messages[currentConversation.value.messages.length - 1].id = data.userMessageId
return;
}
if (event === 'done') {
if (currentConversation.value.id === null) {
currentConversation.value.id = data.conversationId
@@ -129,15 +139,8 @@ const scrollChatWindow = () => {
const send = (message) => {
fetchingResponse.value = true
let parentMessageId = null
if (currentConversation.value.messages.length > 0) {
const lastMessage = currentConversation.value.messages[currentConversation.value.messages.length - 1]
if (lastMessage.is_bot && lastMessage.id !== null) {
parentMessageId = lastMessage.id
}
}
currentConversation.value.messages.push({parentMessageId: parentMessageId, message: message})
fetchReply(message, parentMessageId)
currentConversation.value.messages.push({message: message})
fetchReply(message)
scrollChatWindow()
}
const stop = () => {
@@ -160,6 +163,18 @@ const deleteMessage = (index) => {
currentConversation.value.messages.splice(index, 1)
}
const showWebSearchToggle = ref(false)
const enableWebSearch = ref(false)
const settings = useSettings()
watchEffect(() => {
if (settings.value) {
const settingsValue = toRaw(settings.value)
showWebSearchToggle.value = settingsValue.open_web_search && settingsValue.open_web_search === 'True'
}
})
</script>
<template>
@@ -184,15 +199,7 @@ const deleteMessage = (index) => {
:use-prompt="usePrompt"
:delete-message="deleteMessage"
/>
<v-card
:color="message.is_bot ? '' : 'primary'"
rounded="lg"
elevation="2"
>
<v-card-text>
<MsgContent :content="message.message" />
</v-card-text>
</v-card>
<MsgContent :message="message" />
<MessageActions
v-if="message.is_bot"
:message="message"
@@ -208,21 +215,36 @@ const deleteMessage = (index) => {
<div ref="grab" class="w-100" style="height: 200px;"></div>
</div>
<Welcome v-else />
<v-footer app class="d-flex flex-column">
<div class="px-md-16 w-100 d-flex align-center">
<Prompt v-show="!fetchingResponse" :use-prompt="usePrompt" />
<v-btn
v-show="fetchingResponse"
icon="close"
title="stop"
class="mr-3"
@click="stop"
></v-btn>
<MsgEditor ref="editor" :send-message="send" :disabled="fetchingResponse" :loading="fetchingResponse" />
</div>
<v-footer app>
<div class="px-md-16 w-100 d-flex flex-column">
<div class="d-flex align-center">
<v-btn
v-show="fetchingResponse"
icon="close"
title="stop"
class="mr-3"
@click="stop"
></v-btn>
<MsgEditor ref="editor" :send-message="send" :disabled="fetchingResponse" :loading="fetchingResponse" />
</div>
<v-toolbar
density="comfortable"
color="transparent"
>
<Prompt v-show="!fetchingResponse" :use-prompt="usePrompt" />
<v-switch
v-if="showWebSearchToggle"
v-model="enableWebSearch"
hide-details
color="primary"
:label="$t('webSearch')"
></v-switch>
<v-spacer></v-spacer>
</v-toolbar>
<div class="px-4 py-2 text-disabled text-caption font-weight-light text-center w-100">
© {{ new Date().getFullYear() }} {{ runtimeConfig.public.appName }}
<!-- <div class="py-2 text-disabled text-caption font-weight-light text-center">-->
<!-- © {{ new Date().getFullYear() }} {{ runtimeConfig.public.appName }}-->
<!-- </div>-->
</div>
</v-footer>
<v-snackbar

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Allow: /

View File

@@ -50,4 +50,23 @@ export const genTitle = async (conversationId) => {
return data.value.title
}
return null
}
const transformData = (list) => {
const result = {};
for (let i = 0; i < list.length; i++) {
const item = list[i];
result[item.name] = item.value;
}
return result;
}
export const loadSettings = async () => {
const settings = useSettings()
const { data, error } = await useAuthFetch('/api/chat/settings/', {
method: 'GET'
})
if (!error.value) {
settings.value = transformData(data.value)
}
}

2003
yarn.lock

File diff suppressed because it is too large Load Diff