AnswerView.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <script setup>
  2. import { ref, unref, computed, onMounted } from 'vue';
  3. import { useMessage } from 'naive-ui';
  4. import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome } from '@/components';
  5. import { ChatAsk, ChatAnswer, ChatInput } from '@/components/Chat';
  6. import { chatApi } from '@/api/chat';
  7. import { useInfinite } from '@/composables/useInfinite';
  8. import { useScroll } from '@/composables/useScroll';
  9. import { useChat } from '@/composables/useChat';
  10. import { useRecommend } from '@/composables/useRecommend';
  11. // TODO: 如果这里的key不一样,将会在拆一层组件出来 - list
  12. const { recordList, isFetching, onScrolltolower, onReset, addHistoryRecord } = useInfinite({model: 0});
  13. const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
  14. const { chatDataSource, addChat, updateChat, clearChat } = useChat();
  15. const { recommendList } = useRecommend({type: 0});
  16. const message = useMessage();
  17. const isLoading = ref(false);
  18. const currenSessionId = ref(null);
  19. const isExistInHistory = computed(() => (recordList.value.findIndex(({ sessionId: sId }) => sId === unref(currenSessionId)) === -1));
  20. // 新建对话
  21. const handleCreateDialog = async () => {
  22. message.destroyAll();
  23. if (unref(isLoading)) {
  24. return message.warning('当前对话生成中');
  25. }
  26. if (!unref(chatDataSource).length) {
  27. return message.info('已切换最新会话');
  28. }
  29. clearChat();
  30. }
  31. // 查询对话详情
  32. const handleChatDetail = async ({ sessionId }) => {
  33. isLoading.value = false;
  34. const { data } = await chatApi.getAnswerHistoryDetail({ sessionId });
  35. chatDataSource.value = data.map(({ createTime, sessionId, question, answer }) => ({
  36. createTime,
  37. sessionId,
  38. question,
  39. answer,
  40. loading: false
  41. }));
  42. currenSessionId.value = sessionId;
  43. scrollToBottom();
  44. }
  45. const onRegenerate = async ({ question, realQuestion }) => {
  46. const sessionId = unref(currenSessionId);
  47. const params = {
  48. data: {
  49. sessionId,
  50. question: realQuestion || question,
  51. },
  52. onDownloadProgress: ({ event }) => {
  53. const xhr = event.target;
  54. const { responseText: answer } = xhr;
  55. updateChat({
  56. sessionId,
  57. question,
  58. answer,
  59. loading: true,
  60. delayLoading: false
  61. })
  62. scrollToBottomIfAtBottom();
  63. }
  64. }
  65. try {
  66. const { data: answer } = await chatApi.getChatStream(params);
  67. updateChat({
  68. sessionId,
  69. question,
  70. answer,
  71. loading: false,
  72. delayLoading: false
  73. })
  74. }
  75. finally {
  76. isLoading.value = false;
  77. }
  78. }
  79. // 提交问题
  80. const handleSubmit = async (question, realQuestion = '') => {
  81. // 用于模拟 - 内容生成前置等待状态
  82. if (unref(isExistInHistory)) {
  83. const { data: sessionId } = await chatApi.getChatSessionTag();
  84. currenSessionId.value = sessionId;
  85. }
  86. isLoading.value = true;
  87. addChat({
  88. sessionId: unref(currenSessionId),
  89. question,
  90. realQuestion,
  91. answer: '',
  92. loading: true,
  93. delayLoading: true
  94. })
  95. scrollToBottom();
  96. setTimeout(() => onRegenerate({ question, realQuestion }), 2 * 1000);
  97. }
  98. // 处理推荐问题
  99. const handleWelcomeRecommend = ({ question, realQuestion }) => {
  100. handleSubmit( question, realQuestion );
  101. }
  102. // 删除历史对话
  103. const handeChatDelete = async (id) => {
  104. await chatApi.deleteHistory(id);
  105. onReset();
  106. clearChat();
  107. message.success('删除成功');
  108. }
  109. </script>
  110. <template>
  111. <section class="flex items-start h-full">
  112. <TheSubMenu title="历史记录" @scrollToLower="onScrolltolower" :loading="isFetching">
  113. <template #top>
  114. <div class="create-btn px-[11px] pb-[22px]">
  115. <BaseButton @click="handleCreateDialog">新建对话</BaseButton>
  116. </div>
  117. </template>
  118. <div class="pr-[4px] text-[#5e5e5e]">
  119. <RecodeCardItem
  120. v-for="item in recordList"
  121. :key="item.sessionId"
  122. :title="item.question"
  123. :time="item.createTime"
  124. :data-item="item"
  125. @on-click="handleChatDetail"
  126. @on-delete="handeChatDelete"
  127. />
  128. </div>
  129. </TheSubMenu>
  130. <TheChatView ref="scrollRef">
  131. <ChatWelcome title="您好,我是LibraAI专家问答" card-title="您可以试着问我:"
  132. :sub-title="[
  133. '期待与您一同规划和完成未来的工作。有任何重点或需讨论的事项,随时告诉我。'
  134. ]"
  135. :card-content="recommendList"
  136. v-if="!chatDataSource.length"
  137. @on-click="handleWelcomeRecommend"
  138. />
  139. <div class="conversation-item" v-if="chatDataSource.length">
  140. <template v-for="item in chatDataSource" :key="item.id">
  141. <ChatAsk :content="item.question"></ChatAsk>
  142. <ChatAnswer :content="item.answer" :loading="item.loading" :delay-loading="item.delayLoading"></ChatAnswer>
  143. </template>
  144. </div>
  145. <template #footer>
  146. <ChatInput @on-click="handleSubmit" @on-enter="handleSubmit" v-model:loading="isLoading"></ChatInput>
  147. </template>
  148. </TheChatView>
  149. </section>
  150. </template>