WorkOrder.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <script setup>
  2. import { ref, unref, computed, onUnmounted } from 'vue';
  3. import { useMessage, NDatePicker } from 'naive-ui';
  4. import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
  5. import { ChatAsk, ChatAnswer } from '@/components/Chat';
  6. import { chatApi } from '@/api/chat';
  7. import {useInfinite, useScroll, useChat, useRecommend} from '@/composables';
  8. const ANSWER_ID_KEY = '@@id@@';
  9. let controller = new AbortController();
  10. const { recordList, isFetching, onScrolltolower, onReset, addHistoryRecord } = useInfinite('/front/bigModel/qa/pageList', { module: 1 });
  11. const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
  12. const { chatDataSource, addChat, updateChat, clearChat, updateById } = useChat();
  13. const message = useMessage();
  14. const reportDate = ref();
  15. const switchActive = ref(false);
  16. const isLoading = ref(false);
  17. const inputRef = ref(null);
  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. currenSessionId.value = null;
  30. clearChat();
  31. }
  32. // 查询对话详情
  33. const handleChatDetail = async ({ sessionId }) => {
  34. isLoading.value = false;
  35. controller.abort();
  36. const { data } = await chatApi.getAnswerHistoryDetail({ sessionId });
  37. chatDataSource.value = data.map(item => ({ ...item, loading: false, }));
  38. currenSessionId.value = sessionId;
  39. scrollToBottom();
  40. }
  41. const onRegenerate = async ({ question, realQuestion }) => {
  42. controller = new AbortController();
  43. const sessionId = unref(currenSessionId);
  44. const params = {
  45. data: {
  46. sessionId,
  47. showVal: question,
  48. question: realQuestion || question,
  49. module: 1,
  50. isStrong: Number(unref(switchActive)),
  51. reportDate: reportDate.value
  52. },
  53. signal: controller.signal,
  54. onDownloadProgress: ({ event }) => {
  55. const xhr = event.target;
  56. const { responseText } = xhr;
  57. const [ answer ] = responseText.split(ANSWER_ID_KEY);
  58. updateChat({
  59. sessionId,
  60. showVal:question,
  61. answer,
  62. loading: true,
  63. delayLoading: false,
  64. })
  65. scrollToBottomIfAtBottom();
  66. }
  67. }
  68. try {
  69. const { data } = await chatApi.getChatStream(params);
  70. const [ answer, id ] = data.split(ANSWER_ID_KEY);
  71. updateChat({
  72. id,
  73. sessionId,
  74. showVal: question,
  75. answer,
  76. loading: false,
  77. delayLoading: false
  78. })
  79. scrollToBottomIfAtBottom();
  80. }
  81. catch (error){
  82. console.log("取消了请求 - catch", error);
  83. }
  84. finally {
  85. isLoading.value = false;
  86. onReset();
  87. }
  88. }
  89. // 提交问题
  90. const handleSubmit = async (question, realQuestion = '') => {
  91. // 用于模拟 - 内容生成前置等待状态
  92. if (unref(isExistInHistory)) {
  93. const { data: sessionId } = await chatApi.getChatSessionTag();
  94. currenSessionId.value = sessionId;
  95. }
  96. isLoading.value = true;
  97. addChat({
  98. sessionId: unref(currenSessionId),
  99. showVal: question,
  100. realQuestion,
  101. answer: '',
  102. loading: true,
  103. delayLoading: true,
  104. })
  105. scrollToBottom();
  106. setTimeout(() => onRegenerate({ question, realQuestion }), 2 * 1000);
  107. }
  108. // 处理推荐问题
  109. const handleCreateOrder = () => {
  110. if ( !reportDate.value ) {
  111. return message.warning('请选择日期');
  112. }
  113. handleSubmit(`请生成${reportDate.value}智能工单分析报告`);
  114. }
  115. // 删除历史对话
  116. const handeChatDelete = async (id) => {
  117. await chatApi.deleteHistory(id);
  118. onReset();
  119. clearChat();
  120. message.success('删除成功');
  121. }
  122. onUnmounted(() => {
  123. controller.abort();
  124. })
  125. </script>
  126. <template>
  127. <section class="flex items-start h-full">
  128. <TheSubMenu title="智能工单" @scrollToLower="onScrolltolower" :loading="isFetching">
  129. <template #top>
  130. <div class="create-btn px-[11px] pb-[22px]">
  131. <BaseButton @click="handleCreateDialog">新建工单</BaseButton>
  132. </div>
  133. </template>
  134. <div class="pr-[4px] text-[#5e5e5e]">
  135. <RecodeCardItem v-for="item, index in recordList" :key="item.sessionId + index" :title="item.showVal"
  136. :time="item.createTime" :data-item="item" @on-click="handleChatDetail" @on-delete="handeChatDelete" />
  137. </div>
  138. </TheSubMenu>
  139. <TheChatView ref="scrollRef" :is-footer="false">
  140. <ChatWelcome
  141. title="您好,我是LibraAI智慧工单助手"
  142. :sub-title="[
  143. '基于大语言模型的智能工单分析助手,可以为您实现数据分析及数据解读',
  144. '选择日期并为您生成日报分析'
  145. ]"
  146. v-if="!chatDataSource.length"
  147. />
  148. <div class="conversation-item" v-if="chatDataSource.length">
  149. <template v-for="item in chatDataSource" :key="item.id">
  150. <ChatAsk :content="item.showVal" :sessionId="item.sessionId"></ChatAsk>
  151. <ChatAnswer
  152. :id="item.id"
  153. :content="item.answer"
  154. :loading="item.loading"
  155. :delay-loading="item.delayLoading"
  156. :isSatisfied="item.isSatisfied"
  157. @on-click-icon=" params => updateById(params)"
  158. ></ChatAnswer>
  159. </template>
  160. </div>
  161. <div class="order-container px-[60px] py-[30px] mt-[36px] rounded-[10px] bg-[#fff]" v-if="!chatDataSource.length">
  162. <div class="flex items-end justify-start space-x-[16px] pb-[20px] border-b-[1px] border-solid border-[#F1F1F1]">
  163. <span class="text-[20px] leading-[28px] font-bold">智慧工单报告</span>
  164. <span class="text-[12px] text-[#8F959C]">选择日期后为您生成日报工单</span>
  165. </div>
  166. <main class="order pt-[20px] text-[#1A2029]">
  167. <div class="flex items-center justify-start text-[16px] space-x-[16px]">
  168. <span class="font-bold">选择时间</span>
  169. <div class="w-[164px] border-[1px] border-[#EFEFF0] rounded-[8px] overflow-hidden">
  170. <NDatePicker placeholder="选择日期" :readonly="true" v-model:formatted-value="reportDate" value-format="yyyy-MM-dd">
  171. <template #date-icon>
  172. <SvgIcon name="tool-arrow-bottom"></SvgIcon>
  173. </template>
  174. </NDatePicker>
  175. </div>
  176. </div>
  177. <dl class="pt-[26px] text-[#1A2029] space-y-[16px]">
  178. <dt class="text-[16px] font-bold leading-[22px]">报告内容</dt>
  179. <dd>1、水质数据、生化数据情况</dd>
  180. <dd>2、各项指标数据分析</dd>
  181. <dd>3、对于工艺调整方面、数据趋势方面的措施与建议</dd>
  182. </dl>
  183. </main>
  184. <footer class="pt-[24px]">
  185. <button class="btn-primary" @click="handleCreateOrder">立即生成</button>
  186. </footer>
  187. </div>
  188. </TheChatView>
  189. </section>
  190. </template>
  191. <style scoped lang="scss">
  192. .n-input__input-el {
  193. font-size: 12px !important;
  194. }
  195. .list-item {
  196. margin-bottom: 16px;
  197. .title {
  198. font-size: 14px;
  199. font-weight: bold;
  200. line-height: 20px;
  201. }
  202. .item-top {
  203. display: flex;
  204. justify-content: space-between;
  205. margin-bottom: 10px;
  206. .num {
  207. font-size: 12px;
  208. font-weight: 400;
  209. color: #B0B7C0;
  210. }
  211. }
  212. }
  213. .btn-primary {
  214. width: 88px;
  215. height: 32px;
  216. border-radius: 6px;
  217. background-color: #2454FF;
  218. font-size: 14px;
  219. line-height: 32px;
  220. color: #fff;
  221. &:hover {
  222. background: #1D43CC;
  223. }
  224. }
  225. </style>
  226. <style lang="scss">
  227. .order {
  228. .n-input__input-el, .n-input__placeholder {
  229. font-size: 14px;
  230. }
  231. }
  232. </style>