Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
288c9eeeca | ||
|
|
4d09ff7c8a | ||
|
|
5fa059017c | ||
|
|
323f10844b | ||
|
|
ee035390db | ||
|
|
be743bf799 | ||
|
|
a59f84f2bf | ||
|
|
ed0cf2997d | ||
|
|
7f00c74097 | ||
|
|
f007417fa4 | ||
|
|
27c5e2a3ac | ||
|
|
e90dc0c12b | ||
|
|
837fd8c9ff |
31
README.md
31
README.md
@@ -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
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
BIN
demos/bmc_qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
demos/demo.mp4
Normal file
BIN
demos/demo.mp4
Normal file
Binary file not shown.
@@ -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:
|
||||
|
||||
@@ -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
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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密钥。",
|
||||
|
||||
@@ -84,6 +84,7 @@ const loadConversations = async () => {
|
||||
|
||||
const {mdAndUp} = useDisplay()
|
||||
|
||||
|
||||
const drawerPermanent = computed(() => {
|
||||
return mdAndUp.value
|
||||
})
|
||||
@@ -97,8 +98,18 @@ const signOut = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
onNuxtReady(async () => {
|
||||
const settings = useSettings()
|
||||
const showApiKeySetting = ref(false)
|
||||
watchEffect(() => {
|
||||
if (settings.value) {
|
||||
const settingsValue = toRaw(settings.value)
|
||||
showApiKeySetting.value = settingsValue.open_api_key_setting && settingsValue.open_api_key_setting === 'True'
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
loadConversations()
|
||||
loadSettings()
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -237,6 +248,10 @@ onNuxtReady(async () => {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<ApiKeyDialog
|
||||
v-if="showApiKeySetting"
|
||||
/>
|
||||
|
||||
<ModelParameters/>
|
||||
|
||||
<v-menu
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
const appName = 'ChatGPT UI'
|
||||
const appName = process.env.NUXT_PUBLIC_APP_NAME ?? 'ChatGPT UI'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
dev: false,
|
||||
@@ -14,6 +14,7 @@ export default defineNuxtConfig({
|
||||
appName: appName,
|
||||
typewriter: false,
|
||||
typewriterDelay: 50,
|
||||
customApiKey: false
|
||||
}
|
||||
},
|
||||
build: {
|
||||
|
||||
@@ -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()
|
||||
@@ -53,11 +51,19 @@ const abortFetch = () => {
|
||||
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,
|
||||
openaiApiKey: enableCustomApiKey.value ? openaiApiKey.value : null,
|
||||
message: message,
|
||||
conversationId: currentConversation.value.id
|
||||
})
|
||||
}, webSearchParams)
|
||||
|
||||
try {
|
||||
await fetchEventSource('/api/conversation/', {
|
||||
@@ -157,6 +163,20 @@ const deleteMessage = (index) => {
|
||||
currentConversation.value.messages.splice(index, 1)
|
||||
}
|
||||
|
||||
const showWebSearchToggle = ref(false)
|
||||
const enableWebSearch = ref(false)
|
||||
const enableCustomApiKey = 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'
|
||||
enableCustomApiKey.value = settingsValue.open_api_key_setting && settingsValue.open_api_key_setting === 'True'
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -197,21 +217,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user