ChatAnswer.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <script setup>
  2. import { computed, unref } from 'vue';
  3. import { useMessage } from 'naive-ui';
  4. import { useClipboard } from '@vueuse/core'
  5. import MarkdownIt from 'markdown-it';
  6. import hljs from 'highlight.js';
  7. import mila from 'markdown-it-link-attributes';
  8. // import markdownItLatex from 'markdown-it-latex'
  9. import mdKatex from '@traptitech/markdown-it-katex';
  10. import { SvgIcon } from '@/components';
  11. import { chatApi } from "@/api/chat"
  12. // import 'markdown-it-latex/dist/index.css'
  13. const props = defineProps({
  14. id: {
  15. type: String || Number,
  16. default: ''
  17. },
  18. content: {
  19. type: String,
  20. default: ''
  21. },
  22. loading: {
  23. type: Boolean,
  24. default: false
  25. },
  26. delayLoading: {
  27. type: Boolean,
  28. default: false
  29. },
  30. isSatisfied: {
  31. type: Number,
  32. default: 2
  33. }
  34. })
  35. const emit = defineEmits(['on-click-icon']);
  36. const { copy } = useClipboard();
  37. const message = useMessage();
  38. function highlightBlock(str, lang) {
  39. return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__copy"></span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
  40. }
  41. const mdi = new MarkdownIt({
  42. html: true,
  43. linkify: true,
  44. // breaks: true,
  45. typographer: true,
  46. highlight(code, language) {
  47. const validLang = !!(language && hljs.getLanguage(language))
  48. if (validLang) {
  49. const lang = language ?? ''
  50. return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
  51. }
  52. return highlightBlock(hljs.highlightAuto(code).value, '')
  53. },
  54. })
  55. mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
  56. mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
  57. // mdi.use(markdownItLatex)
  58. const text = computed(() => {
  59. const value = props.content ?? ""
  60. if (!props.asRawText)
  61. return mdi.render(value)
  62. return value
  63. })
  64. const handlLeToggleLike = async (state) => {
  65. const { id } = unref(props);
  66. const isSatisfied = props.isSatisfied === state ? 2 : state;
  67. const params = { id, isSatisfied };
  68. await chatApi.putIsSatisfiedAnswer(params);
  69. isSatisfied < 2 ? message.success('感谢您的反馈') : message.success('已取消反馈');
  70. emit('on-click-icon', params)
  71. }
  72. const handleCopy = () => {
  73. copy(props.content).then(() => {
  74. message.success('复制成功');
  75. })
  76. }
  77. </script>
  78. <template>
  79. <div class="answer-inner">
  80. <div :class="[ 'answer-card', 'px-[20px]', 'py-[20px]']">
  81. <div class="chat-answer_icon relative flex-shrink-0">
  82. <SvgIcon name="common-logo" class="chat-logo " size="30" :style="{ scale: loading ? 0 : 1 }" />
  83. <div style="color: #2454FF" class="la-ball-circus la-dark la-sm flex-shrink-0" v-show="loading">
  84. <div v-for="item in 5" :key="item"></div>
  85. </div>
  86. </div>
  87. <div class="flex-1 pt-[4px] ml-[16px] text-[15px]">
  88. <template v-if="loading && delayLoading">
  89. <p class="font-bold text-[#1A2029] leading-[24px]">内容生成中...</p>
  90. </template>
  91. <div class="markdown-body text-[15px]" v-if="content">
  92. <div v-html="text"></div>
  93. </div>
  94. <template>
  95. <slot></slot>
  96. </template>
  97. </div>
  98. </div>
  99. <ul class="answer-btn-group" v-if="!loading">
  100. <li class="btn" @click="handleCopy">
  101. <SvgIcon name="chat-icon-copy" size="16" />
  102. </li>
  103. <li class="line"></li>
  104. <li :class="['btn', { btn_active: isSatisfied == 1 }]">
  105. <SvgIcon name="chat-icon-yes" size="16" @click="handlLeToggleLike(1)" />
  106. </li>
  107. <li class="line"></li>
  108. <li :class="['btn', { btn_active: isSatisfied == 0 }]">
  109. <SvgIcon name="chat-icon-no" size="16" @click="handlLeToggleLike(0)" />
  110. </li>
  111. </ul>
  112. </div>
  113. </template>
  114. <style lang="scss">
  115. .markdown-body p:last-child {
  116. margin-bottom: 0;
  117. }
  118. .markdown-body {
  119. p:last-child {
  120. margin-bottom: 0;
  121. }
  122. img {
  123. margin-top: 20px;
  124. }
  125. }
  126. .chat-logo {
  127. position: absolute;
  128. transition: all 1s;
  129. }
  130. .answer-inner {
  131. margin-bottom: 20px;
  132. .answer-card {
  133. @include flex(x, start, start);
  134. // padding: 20px 20px 4px 20px;
  135. border-radius: 8px;
  136. background: #fff;
  137. }
  138. .answer-btn-group {
  139. @include flex(x, center, end);
  140. padding-top: 6px;
  141. .btn {
  142. @include flex(x, center, center);
  143. @include layout(28px, 28px, 4px);
  144. color: #89909B;
  145. cursor: pointer;
  146. &:hover, &_active {
  147. background: #DBEFFF;
  148. color: #2454FF;
  149. }
  150. }
  151. .line {
  152. @include layout(1px, 12px, 0);
  153. margin: 0 5px;
  154. background: #D3D0E1;
  155. }
  156. }
  157. }
  158. </style>