feat: 添加用于显示回复消息原文的选项 (#672)
* feat: 添加显示用于原文的选项 * chore: 修复暗色主题下文本颜色问题 给输入和输出气泡添加了 css 类,用来处理在暗色主题下聊天气泡的文本颜色 * feat: 用户输入不应该被渲染,防止 xss --------- Co-authored-by: ChenZhaoYu <790348264@qq.com>
This commit is contained in:
parent
f1584b60e8
commit
47dc009505
|
@ -46,6 +46,8 @@ export default {
|
||||||
deleteMessageConfirm: 'Are you sure to delete this message?',
|
deleteMessageConfirm: 'Are you sure to delete this message?',
|
||||||
deleteHistoryConfirm: 'Are you sure to clear this history?',
|
deleteHistoryConfirm: 'Are you sure to clear this history?',
|
||||||
clearHistoryConfirm: 'Are you sure to clear chat history?',
|
clearHistoryConfirm: 'Are you sure to clear chat history?',
|
||||||
|
preview: 'Preview',
|
||||||
|
showRawText: 'Show as raw text',
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
setting: 'Setting',
|
setting: 'Setting',
|
||||||
|
|
|
@ -46,6 +46,8 @@ export default {
|
||||||
deleteMessageConfirm: '是否删除此消息?',
|
deleteMessageConfirm: '是否删除此消息?',
|
||||||
deleteHistoryConfirm: '确定删除此记录?',
|
deleteHistoryConfirm: '确定删除此记录?',
|
||||||
clearHistoryConfirm: '确定清空聊天记录?',
|
clearHistoryConfirm: '确定清空聊天记录?',
|
||||||
|
preview: '预览',
|
||||||
|
showRawText: '显示原文',
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
setting: '设置',
|
setting: '设置',
|
||||||
|
|
|
@ -46,6 +46,8 @@ export default {
|
||||||
deleteMessageConfirm: '是否刪除此訊息?',
|
deleteMessageConfirm: '是否刪除此訊息?',
|
||||||
deleteHistoryConfirm: '確定刪除此紀錄?',
|
deleteHistoryConfirm: '確定刪除此紀錄?',
|
||||||
clearHistoryConfirm: '確定清除紀錄?',
|
clearHistoryConfirm: '確定清除紀錄?',
|
||||||
|
preview: '預覽',
|
||||||
|
showRawText: '顯示原文',
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
setting: '設定',
|
setting: '設定',
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface Props {
|
||||||
error?: boolean
|
error?: boolean
|
||||||
text?: string
|
text?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
asRawText?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
@ -43,13 +44,14 @@ const wrapClass = computed(() => {
|
||||||
isMobile.value ? 'p-2' : 'px-3 py-2',
|
isMobile.value ? 'p-2' : 'px-3 py-2',
|
||||||
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
|
||||||
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
|
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
|
||||||
|
props.inversion ? 'message-request' : 'message-reply',
|
||||||
{ 'text-red-500': props.error },
|
{ 'text-red-500': props.error },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = computed(() => {
|
const text = computed(() => {
|
||||||
const value = props.text ?? ''
|
const value = props.text ?? ''
|
||||||
if (!props.inversion)
|
if (!props.asRawText)
|
||||||
return mdi.render(value)
|
return mdi.render(value)
|
||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
|
@ -68,7 +70,10 @@ defineExpose({ textRef })
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div ref="textRef" class="leading-relaxed break-words">
|
<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 v-else class="whitespace-pre-wrap" v-text="text" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { NDropdown } from 'naive-ui'
|
import { NDropdown } from 'naive-ui'
|
||||||
import AvatarComponent from './Avatar.vue'
|
import AvatarComponent from './Avatar.vue'
|
||||||
import TextComponent from './Text.vue'
|
import TextComponent from './Text.vue'
|
||||||
|
@ -29,24 +29,41 @@ const { iconRender } = useIconRender()
|
||||||
|
|
||||||
const textRef = ref<HTMLElement>()
|
const textRef = ref<HTMLElement>()
|
||||||
|
|
||||||
const options = [
|
const asRawText = ref(props.inversion)
|
||||||
{
|
|
||||||
label: t('chat.copy'),
|
|
||||||
key: 'copyText',
|
|
||||||
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('common.delete'),
|
|
||||||
key: 'delete',
|
|
||||||
icon: iconRender({ icon: 'ri:delete-bin-line' }),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function handleSelect(key: 'copyRaw' | 'copyText' | 'delete') {
|
const options = computed(() => {
|
||||||
|
const common = [
|
||||||
|
{
|
||||||
|
label: t('chat.copy'),
|
||||||
|
key: 'copyText',
|
||||||
|
icon: iconRender({ icon: 'ri:file-copy-2-line' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('common.delete'),
|
||||||
|
key: 'delete',
|
||||||
|
icon: iconRender({ icon: 'ri:delete-bin-line' }),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
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) {
|
switch (key) {
|
||||||
case 'copyText':
|
case 'copyText':
|
||||||
copyText({ text: props.text ?? '' })
|
copyText({ text: props.text ?? '' })
|
||||||
return
|
return
|
||||||
|
case 'toggleRenderType':
|
||||||
|
asRawText.value = !asRawText.value
|
||||||
|
return
|
||||||
case 'delete':
|
case 'delete':
|
||||||
emit('delete')
|
emit('delete')
|
||||||
}
|
}
|
||||||
|
@ -79,6 +96,7 @@ function handleRegenerate() {
|
||||||
:error="error"
|
:error="error"
|
||||||
:text="text"
|
:text="text"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:as-raw-text="asRawText"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -45,20 +45,29 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #b3b3b3;
|
color: #b3b3b3;
|
||||||
|
|
||||||
&__copy{
|
&__copy {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #65a665;
|
color: #65a665;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
|
|
||||||
|
.message-reply {
|
||||||
|
.raw-text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: var(--n-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.highlight pre,
|
.highlight pre,
|
||||||
pre {
|
pre {
|
||||||
background-color: #282c34;
|
background-color: #282c34;
|
||||||
|
|
Loading…
Reference in New Issue