index.vue 15 KB

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