Compare commits

...

17 Commits

Author SHA1 Message Date
ChenZhaoYu
a6605e8a57 chore: v2.11.1 2023-10-11 16:17:19 +08:00
ChenZhaoYu
3482565481 chore: doc 2023-10-07 08:33:01 +08:00
ChenZhaoYu
a6f670101a fix: 不规范的引入 2023-09-26 11:53:18 +08:00
ChenZhaoYu
2683977e21 chore: 2.11.1 2023-09-26 11:52:38 +08:00
ChenZhaoYu
e1d8f5ff56 feat: 清空聊天历史 2023-09-26 11:50:38 +08:00
ChenZhaoYu
4f9232c130 chore: 格式化代码 2023-09-26 11:39:28 +08:00
ChenZhaoYu
c226d07368 fix: 修复打字机动画 2023-09-26 11:38:58 +08:00
Carson Yang
2b2efe2f15 Update README.md (#1864) 2023-08-09 11:45:11 +08:00
march
c2ce7009e6 fix: store循环引用 (#1880) 2023-08-09 11:43:57 +08:00
Peter Dave Hello
b651ef8373 chore: Improve zh-TW locale (#1837) 2023-07-15 11:19:45 +08:00
shansing
b8f2a0e849 feat: 允许temperature调到2 (#1797)
ref: https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature
2023-06-26 18:17:11 +08:00
Tin Nguyen Trong
c6e1663a39 Create vi-VN.ts (#1798) 2023-06-26 18:16:47 +08:00
怀瑾
847a2d4d8c style: 优化移动端代码展示 (#1752) 2023-06-19 14:28:10 +08:00
BertramRay
6e272bb343 feat: 支持最新的gpt-3.5-turbo-16k模型 (#1789)
* fix: 增加 16k 模型支持

* fix: 修改判断逻辑

* fix: 修改 gpt-3.5-turbo tokens 判断逻辑

---------

Co-authored-by: ziyang <ziyang@dora.design>
2023-06-19 14:26:22 +08:00
舜岳
bc390ef09d feat: "Stop Responding" 国际化,使用 chatgpt 翻译 (#1735) 2023-05-31 14:05:53 +08:00
tranhungonline
1cb5393b91 fix: 移动端新建会话关闭侧边栏 (#1661)
Co-authored-by: ChenZhaoYu <790348264@qq.com>
2023-05-06 10:58:06 +08:00
ChenZhaoYu
0f51e51827 fix: 移动端塌缩 2023-05-04 11:19:25 +08:00
22 changed files with 244 additions and 36 deletions

View File

@@ -1,3 +1,16 @@
## v2.11.1
`2023-10-11`
## Enhancement
- 优化打字机光标效果
- 清空聊天历史按钮
- 更新文档
## BugFix
- 修复移动端上的问题
- 修复不规范的引入导致的问题
## v2.11.0
`2023-04-26`

View File

@@ -2,8 +2,6 @@
> 声明:此项目只发布于 GitHub基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号、付费服务、讨论群、讨论组等行为。谨防受骗。
更多功能:[chatgpt-web-plus](https://github.com/Chanzhaoyu/chatgpt-web-plus)
![cover](./docs/c1.png)
![cover2](./docs/c2.png)
@@ -29,6 +27,7 @@
- [防止爬虫抓取](#防止爬虫抓取)
- [使用 Railway 部署](#使用-railway-部署)
- [Railway 环境变量](#railway-环境变量)
- [使用 Sealos 部署](#使用-sealos-部署)
- [手动打包](#手动打包)
- [后端服务](#后端服务-1)
- [前端网页](#前端网页-1)
@@ -218,7 +217,7 @@ services:
# API接口地址可选设置 OPENAI_API_KEY 时可用
OPENAI_API_BASE_URL: xxx
# API模型可选设置 OPENAI_API_KEY 时可用https://platform.openai.com/docs/models
# gpt-4, gpt-4-0314, gpt-4-32k, gpt-4-32k-0314, gpt-3.5-turbo, gpt-3.5-turbo-0301, text-davinci-003, text-davinci-002, code-davinci-002
# gpt-4, gpt-4-0314, gpt-4-0613, gpt-4-32k, gpt-4-32k-0314, gpt-4-32k-0613, gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0301, gpt-3.5-turbo-0613, text-davinci-003, text-davinci-002, code-davinci-002
OPENAI_API_MODEL: xxx
# 反向代理,可选
API_REVERSE_PROXY: xxx
@@ -278,6 +277,12 @@ services:
> 注意: `Railway` 修改环境变量会重新 `Deploy`
### 使用 Sealos 部署
[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dchatgpt-web)
> 环境变量与 Docker 环境变量一致
### 手动打包
#### 后端服务
> 如果你不需要本项目的 `node` 接口,可以省略如下操作

View File

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

View File

@@ -58,6 +58,12 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
options.maxResponseTokens = 2048
}
}
else if (model.toLowerCase().includes('gpt-3.5')) {
if (model.toLowerCase().includes('16k')) {
options.maxModelTokens = 16384
options.maxResponseTokens = 4096
}
}
if (isNotEmptyString(OPENAI_API_BASE_URL))
options.apiBaseUrl = `${OPENAI_API_BASE_URL}/v1`

View File

@@ -1,8 +1,8 @@
<script setup lang='ts'>
import { computed, onMounted, ref } from 'vue'
import { NSpin } from 'naive-ui'
import pkg from '../../../../package.json'
import { fetchChatConfig } from '@/api'
import pkg from '@/../package.json'
import { useAuthStore } from '@/store'
interface ConfigState {

View File

@@ -42,7 +42,7 @@ function handleReset() {
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[120px]">{{ $t('setting.temperature') }} </span>
<div class="flex-1">
<NSlider v-model:value="temperature" :max="1" :min="0" :step="0.1" />
<NSlider v-model:value="temperature" :max="2" :min="0" :step="0.1" />
</div>
<span>{{ temperature }}</span>
<NButton size="tiny" text type="primary" @click="updateSettings({ temperature })">

View File

@@ -26,6 +26,7 @@ export default {
failed: 'Failed',
verify: 'Verify',
unauthorizedTips: 'Unauthorized, please verify first.',
stopResponding: 'Stop Responding',
},
chat: {
newChatButton: 'New Chat',

View File

@@ -26,6 +26,7 @@ export default {
failed: '실패',
verify: '검증',
unauthorizedTips: '인증되지 않았습니다. 먼저 확인하십시오.',
stopResponding: '응답 중지',
},
chat: {
newChatButton: '새로운 채팅',

View File

@@ -26,6 +26,7 @@ export default {
failed: 'Не удалось',
verify: 'Проверить',
unauthorizedTips: 'Не авторизован, сначала подтвердите свою личность.',
stopResponding: 'Прекращение отклика',
},
chat: {
newChatButton: 'Новый чат',

94
src/locales/vi-VN.ts Normal file
View File

@@ -0,0 +1,94 @@
export default {
common: {
add: 'Thêm',
addSuccess: 'Thêm thành công',
edit: 'Sửa',
editSuccess: 'Sửa thành công',
delete: 'Xóa',
deleteSuccess: 'Xóa thành công',
save: 'Lưu',
saveSuccess: 'Lưu thành công',
reset: 'Đặt lại',
action: 'Hành động',
export: 'Xuất',
exportSuccess: 'Xuất thành công',
import: 'Nhập',
importSuccess: 'Nhập thành công',
clear: 'Dọn dẹp',
clearSuccess: 'Dọn dẹp thành công',
yes: 'Có',
no: 'Không',
confirm: 'Xác nhận',
download: 'Tải xuống',
noData: 'Không có dữ liệu',
wrong: 'Đã xảy ra lỗi, vui lòng thử lại sau.',
success: 'Thành công',
failed: 'Thất bại',
verify: 'Xác minh',
unauthorizedTips: 'Không được ủy quyền, vui lòng xác minh trước.',
},
chat: {
newChatButton: 'Tạo hội thoại',
placeholder: 'Hỏi tôi bất cứ điều gì...(Shift + Enter = ngắt dòng, "/" to trigger prompts)',
placeholderMobile: 'Hỏi tôi bất cứ điều gì...',
copy: 'Sao chép',
copied: 'Đã sao chép',
copyCode: 'Sao chép Code',
clearChat: 'Clear Chat',
clearChatConfirm: 'Bạn có chắc chắn xóa cuộc trò chuyện này?',
exportImage: 'Xuất hình ảnh',
exportImageConfirm: 'Bạn có chắc chắn xuất cuộc trò chuyện này sang png không?',
exportSuccess: 'Xuất thành công',
exportFailed: 'Xuất thất bại',
usingContext: 'Context Mode',
turnOnContext: 'Ở chế độ hiện tại, việc gửi tin nhắn sẽ mang theo các bản ghi trò chuyện trước đó.',
turnOffContext: 'Ở chế độ hiện tại, việc gửi tin nhắn sẽ không mang theo các bản ghi trò chuyện trước đó.',
deleteMessage: 'Xóa tin nhắn',
deleteMessageConfirm: 'Bạn có chắc chắn xóa tin nhắn này?',
deleteHistoryConfirm: 'Bạn có chắc chắn để xóa lịch sử này?',
clearHistoryConfirm: 'Bạn có chắc chắn để xóa lịch sử trò chuyện?',
preview: 'Xem trước',
showRawText: 'Hiển thị dưới dạng văn bản thô',
},
setting: {
setting: 'Cài đặt',
general: 'Chung',
advanced: 'Nâng cao',
config: 'Cấu hình',
avatarLink: 'Avatar Link',
name: 'Tên',
description: 'Miêu tả',
role: 'Vai trò',
temperature: 'Nhiệt độ',
top_p: 'Top_p',
resetUserInfo: 'Đặt lại thông tin người dùng',
chatHistory: 'Lịch sử trò chuyện',
theme: 'Giao diện',
language: 'Ngôn ngữ',
api: 'API',
reverseProxy: 'Reverse Proxy',
timeout: 'Timeout',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API Balance',
monthlyUsage: 'Sử dụng hàng tháng',
},
store: {
siderButton: 'Prompt Store',
local: 'Local',
online: 'Online',
title: 'Tiêu đề',
description: 'Miêu tả',
clearStoreConfirm: 'Cho dù để xóa dữ liệu?',
importPlaceholder: 'Vui lòng dán dữ liệu JSON vào đây',
addRepeatTitleTips: 'Tiêu đề trùng lặp, vui lòng nhập lại',
addRepeatContentTips: 'Nội dung trùng lặp: {msg}, vui lòng nhập lại',
editRepeatTitleTips: 'Xung đột tiêu đề, vui lòng sửa lại',
editRepeatContentTips: 'Xung đột nội dung {msg} , vui lòng sửa đổi lại',
importError: 'Key value mismatch',
importRepeatTitle: 'Tiêu đề liên tục bị bỏ qua: {msg}',
importRepeatContent: 'Nội dung liên tục bị bỏ qua: {msg}',
onlineImportWarning: 'Lưu ý: Vui lòng kiểm tra nguồn tệp JSON!',
downloadError: 'Vui lòng kiểm tra trạng thái mạng và tính hợp lệ của tệp JSON',
},
}

View File

@@ -26,6 +26,7 @@ export default {
failed: '操作失败',
verify: '验证',
unauthorizedTips: '未经授权,请先进行验证。',
stopResponding: '停止响应',
},
chat: {
newChatButton: '新建聊天',
@@ -46,7 +47,7 @@ export default {
deleteMessage: '删除消息',
deleteMessageConfirm: '是否删除此消息?',
deleteHistoryConfirm: '确定删除此记录?',
clearHistoryConfirm: '确定清空聊天记录?',
clearHistoryConfirm: '确定清空记录?',
preview: '预览',
showRawText: '显示原文',
},

View File

@@ -26,6 +26,7 @@ export default {
failed: '操作失敗',
verify: '驗證',
unauthorizedTips: '未經授權,請先進行驗證。',
stopResponding: '停止回應',
},
chat: {
newChatButton: '新增對話',

3
src/store/helper.ts Normal file
View File

@@ -0,0 +1,3 @@
import { createPinia } from 'pinia'
export const store = createPinia()

View File

@@ -1,7 +1,5 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
export const store = createPinia()
import { store } from './helper'
export function setupStore(app: App) {
app.use(store)

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import type { AppState, Language, Theme } from './helper'
import { getLocalSetting, setLocalSetting } from './helper'
import { store } from '@/store'
import { store } from '@/store/helper'
export const useAppStore = defineStore('app-store', {
state: (): AppState => getLocalSetting(),

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { getToken, removeToken, setToken } from './helper'
import { store } from '@/store'
import { store } from '@/store/helper'
import { fetchSession } from '@/api'
interface SessionResponse {

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { getLocalState, setLocalState } from './helper'
import { defaultState, getLocalState, setLocalState } from './helper'
import { router } from '@/router'
export const useChatStore = defineStore('chat-store', {
@@ -182,6 +182,11 @@ export const useChatStore = defineStore('chat-store', {
}
},
clearHistory() {
this.$state = { ...defaultState() }
this.recordState()
},
async reloadRoute(uuid?: number) {
this.recordState()
await router.push({ name: 'Chat', params: { uuid } })

View File

@@ -9,7 +9,7 @@ interface Props {
interface Emit {
(ev: 'export'): void
(ev: 'toggleUsingContext'): void
(ev: 'handleClear'): void
}
defineProps<Props>()
@@ -36,8 +36,8 @@ function handleExport() {
emit('export')
}
function toggleUsingContext() {
emit('toggleUsingContext')
function handleClear() {
emit('handleClear')
}
</script>
@@ -62,16 +62,16 @@ function toggleUsingContext() {
{{ currentChatHistory?.title ?? '' }}
</h1>
<div class="flex items-center space-x-2">
<HoverButton @click="toggleUsingContext">
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
<SvgIcon icon="ri:chat-history-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="handleClear">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" />
</span>
</HoverButton>
</div>
</div>
</header>

View File

@@ -107,13 +107,10 @@ onUnmounted(() => {
<div class="text-black" :class="wrapClass">
<div ref="textRef" class="leading-relaxed break-words">
<div v-if="!inversion">
<div v-if="!asRawText" class="markdown-body" v-html="text" />
<div v-if="!asRawText" class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
<div v-else class="whitespace-pre-wrap" v-text="text" />
</div>
<div v-else class="whitespace-pre-wrap" v-text="text" />
<template v-if="loading">
<span class="dark:text-white w-[4px] h-[20px] block animate-blink" />
</template>
</div>
</div>
</template>

View File

@@ -57,10 +57,60 @@
}
}
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
@keyframes blink {
to {
visibility: hidden;
}
}
}
html.dark {
.markdown-body {
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
color: #65a665;
}
}
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
@@ -73,3 +123,13 @@ html.dark {
background-color: #282c34;
}
}
@media screen and (max-width: 533px) {
.markdown-body .code-block-wrapper {
padding: unset;
code {
padding: 24px 16px 16px 16px;
}
}
}

View File

@@ -93,7 +93,7 @@ async function onConversation() {
+uuid,
{
dateTime: new Date().toLocaleString(),
text: '',
text: '思考中',
loading: true,
inversion: false,
error: false,
@@ -469,7 +469,7 @@ onUnmounted(() => {
v-if="isMobile"
:using-context="usingContext"
@export="handleExport"
@toggle-using-context="toggleUsingContext"
@handle-clear="handleClear"
/>
<main class="flex-1 overflow-hidden">
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
@@ -502,7 +502,7 @@ onUnmounted(() => {
<template #icon>
<SvgIcon icon="ri:stop-circle-line" />
</template>
Stop Responding
{{ t('common.stopResponding') }}
</NButton>
</div>
</div>
@@ -513,7 +513,7 @@ 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">
<HoverButton v-if="!isMobile" @click="handleClear">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:delete-bin-line" />
</span>
@@ -523,7 +523,7 @@ onUnmounted(() => {
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
<HoverButton v-if="!isMobile" @click="toggleUsingContext">
<HoverButton @click="toggleUsingContext">
<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
<SvgIcon icon="ri:chat-history-line" />
</span>

View File

@@ -1,16 +1,19 @@
<script setup lang='ts'>
import type { CSSProperties } from 'vue'
import { computed, ref, watch } from 'vue'
import { NButton, NLayoutSider } from 'naive-ui'
import { NButton, NLayoutSider, useDialog } from 'naive-ui'
import List from './List.vue'
import Footer from './Footer.vue'
import { useAppStore, useChatStore } from '@/store'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { PromptStore } from '@/components/common'
import { PromptStore, SvgIcon } from '@/components/common'
import { t } from '@/locales'
const appStore = useAppStore()
const chatStore = useChatStore()
const dialog = useDialog()
const { isMobile } = useBasicLayout()
const show = ref(false)
@@ -26,6 +29,20 @@ function handleUpdateCollapsed() {
appStore.setSiderCollapsed(!collapsed.value)
}
function handleClearAll() {
dialog.warning({
title: t('chat.deleteMessage'),
content: t('chat.clearHistoryConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: () => {
chatStore.clearHistory()
if (isMobile.value)
appStore.setSiderCollapsed(true)
},
})
}
const getMobileClass = computed<CSSProperties>(() => {
if (isMobile.value) {
return {
@@ -79,9 +96,14 @@ watch(
<div class="flex-1 min-h-0 pb-4 overflow-hidden">
<List />
</div>
<div class="p-4">
<NButton block @click="show = true">
{{ $t('store.siderButton') }}
<div class="flex items-center p-4 space-x-4">
<div class="flex-1">
<NButton block @click="show = true">
{{ $t('store.siderButton') }}
</NButton>
</div>
<NButton @click="handleClearAll">
<SvgIcon icon="ri:close-circle-line" />
</NButton>
</div>
</main>
@@ -89,7 +111,7 @@ watch(
</div>
</NLayoutSider>
<template v-if="isMobile">
<div v-show="!collapsed" class="fixed inset-0 z-40 bg-black/40" @click="handleUpdateCollapsed" />
<div v-show="!collapsed" class="fixed inset-0 z-40 w-full h-full bg-black/40" @click="handleUpdateCollapsed" />
</template>
<PromptStore v-model:visible="show" />
</template>