Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa14276d0a | ||
|
|
8718dc4ed1 | ||
|
|
fe814acfd9 | ||
|
|
1e4f14c9b7 | ||
|
|
137ca5ae1a | ||
|
|
8a9b705b99 | ||
|
|
82c1811034 | ||
|
|
0d6aef6872 |
12
README.md
12
README.md
@@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
[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)
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
|
https://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
|
||||||
|
|
||||||
|
|
||||||
## 📢Updates
|
## 📢Updates
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary><strong>2023-04-06</strong></summary>
|
||||||
|
The client is now deployed as server-side rendering (SSR), and the environment variables are now available, see docker-compose configuration below for available environment variables. Improved first screen loading speed and reduced white screen time.
|
||||||
|
</details>
|
||||||
|
|
||||||
<details open>
|
<details open>
|
||||||
<summary><strong>2023-03-27</strong></summary>
|
<summary><strong>2023-03-27</strong></summary>
|
||||||
🚀 Support gpt-4 model. You can select the model in the "Model Parameters" of the front-end.
|
🚀 Support gpt-4 model. You can select the model in the "Model Parameters" of the front-end.
|
||||||
@@ -95,6 +104,9 @@ services:
|
|||||||
image: wongsaang/chatgpt-ui-client:latest
|
image: wongsaang/chatgpt-ui-client:latest
|
||||||
environment:
|
environment:
|
||||||
- SERVER_DOMAIN=http://backend-web-server
|
- SERVER_DOMAIN=http://backend-web-server
|
||||||
|
# - NUXT_PUBLIC_APP_NAME='ChatGPT UI' # The name of the application
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER=true # Whether to enable the typewriter effect, default false
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER_DELAY=50 # The delay time of the typewriter effect, default 50ms
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend-web-server
|
- backend-web-server
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
9
app.vue
9
app.vue
@@ -1,3 +1,12 @@
|
|||||||
|
<script setup>
|
||||||
|
onNuxtReady(() => {
|
||||||
|
fetchSystemSettings()
|
||||||
|
// api key
|
||||||
|
const apiKey = useApiKey()
|
||||||
|
apiKey.value = getStoredApiKey()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
<NuxtLoadingIndicator />
|
<NuxtLoadingIndicator />
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -166,18 +169,20 @@ const deleteMessage = (index) => {
|
|||||||
props.conversation.messages.splice(index, 1)
|
props.conversation.messages.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const showWebSearchToggle = ref(false)
|
|
||||||
const enableWebSearch = ref(false)
|
|
||||||
const enableCustomApiKey = ref(false)
|
|
||||||
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
const enableWebSearch = ref(false)
|
||||||
|
|
||||||
watchEffect(() => {
|
const showWebSearchToggle = computed(() => {
|
||||||
if (settings.value) {
|
return settings.value && settings.value.open_web_search && settings.value.open_web_search === 'True'
|
||||||
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'
|
const enableCustomApiKey = computed(() => {
|
||||||
}
|
return settings.value && settings.value.open_api_key_setting && settings.value.open_api_key_setting === 'True'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
onNuxtReady(() => {
|
||||||
|
currentModel.value = getCurrentModel()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -257,11 +262,42 @@ watchEffect(() => {
|
|||||||
<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>
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ const availableModels = [
|
|||||||
]
|
]
|
||||||
const currentModelDefault = ref(MODELS[currentModel.value.name])
|
const currentModelDefault = ref(MODELS[currentModel.value.name])
|
||||||
|
|
||||||
watch(currentModel, (newVal, oldVal) => {
|
onNuxtReady(() => {
|
||||||
currentModelDefault.value = MODELS[newVal.name]
|
currentModel.value = getCurrentModel()
|
||||||
saveCurrentModel(newVal)
|
watch(currentModel, (newVal, oldVal) => {
|
||||||
}, { deep: true })
|
currentModelDefault.value = MODELS[newVal.name]
|
||||||
|
saveCurrentModel(newVal)
|
||||||
|
}, { deep: true })
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-list-subheader>{{ $t('temperature') }}</v-list-subheader>
|
<v-list-subheader>{{ $t('temperature') }}</v-list-subheader>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="currentModel.temperature"
|
v-model.number="currentModel.temperature"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -82,7 +85,7 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-list-subheader>{{ $t('maxTokens') }}</v-list-subheader>
|
<v-list-subheader>{{ $t('maxTokens') }}</v-list-subheader>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="currentModel.max_tokens"
|
v-model.number="currentModel.max_tokens"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -93,6 +96,9 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
class="flex-grow-0"
|
class="flex-grow-0"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-caption">
|
||||||
|
{{ $t('maxTokenTips1') }} <b>{{ currentModelDefault.total_tokens }}</b> {{ $t('maxTokenTips2') }}
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-slider
|
<v-slider
|
||||||
@@ -111,7 +117,7 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-list-subheader>{{ $t('topP') }}</v-list-subheader>
|
<v-list-subheader>{{ $t('topP') }}</v-list-subheader>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="currentModel.top_p"
|
v-model.number="currentModel.top_p"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -138,7 +144,7 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-list-subheader>{{ $t('frequencyPenalty') }}</v-list-subheader>
|
<v-list-subheader>{{ $t('frequencyPenalty') }}</v-list-subheader>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="currentModel.frequency_penalty"
|
v-model.number="currentModel.frequency_penalty"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -164,7 +170,7 @@ watch(currentModel, (newVal, oldVal) => {
|
|||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-list-subheader>{{ $t('presencePenalty') }}</v-list-subheader>
|
<v-list-subheader>{{ $t('presencePenalty') }}</v-list-subheader>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="currentModel.presence_penalty"
|
v-model.number="currentModel.presence_penalty"
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
density="compact"
|
density="compact"
|
||||||
@@ -51,7 +51,6 @@ const bindCopyCodeToButtons = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('mounted')
|
|
||||||
bindCopyCodeToButtons()
|
bindCopyCodeToButtons()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,8 @@ const loadConversations = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const showApiKeySetting = ref(false)
|
const showApiKeySetting = computed(() => {
|
||||||
watchEffect(() => {
|
return settings.value && settings.value.open_api_key_setting && settings.value.open_api_key_setting === 'True'
|
||||||
if (settings.value) {
|
|
||||||
const settingsValue = toRaw(settings.value)
|
|
||||||
showApiKeySetting.value = settingsValue.open_api_key_setting && settingsValue.open_api_key_setting === 'True'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const signOut = async () => {
|
const signOut = async () => {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const useApiKey = () => useState('apiKey', () => getStoredApiKey())
|
|||||||
|
|
||||||
export const useConversations = () => useState('conversations', () => [])
|
export const useConversations = () => useState('conversations', () => [])
|
||||||
|
|
||||||
export const useSettings = () => useState('settings', () => getSystemSettings())
|
export const useSettings = () => useState('settings', () => {})
|
||||||
|
|
||||||
export const useUser = () => useState('user', () => null)
|
export const useUser = () => useState('user', () => null)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ services:
|
|||||||
image: wongsaang/chatgpt-ui-client:latest
|
image: wongsaang/chatgpt-ui-client:latest
|
||||||
environment:
|
environment:
|
||||||
- SERVER_DOMAIN=http://backend-web-server
|
- SERVER_DOMAIN=http://backend-web-server
|
||||||
|
# - NUXT_PUBLIC_APP_NAME='ChatGPT UI' # The name of the application
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER=true # Whether to enable the typewriter effect, default false
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER_DELAY=50 # The delay time of the typewriter effect, default 50ms
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend-web-server
|
- backend-web-server
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
[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://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
|
https://user-images.githubusercontent.com/46235412/227156264-ca17ab17-999b-414f-ab06-3f75b5235bfe.mp4
|
||||||
|
|
||||||
|
|
||||||
## 📢 更新
|
## 📢 更新
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary><strong>2023-04-06</strong></summary>
|
||||||
|
客户端改成服务端渲染(SSR)的方式部署,现在可以使用环境变量了,可用环境变量请看下方 docker-compose 配置。提升了首屏加载速度,减少白屏时间。
|
||||||
|
</details>
|
||||||
|
|
||||||
<details open>
|
<details open>
|
||||||
<summary><strong>2023-03-27</strong></summary>
|
<summary><strong>2023-03-27</strong></summary>
|
||||||
🚀 支持 gpt-4 模型。你可以在前端的“模型参数”中选择模型,gpt-4 模型需要通过 openai 的白名单才能使用。
|
🚀 支持 gpt-4 模型。你可以在前端的“模型参数”中选择模型,gpt-4 模型需要通过 openai 的白名单才能使用。
|
||||||
@@ -91,6 +100,9 @@ services:
|
|||||||
image: wongsaang/chatgpt-ui-client:latest
|
image: wongsaang/chatgpt-ui-client:latest
|
||||||
environment:
|
environment:
|
||||||
- SERVER_DOMAIN=http://backend-web-server
|
- SERVER_DOMAIN=http://backend-web-server
|
||||||
|
# - NUXT_PUBLIC_APP_NAME='ChatGPT UI' # APP 名称
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER=true # 是否开启 打字机 效果
|
||||||
|
# - NUXT_PUBLIC_TYPEWRITER_DELAY=50 # 打字机效果的延迟时间,单位:毫秒,默认:50
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend-web-server
|
- backend-web-server
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -50,6 +50,10 @@
|
|||||||
"webSearch": "Web Search",
|
"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]",
|
"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]",
|
||||||
"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",
|
||||||
|
"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.",
|
||||||
|
|||||||
@@ -50,6 +50,10 @@
|
|||||||
"webSearch": "Поиск в интернете",
|
"webSearch": "Поиск в интернете",
|
||||||
"webSearchDefaultPrompt": "Результаты веб-поиска:\n\n[web_results]\nТекущая дата: [current_date]\n\nИнструкции: Используя предоставленные результаты веб-поиска, напишите развернутый ответ на заданный запрос. Обязательно цитируйте результаты, используя обозначение [[number](URL)] после ссылки. Если предоставленные результаты поиска относятся к нескольким темам с одинаковым названием, напишите отдельные ответы для каждой темы.\nЗапрос: [query]",
|
"webSearchDefaultPrompt": "Результаты веб-поиска:\n\n[web_results]\nТекущая дата: [current_date]\n\nИнструкции: Используя предоставленные результаты веб-поиска, напишите развернутый ответ на заданный запрос. Обязательно цитируйте результаты, используя обозначение [[number](URL)] после ссылки. Если предоставленные результаты поиска относятся к нескольким темам с одинаковым названием, напишите отдельные ответы для каждой темы.\nЗапрос: [query]",
|
||||||
"genTitlePrompt": "Придумайте короткий заголовок для следующего содержания, не более 10 слов. \n\nСодержание: ",
|
"genTitlePrompt": "Придумайте короткий заголовок для следующего содержания, не более 10 слов. \n\nСодержание: ",
|
||||||
|
"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.",
|
||||||
|
"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, прежде чем вы сможете использовать этот клиент.",
|
||||||
|
|||||||
@@ -50,6 +50,10 @@
|
|||||||
"webSearch": "网页搜索",
|
"webSearch": "网页搜索",
|
||||||
"webSearchDefaultPrompt": "网络搜索结果:\n\n[web_results]\n当前日期:[current_date]\n\n说明:使用提供的网络搜索结果,对给定的查询写出全面的回复。确保在引用参考文献后使用 [[number](URL)] 符号进行引用结果. 如果提供的搜索结果涉及到多个具有相同名称的主题,请针对每个主题编写单独的答案。\n查询:[query]",
|
"webSearchDefaultPrompt": "网络搜索结果:\n\n[web_results]\n当前日期:[current_date]\n\n说明:使用提供的网络搜索结果,对给定的查询写出全面的回复。确保在引用参考文献后使用 [[number](URL)] 符号进行引用结果. 如果提供的搜索结果涉及到多个具有相同名称的主题,请针对每个主题编写单独的答案。\n查询:[query]",
|
||||||
"genTitlePrompt": "为以下内容生成一个不超过10个字的简短标题。 \n\n内容: ",
|
"genTitlePrompt": "为以下内容生成一个不超过10个字的简短标题。 \n\n内容: ",
|
||||||
|
"maxTokenTips1": "当前模型的最大上下文长度为",
|
||||||
|
"maxTokenTips2": "个 token,它包括了指令的长度和生成的文本长度。此处的最大 token 数量是指生成的文本长度。所以您应该为您的指令预留一些空间,不宜设置过大或拉满。",
|
||||||
|
"frugalMode": "节俭模式",
|
||||||
|
"frugalModeTip": "开启节俭模式,客户端不会把历史消息发送给ChatGPT,可以节省 token 的消耗。如果你想让 ChatGPT 了解对话的上下文,请关闭节俭模式。",
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"introduction1": "是一个非官方的ChatGPT客户端,但使用OpenAI的官方API",
|
"introduction1": "是一个非官方的ChatGPT客户端,但使用OpenAI的官方API",
|
||||||
"introduction2": "在使用本客户端之前,您需要一个OpenAI API密钥。",
|
"introduction2": "在使用本客户端之前,您需要一个OpenAI API密钥。",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ const transformData = (list) => {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSystemSettings = async () => {
|
export const fetchSystemSettings = async () => {
|
||||||
const { data, error } = await useAuthFetch('/api/chat/settings/', {
|
const { data, error } = await useAuthFetch('/api/chat/settings/', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
if (!error.value) {
|
if (!error.value) {
|
||||||
return transformData(data.value)
|
const settings = useSettings()
|
||||||
|
settings.value = transformData(data.value)
|
||||||
}
|
}
|
||||||
return {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchUser = async () => {
|
export const fetchUser = async () => {
|
||||||
|
|||||||
31
yarn.lock
31
yarn.lock
@@ -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==
|
||||||
|
|||||||
Reference in New Issue
Block a user