index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <script setup lang="ts">
  2. import {
  3. IconSearch,
  4. IconTiktokColor,
  5. IconSend,
  6. IconClose,
  7. } from '@arco-design/web-vue/es/icon';
  8. import { useAppStore } from '@/store';
  9. import { computed, ref, onMounted, reactive, nextTick } from 'vue';
  10. import { Message } from '@arco-design/web-vue';
  11. import moment from 'moment';
  12. import AddSession from '@/views/session/sessionManager/components/addSession.vue';
  13. import {
  14. sessionListApi,
  15. deleteSessionApi,
  16. getSessionDetailsApi,
  17. chatApi,
  18. } from '@/api/session';
  19. const sessionDetailList = ref([]); //根据会话id出来的会话详情
  20. const sessionList = ref([]); //会话列表
  21. const modalObj = reactive({ add: false });
  22. const currIndex = ref(0);
  23. const displayedText = ref(''); // 正在显示的文字
  24. let timer: number | null = null;
  25. const streamStr = ref('');
  26. const inputMsg = ref('');
  27. const activeSessionId = ref('');
  28. const sendMessage = async (event) => {
  29. event.preventDefault();
  30. if (!activeSessionId.value) {
  31. Message.warning('请选择会话');
  32. return;
  33. }
  34. if (inputMsg.value) {
  35. const { code, data } = await chatApi({
  36. conversation_id: activeSessionId.value,
  37. messages: inputMsg.value,
  38. });
  39. const res = await getSessionDetailsApi(activeSessionId.value);
  40. if (res.code === 200) {
  41. sessionDetailList.value = res.data.message.map((item, index) => {
  42. if (index === res.data.message.length - 1) {
  43. item.role = 'last';
  44. displayedText.value = '';
  45. currIndex.value = 0;
  46. streamStr.value = item.content;
  47. startDisplayStr();
  48. }
  49. return item;
  50. });
  51. refreshScroll();
  52. }
  53. inputMsg.value = '';
  54. } else {
  55. Message.warning('消息不能为空');
  56. }
  57. };
  58. const querySessionDetail = async (session) => {
  59. activeSessionId.value = session.id;
  60. const { code, data } = await getSessionDetailsApi(session.id);
  61. if (code === 200) {
  62. sessionDetailList.value = data.message;
  63. refreshScroll(); //刷新滚动条位置
  64. }
  65. };
  66. const scrollbar = ref(null);
  67. const refreshScroll = () => {
  68. nextTick(() => {
  69. const container = document.getElementById('home');
  70. scrollbar.value.scrollTop(container.scrollHeight);
  71. });
  72. };
  73. // 查询会话列表
  74. const querySessionList = async () => {
  75. const { code, data } = await sessionListApi();
  76. if (code === 200) {
  77. sessionList.value = data;
  78. } else {
  79. Message.warning('查询失败');
  80. }
  81. };
  82. //新增会话之后刷新会话列表
  83. const addSession = () => {
  84. querySessionList();
  85. };
  86. onMounted(() => {
  87. querySessionList();
  88. });
  89. const appStore = useAppStore();
  90. const theme = computed(() => {
  91. return appStore.theme;
  92. });
  93. //文字动态输出
  94. const startDisplayStr = () => {
  95. if (timer) {
  96. clearTimeout(timer!);
  97. }
  98. const res = streamStr.value;
  99. // 将数组中的字符串拼接起来
  100. if (currIndex.value < res.length) {
  101. displayedText.value += res[currIndex.value];
  102. currIndex.value++;
  103. setTimeout(startDisplayStr, 100);
  104. } else {
  105. clearTimeout(timer!);
  106. timer = null;
  107. }
  108. };
  109. </script>
  110. <template>
  111. <div class="container">
  112. <AddSession :modalObj="modalObj" @addSession="addSession"></AddSession>
  113. <a-card class="top-title">AI会话记录</a-card>
  114. <a-row :gutter="[5, 5]" style="margin-top: 3px">
  115. <a-col :span="4">
  116. <a-card style="height: 60px">
  117. <template #cover>
  118. <div style="display: flex; justify-content: space-between">
  119. <a-button
  120. type="primary"
  121. shape="round"
  122. class="card-btn-1"
  123. @click="modalObj.add = true"
  124. >
  125. +新建会话
  126. </a-button>
  127. <a-button type="text" shape="circle" class="card-btn-2">
  128. <icon-search />
  129. </a-button>
  130. </div>
  131. </template>
  132. </a-card>
  133. <a-card class="left">
  134. <a-scrollbar
  135. class="left-list"
  136. style="
  137. height: calc(100vh - 160px);
  138. overflow-y: auto;
  139. overflow-x: hidden;
  140. "
  141. >
  142. <div
  143. class="item"
  144. v-for="session in sessionList"
  145. @click="querySessionDetail(session)"
  146. :class="{ isLeftActive: activeSessionId === session.id }"
  147. >
  148. <div class="text" :class="{ light: theme === 'dark' }">{{
  149. session.name
  150. }}</div>
  151. <div class="time">{{
  152. moment(new Date(session.create_time)).format(
  153. 'YYYY-MM-DD HH:mm:ss'
  154. )
  155. }}</div>
  156. </div>
  157. </a-scrollbar>
  158. </a-card>
  159. </a-col>
  160. <a-col :span="15">
  161. <a-card class="center">
  162. <div v-if="sessionDetailList.length === 0">
  163. <div class="center-title">智能问答</div>
  164. <div class="center-content">
  165. 我可以理解和学习人类的语言,具备多轮对话的能力,现在和我开始交流吧~
  166. </div>
  167. <div class="center-question">
  168. <div class="center-question-left">试一试这样问我</div>
  169. <div class="center-question-right">
  170. <a-button type="primary">换一换</a-button>
  171. </div>
  172. </div>
  173. <a-row justify="space-around" class="center-list">
  174. <a-col :span="7" class="item">
  175. <div class="item-title">
  176. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  177. </div>
  178. <div class="item-content" :class="{ dark: theme === 'dark' }">
  179. 编写引人注目且具有说服力的、适用于产品的...
  180. </div>
  181. </a-col>
  182. <a-col :span="7" class="item">
  183. <div class="item-title">
  184. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  185. </div>
  186. <div class="item-content" :class="{ dark: theme === 'dark' }">
  187. 编写引人注目且具有说服力的、适用于产品的...
  188. </div>
  189. </a-col>
  190. <a-col :span="7" class="item">
  191. <div class="item-title">
  192. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  193. </div>
  194. <div class="item-content" :class="{ dark: theme === 'dark' }">
  195. 编写引人注目且具有说服力的、适用于产品的...
  196. </div>
  197. </a-col>
  198. <a-col :span="7" class="item">
  199. <div class="item-title">
  200. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  201. </div>
  202. <div class="item-content" :class="{ dark: theme === 'dark' }">
  203. 编写引人注目且具有说服力的、适用于产品的...
  204. </div>
  205. </a-col>
  206. <a-col :span="7" class="item">
  207. <div class="item-title">
  208. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  209. </div>
  210. <div class="item-content" :class="{ dark: theme === 'dark' }">
  211. 编写引人注目且具有说服力的、适用于产品的...
  212. </div>
  213. </a-col>
  214. <a-col :span="7" class="item">
  215. <div class="item-title">
  216. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  217. </div>
  218. <div class="item-content" :class="{ dark: theme === 'dark' }">
  219. 编写引人注目且具有说服力的、适用于产品的...
  220. </div>
  221. </a-col>
  222. </a-row>
  223. </div>
  224. <a-scrollbar
  225. ref="scrollbar"
  226. id="home"
  227. v-else
  228. class="chat-list"
  229. style="width: 90%; overflow: auto; height: 60vh; margin: 0px auto"
  230. >
  231. <div class="chat-item" v-for="sessionDetail in sessionDetailList">
  232. <a-comment
  233. v-if="sessionDetail.role === 'user'"
  234. avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
  235. >
  236. <template #content>
  237. <div :class="{ light: theme === 'light' }">{{
  238. sessionDetail.content
  239. }}</div>
  240. </template>
  241. </a-comment>
  242. <a-comment
  243. v-else-if="sessionDetail.role === 'assistant'"
  244. avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp"
  245. >
  246. <template #content>
  247. <a-card
  248. class="chat-item-answer"
  249. style="background-color: rgba(63, 64, 79, 1)"
  250. >
  251. <div :class="{ light: theme === 'light' }">{{
  252. sessionDetail.content
  253. }}</div>
  254. </a-card>
  255. </template>
  256. </a-comment>
  257. <a-comment
  258. v-else-if="sessionDetail.role === 'last'"
  259. avatar="https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/9eeb1800d9b78349b24682c3518ac4a3.png~tplv-uwbnlip3yd-webp.webp"
  260. >
  261. <template #content>
  262. <a-textarea
  263. readonly
  264. auto-size
  265. v-model="displayedText"
  266. class="chat-item-answer"
  267. style="background-color: rgba(63, 64, 79, 1)"
  268. >
  269. </a-textarea>
  270. </template>
  271. </a-comment>
  272. </div>
  273. </a-scrollbar>
  274. <div class="center-bottom">
  275. <a-textarea
  276. v-model="inputMsg"
  277. @keydown.shift.enter="sendMessage"
  278. style="height: 180px"
  279. placeholder="输入您想了解的内容,Shift+Enter发送"
  280. :max-length="500"
  281. allow-clear
  282. show-word-limit
  283. >
  284. </a-textarea>
  285. </div>
  286. </a-card>
  287. </a-col>
  288. <a-col :span="5">
  289. <a-card class="right">
  290. <div class="right-top">
  291. <div class="right-title">数智库</div>
  292. <div class="right-btn">
  293. <a-button type="outline" shape="circle" style="border: none">
  294. <icon-search />
  295. </a-button>
  296. <a-button
  297. type="outline"
  298. shape="circle"
  299. style="border: none; margin-left: -10px"
  300. >
  301. <icon-close />
  302. </a-button>
  303. </div>
  304. </div>
  305. <div class="right-tag">
  306. <a-space>
  307. <a-button type="primary" shape="round" size="mini" class="btn"
  308. >全部</a-button
  309. >
  310. <a-button type="outline" shape="round" size="mini" class="btn"
  311. >文档创作</a-button
  312. >
  313. <a-button type="outline" shape="round" size="mini" class="btn"
  314. >知识学习</a-button
  315. >
  316. </a-space>
  317. <a-space style="margin-top: 10px">
  318. <a-button type="outline" shape="round" size="mini" class="btn"
  319. >效率提升</a-button
  320. >
  321. </a-space>
  322. </div>
  323. <div class="right-list">
  324. <div class="right-item">
  325. <div class="item-title">
  326. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  327. </div>
  328. <div class="item-content" :class="{ dark: theme === 'dark' }">
  329. 编写引人注目且具有说服力的、适用于产品的...
  330. </div>
  331. </div>
  332. <div class="right-item">
  333. <div class="item-title">
  334. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  335. </div>
  336. <div class="item-content" :class="{ dark: theme === 'dark' }">
  337. 编写引人注目且具有说服力的、适用于产品的...
  338. </div>
  339. </div>
  340. <div class="right-item">
  341. <div class="item-title">
  342. <IconTiktokColor></IconTiktokColor>抖音带货脚本
  343. </div>
  344. <div class="item-content" :class="{ dark: theme === 'dark' }">
  345. 编写引人注目且具有说服力的、适用于产品的...
  346. </div>
  347. </div>
  348. </div>
  349. </a-card>
  350. </a-col>
  351. </a-row>
  352. </div>
  353. </template>
  354. <style scoped lang="scss">
  355. .isLeftActive {
  356. background-color: lightgrey;
  357. }
  358. .light {
  359. color: white !important;
  360. }
  361. .dark {
  362. color: gray !important;
  363. }
  364. .container {
  365. .top-title {
  366. line-height: 60px;
  367. font-size: 25px;
  368. font-family: 黑体;
  369. }
  370. .center,
  371. .right {
  372. box-sizing: border-box;
  373. height: calc(100vh - 100px);
  374. }
  375. .left {
  376. /* height: calc(100vh - 160px);
  377. overflow-y: auto;
  378. overflow-x: hidden;*/
  379. border: 0px;
  380. .left-list {
  381. .item {
  382. cursor: pointer;
  383. .text,
  384. .time {
  385. line-height: 30px;
  386. }
  387. .text {
  388. color: black;
  389. padding-left: 10px;
  390. }
  391. .time {
  392. color: gray;
  393. font-size: 12px;
  394. padding-left: 10px;
  395. }
  396. }
  397. }
  398. }
  399. .card-btn-1 {
  400. margin: 10px;
  401. width: 75%;
  402. }
  403. .card-btn-2 {
  404. margin: 10px 10px;
  405. }
  406. .center {
  407. position: relative;
  408. .center-title {
  409. line-height: 60px;
  410. font-size: 25px;
  411. font-family: 黑体;
  412. color: deepskyblue;
  413. }
  414. .center-content {
  415. font-size: 14px;
  416. color: gray;
  417. }
  418. .center-question {
  419. margin-top: 20px;
  420. display: flex;
  421. justify-content: space-between;
  422. .center-question-left {
  423. margin-top: 5px;
  424. margin-left: 20px;
  425. }
  426. .center-question-right {
  427. margin-right: 20px;
  428. }
  429. }
  430. .center-list {
  431. margin-top: 10px;
  432. .item {
  433. border-radius: 10px;
  434. margin-top: 10px;
  435. padding: 10px;
  436. height: 120px;
  437. background-color: lightcyan;
  438. .item-title {
  439. text-align: center;
  440. line-height: 40px;
  441. font-size: 20px;
  442. font-family: 黑体;
  443. color: black;
  444. }
  445. }
  446. }
  447. .center-bottom {
  448. position: absolute;
  449. width: 90%;
  450. bottom: 20px;
  451. left: 5%;
  452. }
  453. }
  454. .right {
  455. .right-top {
  456. display: flex;
  457. justify-content: space-between;
  458. .right-title {
  459. font-size: 25px;
  460. font-family: 黑体;
  461. color: black;
  462. }
  463. .right-btn {
  464. position: relative;
  465. left: 20px;
  466. top: 0px;
  467. }
  468. }
  469. .right-tag {
  470. margin-top: 20px;
  471. }
  472. .right-list {
  473. .right-item {
  474. border-radius: 10px;
  475. margin-top: 10px;
  476. padding: 10px;
  477. height: 120px;
  478. background-color: lightcyan;
  479. .item-title {
  480. text-align: center;
  481. line-height: 40px;
  482. font-size: 20px;
  483. font-family: 黑体;
  484. color: black;
  485. }
  486. }
  487. }
  488. }
  489. }
  490. </style>