feat: 添加用于显示回复消息原文的选项 (#672)

* feat: 添加显示用于原文的选项

* chore: 修复暗色主题下文本颜色问题

给输入和输出气泡添加了 css 类,用来处理在暗色主题下聊天气泡的文本颜色

* feat: 用户输入不应该被渲染,防止 xss

---------

Co-authored-by: ChenZhaoYu <790348264@qq.com>
This commit is contained in:
Yi 2023-03-21 08:44:40 +08:00 committed by GitHub
parent f1584b60e8
commit 47dc009505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 17 deletions

View File

@ -46,6 +46,8 @@ export default {
deleteMessageConfirm: 'Are you sure to delete this message?',
deleteHistoryConfirm: 'Are you sure to clear this history?',
clearHistoryConfirm: 'Are you sure to clear chat history?',
preview: 'Preview',
showRawText: 'Show as raw text',
},
setting: {
setting: 'Setting',

View File

@ -46,6 +46,8 @@ export default {
deleteMessageConfirm: '是否删除此消息?',
deleteHistoryConfirm: '确定删除此记录?',
clearHistoryConfirm: '确定清空聊天记录?',
preview: '预览',
showRawText: '显示原文',
},
setting: {
setting: '设置',

View File

@ -46,6 +46,8 @@ export default {
deleteMessageConfirm: '是否刪除此訊息?',
deleteHistoryConfirm: '確定刪除此紀錄?',
clearHistoryConfirm: '確定清除紀錄?',
preview: '預覽',
showRawText: '顯示原文',
},
setting: {
setting: '設定',

View File

@ -12,6 +12,7 @@ interface Props {
error?: boolean
text?: string
loading?: boolean
asRawText?: boolean
}
const props = defineProps<Props>()
@ -43,13 +44,14 @@ const wrapClass = computed(() => {
isMobile.value ? 'p-2' : 'px-3 py-2',
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
props.inversion ? 'message-request' : 'message-reply',
{ 'text-red-500': props.error },
]
})
const text = computed(() => {
const value = props.text ?? ''
if (!props.inversion)
if (!props.asRawText)
return mdi.render(value)
return value
})
@ -68,7 +70,10 @@ defineExpose({ textRef })
</template>
<template v-else>
<div ref="textRef" class="leading-relaxed break-words">
<div v-if="!inversion" class="markdown-body" v-html="text" />
<div v-if="!inversion">
<div v-if="!asRawText" class="markdown-body" v-html="text" />
<div v-else class="raw-text" v-text="text" />
</div>
<div v-else class="whitespace-pre-wrap" v-text="text" />
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang='ts'>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { NDropdown } from 'naive-ui'
import AvatarComponent from './Avatar.vue'
import TextComponent from './Text.vue'
@ -29,7 +29,10 @@ const { iconRender } = useIconRender()
const textRef = ref<HTMLElement>()
const options = [
const asRawText = ref(props.inversion)
const options = computed(() => {
const common = [
{
label: t('chat.copy'),
key: 'copyText',
@ -40,13 +43,27 @@ const options = [
key: 'delete',
icon: iconRender({ icon: 'ri:delete-bin-line' }),
},
]
]
function handleSelect(key: 'copyRaw' | 'copyText' | 'delete') {
if (!props.inversion) {
common.unshift({
label: asRawText.value ? t('chat.preview') : t('chat.showRawText'),
key: 'toggleRenderType',
icon: iconRender({ icon: asRawText.value ? 'ic:outline-code-off' : 'ic:outline-code' }),
})
}
return common
})
function handleSelect(key: 'copyText' | 'delete' | 'toggleRenderType') {
switch (key) {
case 'copyText':
copyText({ text: props.text ?? '' })
return
case 'toggleRenderType':
asRawText.value = !asRawText.value
return
case 'delete':
emit('delete')
}
@ -79,6 +96,7 @@ function handleRegenerate() {
:error="error"
:text="text"
:loading="loading"
:as-raw-text="asRawText"
/>
<div class="flex flex-col">
<button

View File

@ -45,20 +45,29 @@
align-items: center;
color: #b3b3b3;
&__copy{
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
}
html.dark {
.message-reply {
.raw-text {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;