chatText.vue
3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<template>
<div v-if="text != ''" class="textWrap" :class="[inversion === 'user' ? 'self' : 'chatgpt']" ref="textRef">
<div v-if="inversion != 'user'">
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
</div>
<div v-else class="msg" v-text="text" />
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue';
import MarkdownIt from 'markdown-it';
import mdKatex from '@traptitech/markdown-it-katex';
import mila from 'markdown-it-link-attributes';
import hljs from 'highlight.js';
import './style/github-markdown.less';
import './style/highlight.less';
import './style/style.less';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
const textRef = ref();
const mdi = new MarkdownIt({
html: false,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language));
if (validLang) {
const lang = language ?? '';
return highlightBlock(hljs.highlight(code, { language: lang }).value, lang);
}
return highlightBlock(hljs.highlightAuto(code).value, '');
},
});
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
const text = computed(() => {
const value = props.text ?? '';
if (props.inversion != 'user') return mdi.render(value);
return value;
});
function highlightBlock(str: string, lang?: string) {
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">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
}
function addCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy');
copyBtn.forEach((btn) => {
btn.addEventListener('click', () => {
const code = btn.parentElement?.nextElementSibling?.textContent;
if (code) {
copyToClip(code).then(() => {
btn.textContent = '复制成功';
setTimeout(() => {
btn.textContent = '复制代码';
}, 1e3);
});
}
});
});
}
}
function removeCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy');
copyBtn.forEach((btn) => {
btn.removeEventListener('click', () => {});
});
}
}
onMounted(() => {
addCopyEvents();
});
onUpdated(() => {
addCopyEvents();
});
onUnmounted(() => {
removeCopyEvents();
});
function copyToClip(text: string) {
return new Promise((resolve, reject) => {
try {
const input: HTMLTextAreaElement = document.createElement('textarea');
input.setAttribute('readonly', 'readonly');
input.value = text;
document.body.appendChild(input);
input.select();
if (document.execCommand('copy')) document.execCommand('copy');
document.body.removeChild(input);
resolve(text);
} catch (error) {
reject(error);
}
});
}
</script>
<style lang="less" scoped>
.textWrap {
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.self {
// background-color: #d2f9d1;
background-color: @primary-color;
color: #fff;
overflow-wrap: break-word;
line-height: 1.625;
min-width: 20px;
}
.chatgpt {
background-color: #f4f6f8;
font-size: 0.875rem;
line-height: 1.25rem;
}
</style>