mirror of
https://github.com/Chanzhaoyu/chatgpt-web.git
synced 2025-10-22 11:54:08 +00:00
feat: version 2.9.1 (#207)
* feat: i18n * chore: format * feat: 补充遗漏翻译 * chore: update deps * feat: 复制代码块[#196][#197] * chore: version 2.9.1
This commit is contained in:
@@ -4,6 +4,7 @@ import { marked } from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { encodeHTML } from '@/utils/format'
|
||||
import { t } from '@/locales'
|
||||
|
||||
interface Props {
|
||||
inversion?: boolean
|
||||
@@ -26,8 +27,10 @@ renderer.html = (html) => {
|
||||
|
||||
renderer.code = (code, language) => {
|
||||
const validLang = !!(language && hljs.getLanguage(language))
|
||||
if (validLang)
|
||||
return `<pre><code class="hljs ${language}">${hljs.highlight(language, code).value}</code></pre>`
|
||||
if (validLang) {
|
||||
const lang = language ?? ''
|
||||
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${language}">${hljs.highlight(lang, code).value}</code></pre>`
|
||||
}
|
||||
return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref } from 'vue'
|
||||
import { NDropdown, useMessage } from 'naive-ui'
|
||||
import { NDropdown } from 'naive-ui'
|
||||
import AvatarComponent from './Avatar.vue'
|
||||
import TextComponent from './Text.vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { copyText } from '@/utils/format'
|
||||
import { useIconRender } from '@/hooks/useIconRender'
|
||||
import { t } from '@/locales'
|
||||
|
||||
interface Props {
|
||||
dateTime?: string
|
||||
@@ -24,25 +25,18 @@ const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const { iconRender } = useIconRender()
|
||||
|
||||
const textRef = ref<HTMLElement>()
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: 'Copy Raw',
|
||||
key: 'copyRaw',
|
||||
label: t('chat.copy'),
|
||||
key: 'copyText',
|
||||
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
|
||||
},
|
||||
{
|
||||
label: 'Copy Text',
|
||||
key: 'copyText',
|
||||
icon: iconRender({ icon: 'ri:file-copy-line' }),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
label: t('common.delete'),
|
||||
key: 'delete',
|
||||
icon: iconRender({ icon: 'ri:delete-bin-line' }),
|
||||
},
|
||||
@@ -50,15 +44,8 @@ const options = [
|
||||
|
||||
function handleSelect(key: 'copyRaw' | 'copyText' | 'delete') {
|
||||
switch (key) {
|
||||
case 'copyRaw':
|
||||
if (textRef.value && (textRef.value as any).textRef) {
|
||||
copyText({ text: (textRef.value as any).textRef.innerText })
|
||||
ms.success('Copied Raw')
|
||||
}
|
||||
return
|
||||
case 'copyText':
|
||||
copyText({ text: props.text ?? '', origin: false })
|
||||
ms.success('Copied Text')
|
||||
copyText({ text: props.text ?? '' })
|
||||
return
|
||||
case 'delete':
|
||||
emit('delete')
|
||||
|
@@ -24,9 +24,37 @@
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
code.hljs{
|
||||
code.hljs {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
&-wrapper {
|
||||
position: relative;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
color: #b3b3b3;
|
||||
|
||||
&__copy{
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
user-select: none;
|
||||
&:hover {
|
||||
color: #65a665;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.dark {
|
||||
|
18
src/views/chat/hooks/useCopyCode.ts
Normal file
18
src/views/chat/hooks/useCopyCode.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
export function useCopyCode() {
|
||||
function copyCodeBlock() {
|
||||
const codeBlockWrapper = document.querySelectorAll('.code-block-wrapper')
|
||||
codeBlockWrapper.forEach((wrapper) => {
|
||||
const copyBtn = wrapper.querySelector('.code-block-header__copy')
|
||||
const codeBlock = wrapper.querySelector('.code-block-body')
|
||||
if (copyBtn && codeBlock) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(codeBlock.textContent ?? '')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => copyCodeBlock())
|
||||
}
|
@@ -1,23 +1,25 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { NButton, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import { NButton, NInput, useDialog } from 'naive-ui'
|
||||
import { Message } from './components'
|
||||
import { useScroll } from './hooks/useScroll'
|
||||
import { useChat } from './hooks/useChat'
|
||||
import { useCopyCode } from './hooks/useCopyCode'
|
||||
import { HoverButton, SvgIcon } from '@/components/common'
|
||||
import { useBasicLayout } from '@/hooks/useBasicLayout'
|
||||
import { useChatStore } from '@/store'
|
||||
import { fetchChatAPIProcess } from '@/api'
|
||||
import { t } from '@/locales'
|
||||
|
||||
let controller = new AbortController()
|
||||
|
||||
const route = useRoute()
|
||||
const dialog = useDialog()
|
||||
const ms = useMessage()
|
||||
|
||||
const chatStore = useChatStore()
|
||||
|
||||
useCopyCode()
|
||||
const { isMobile } = useBasicLayout()
|
||||
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
|
||||
const { scrollRef, scrollToBottom } = useScroll()
|
||||
@@ -118,7 +120,7 @@ async function onConversation() {
|
||||
})
|
||||
}
|
||||
catch (error: any) {
|
||||
const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||
const errorMessage = error?.message ?? t('common.wrong')
|
||||
|
||||
if (error.message === 'canceled') {
|
||||
updateChatSome(
|
||||
@@ -245,7 +247,7 @@ async function onRegenerate(index: number) {
|
||||
return
|
||||
}
|
||||
|
||||
const errorMessage = error?.message ?? 'Something went wrong, please try again later.'
|
||||
const errorMessage = error?.message ?? t('common.wrong')
|
||||
|
||||
updateChat(
|
||||
+uuid,
|
||||
@@ -271,13 +273,12 @@ function handleDelete(index: number) {
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: 'Delete Message',
|
||||
content: 'Are you sure to delete this message?',
|
||||
positiveText: 'Yes',
|
||||
negativeText: 'No',
|
||||
title: t('chat.deleteMessage'),
|
||||
content: t('chat.deleteMessageConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: () => {
|
||||
chatStore.deleteChatByUuid(+uuid, index)
|
||||
ms.success('Message deleted successfully.')
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -287,10 +288,10 @@ function handleClear() {
|
||||
return
|
||||
|
||||
dialog.warning({
|
||||
title: 'Clear Chat',
|
||||
content: 'Are you sure to clear this chat?',
|
||||
positiveText: 'Yes',
|
||||
negativeText: 'No',
|
||||
title: t('chat.clearChat'),
|
||||
content: t('chat.clearChatConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: () => {
|
||||
chatStore.clearChatByUuid(+uuid)
|
||||
},
|
||||
@@ -315,8 +316,8 @@ function handleStop() {
|
||||
|
||||
const placeholder = computed(() => {
|
||||
if (isMobile.value)
|
||||
return 'Ask me anything...'
|
||||
return 'Ask me anything... (Shift + Enter = line break)'
|
||||
return t('chat.placeholderMobile')
|
||||
return t('chat.placeholder')
|
||||
})
|
||||
|
||||
const buttonDisabled = computed(() => {
|
||||
|
@@ -11,7 +11,7 @@ const show = ref(false)
|
||||
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t dark:border-neutral-800">
|
||||
<UserAvatar />
|
||||
|
||||
<HoverButton tooltip="Setting" @click="show = true">
|
||||
<HoverButton :tooltip="$t('setting.setting')" @click="show = true">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||
<SvgIcon icon="ri:settings-4-line" />
|
||||
</span>
|
||||
|
@@ -49,7 +49,7 @@ function isActive(uuid: number) {
|
||||
<template v-if="!dataSources.length">
|
||||
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
|
||||
<span>No history</span>
|
||||
<span>{{ $t('common.noData') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -87,7 +87,7 @@ function isActive(uuid: number) {
|
||||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</button>
|
||||
</template>
|
||||
Are you sure to clear this history?
|
||||
{{ $t('chat.deleteHistoryConfirm') }}
|
||||
</NPopconfirm>
|
||||
</template>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user