ChatPC.vue 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816
  1. <template>
  2. <div class="chat bg-white">
  3. <div class="flex items-center h-screen relative">
  4. <div class="leftInfo relative" v-show="!showNodata">
  5. <div class="history">
  6. <!-- historyActive -->
  7. <div class="historyBox" v-for="(item, index) in allRecords" :key="index">
  8. <div class="top flex w-full cursor-pointer" @click="item.open = !item.open">
  9. <div class="photo">
  10. <img class="object-cover" :src="baseApi + item.picture" @error="$AIPohotoError" alt="">
  11. </div>
  12. <div class="info flex-1 ml-2 flex flex-col justify-around">
  13. <div class="text-sm font-semibold">{{ item.characterName }}</div>
  14. <div class="text-sm text-gray-400">{{ item.prologue }}</div>
  15. </div>
  16. <div class="flex items-center text-lg" style="height: 48px;">
  17. <i v-if="!item.open" class="el-icon-arrow-down" style="font-size: 16px;"></i>
  18. <i v-else class="el-icon-arrow-up" style="font-size: 16px;"></i>
  19. </div>
  20. </div>
  21. <!-- <transition name="el-zoom-in-top"> -->
  22. <div class="dialogueList mt-4" v-show="item.open">
  23. <div class="flex justify-between mb-2">
  24. <el-button v-if="item.isDelete == 0" type="primary" class="flex-1" round
  25. @click="newStart_juese(item)">新的对话</el-button>
  26. <div v-else class="flex-1 rounded border border-gray-200 text-sm p-2 text-gray-600">
  27. {{ item.characterName }}已经被设定为私有或被创建者删除。
  28. </div>
  29. <el-dropdown class="flex justify-end items-center ml-2" trigger="click"
  30. @command="handleCommand2($event, item)">
  31. <div class=" w-8 h-8 bg-gray-100 rounded flex justify-center items-center cursor-pointer">
  32. <i slot="reference" class="el-icon-more"></i>
  33. </div>
  34. <el-dropdown-menu slot="dropdown">
  35. <el-dropdown-item command="2">删除</el-dropdown-item>
  36. </el-dropdown-menu>
  37. </el-dropdown>
  38. </div>
  39. <div
  40. class="dialogue flex justify-between items-center w-full h-16 rounded-md px-2 mb-2 cursor-pointer relative"
  41. :class="info.id == item.id && index2 == recordsIndex && 'historyActive'"
  42. v-for="(item2, index2) in item.chatCharacterList" :key="index2"
  43. @click.self="clickDialogue(item, item2, index2)">
  44. <span @click.self="clickDialogue(item, item2, index2)">
  45. <i v-if="item2.sceneId || item2.sceneIcon" class="el-icon-video-camera"></i>
  46. {{ item2.chatTitle }}
  47. </span>
  48. <span @click.self="clickDialogue(item, item2, index2)" class=" text-sm text-gray-500">{{ item2.createTime
  49. }}</span>
  50. <el-dropdown class="dialogueIconBg absolute h-16 w-20 right-0 flex justify-end items-center pr-4"
  51. trigger="click" @command="handleCommand($event, item, item2)">
  52. <div>
  53. <i slot="reference" class="el-icon-more"></i>
  54. </div>
  55. <el-dropdown-menu slot="dropdown">
  56. <el-dropdown-item command="1">修改标题</el-dropdown-item>
  57. <el-dropdown-item command="2">删除</el-dropdown-item>
  58. </el-dropdown-menu>
  59. </el-dropdown>
  60. </div>
  61. </div>
  62. <!-- </transition> -->
  63. </div>
  64. </div>
  65. <!-- 角色详情 -->
  66. <div class="chatInfo absolute top-0 left-0 w-full h-full z-30 bg-white" v-show="showInfo == 1">
  67. <div class="flex flex-col p-4 h-full">
  68. <div class="flex justify-between items-center text-base mb-3">
  69. <div>角色详情</div>
  70. <i class="el-icon-close cursor-pointer" @click="showInfoChange(0)"></i>
  71. </div>
  72. <div class="flex">
  73. <span class="chatInfo_photo">
  74. <img class="w-16 h-16 rounded-full overflow-hidden object-cover" :src="baseApi + info.picture"
  75. @error="$AIPohotoError">
  76. </span>
  77. <div class="w-0 flex-1 ml-2">
  78. <div class="text-xl font-medium">{{ info.characterName }}</div>
  79. <div class="mt-1 text-gray-500 text-sm">Xchat No.{{ info.id }}</div>
  80. </div>
  81. </div>
  82. <div class="flex mt-4">
  83. <div class="hot flex w-auto items-center px-2 py-1 text-sm mr-2 rounded-full">
  84. <v-icon name="fire" scale="1" />
  85. <span class="ml-1">{{ info.hotCount }}</span>
  86. </div>
  87. </div>
  88. <div class="flex justify-around mt-4">
  89. <div class=" rounded-full w-32 text-center py-2 text-sm cursor-pointer" style="border: solid 1px #000">分享
  90. </div>
  91. <div class=" rounded-full w-32 text-center py-2 text-sm cursor-pointer" style="border: solid 1px #000"
  92. @click="showDetail = !showDetail">角色详情</div>
  93. </div>
  94. <transition name="el-zoom-in-top">
  95. <div v-show="showDetail">
  96. <div class="mt-5 text-base pb-3 border-b border-gray-200">
  97. <div>
  98. <i class="el-icon-document"></i>
  99. 简介
  100. </div>
  101. <div class="mt-3 text-sm max-h-48">
  102. {{ info.prologue }}
  103. </div>
  104. </div>
  105. <div class="flex justify-between items-center mt-5 text-base pb-3 border-b border-gray-200">
  106. <div>
  107. <i class="el-icon-user"></i>
  108. 创作人
  109. </div>
  110. <div class="text-sm max-h-48">
  111. {{ info.author }}
  112. </div>
  113. </div>
  114. <div class="flex justify-between items-center mt-5 text-base pb-3 border-b border-gray-200">
  115. <div>
  116. <i class="el-icon-time"></i>
  117. 创建时间
  118. </div>
  119. <div class="text-sm max-h-48">
  120. {{ info.createTime }}
  121. </div>
  122. </div>
  123. <div class="mt-5 text-base">
  124. <i class="el-icon-discount"></i>
  125. 特征
  126. </div>
  127. <div class="tags mt-4 pb-3">
  128. <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
  129. {{ item }}
  130. </span>
  131. </div>
  132. </div>
  133. </transition>
  134. <div class="mt-5 text-base pb-3 flex-1 flex flex-col">
  135. <div>
  136. <!-- <i class="el-icon-document"></i> -->
  137. 查看场景
  138. </div>
  139. <div class="mt-3 text-sm max-h-48">
  140. <div v-if="info.sceneList && info.sceneList.length > 0"
  141. class="grid grid-cols-2 gap-2 mb-4 cursor-pointer">
  142. <template v-for="(item, index) in info.sceneList">
  143. <div v-if="item.isDelete == 0" class="flex flex-col justify-center" :key="index"
  144. @click="sceneChange(item)">
  145. <img class=" rounded w-full h-24 object-cover" :src="baseApi + item.background" alt="">
  146. <div>{{ item.sceneName }}</div>
  147. <div class=" text-gray-400">{{ item.sceneDescription }}</div>
  148. </div>
  149. </template>
  150. </div>
  151. <el-empty v-else description="暂无场景"></el-empty>
  152. <div class="flex justify-center mt-10">
  153. <el-button type="primary" round @click="toCreateScene">创建场景</el-button>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. <!-- 场景详情 -->
  160. <div class="absolute top-0 left-0 w-full h-full z-30 bg-white overflow-hidden" v-show="showInfo == 2">
  161. <div class="flex flex-col p-4 h-full">
  162. <div class="flex justify-between items-center text-base mb-3">
  163. <div>场景详情</div>
  164. <i class="el-icon-close cursor-pointer" @click="showInfoChange(0)"></i>
  165. </div>
  166. <div class=" text-gray-500">{{ sceneInfo.sceneDescription }}</div>
  167. </div>
  168. </div>
  169. </div>
  170. <div class="content" :class="fullScreen && 'contentFull'" v-show="!showNodata"
  171. :style="{ backgroundImage: `url(${baseApi}${sceneInfo.background})` }">
  172. <!-- <div class="leftImg">
  173. <img :src="baseApi + info.picture" alt="">
  174. <div class="aiInfo">
  175. <span class="photo">
  176. <img :src="baseApi + info.picture">
  177. </span>
  178. <div class="info">
  179. <div class="name">{{ info.characterName }}</div>
  180. <div class="tags">
  181. <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
  182. {{ item }}
  183. </span>
  184. </div>
  185. <div class="infoContent">{{ info.prologue }}</div>
  186. </div>
  187. </div>
  188. </div> -->
  189. <div class="fullScreenButton rounded-full border border-gray-200" @click="clickFullScreen">
  190. <i class="el-icon-arrow-left" v-if="!fullScreen"></i>
  191. <i class="el-icon-arrow-right" v-else></i>
  192. </div>
  193. <div class="chat-box" :class="sceneId && 'mask'">
  194. <div class="chatBoxTitle absolute top-0">
  195. <div>
  196. {{ info.characterName }}
  197. <el-dropdown v-if="sceneId" class="flex justify-end items-center" trigger="click" @command="handleCommand3"
  198. @visible-change="dropdown3Show = !dropdown3Show">
  199. <div>
  200. <div slot="reference" class="flex justify-center items-center cursor-pointer text-white">
  201. 已进入场景:{{ sceneInfo.sceneName }}
  202. <i class="el-icon-arrow-up ml-2" style="font-size: 16px;" v-if="dropdown3Show"></i>
  203. <i class="el-icon-arrow-down ml-2" style="font-size: 16px;" v-else></i>
  204. </div>
  205. </div>
  206. <el-dropdown-menu slot="dropdown">
  207. <el-dropdown-item command="1">场景详情</el-dropdown-item>
  208. <el-dropdown-item command="2">新场景对话</el-dropdown-item>
  209. <el-dropdown-item command="3">回到常规对话</el-dropdown-item>
  210. </el-dropdown-menu>
  211. </el-dropdown>
  212. </div>
  213. <div class="flex w-60 justify-end">
  214. <el-tooltip effect="dark" content="设定" placement="bottom">
  215. <el-popover placement="bottom" width="400" trigger="click">
  216. <div class="p-2">
  217. <div class="flex justify-between mb-4">
  218. <span>语音自动播放</span>
  219. <el-switch v-model="setting.value1" @change="ChangeSettingValue1">
  220. </el-switch>
  221. </div>
  222. <div class="flex justify-between">
  223. <span>全屏幕</span>
  224. <el-switch v-model="fullScreen">
  225. </el-switch>
  226. </div>
  227. </div>
  228. <i slot="reference" class="fa-solid fa-sliders cursor-pointer text-xl mr-6"></i>
  229. </el-popover>
  230. </el-tooltip>
  231. <el-tooltip effect="dark" content="角色详情" placement="bottom">
  232. <i class="fa-solid fa-file-invoice cursor-pointer text-xl mr-6" @click="showInfoChange(1)"></i>
  233. </el-tooltip>
  234. <el-tooltip v-if="!sceneId" effect="dark" content="开始新的对话" placement="bottom">
  235. <i class="fa-solid fa-comment-medical cursor-pointer text-xl mr-6" @click="newStart"></i>
  236. </el-tooltip>
  237. <el-tooltip effect="dark" content="历史对话" placement="bottom">
  238. <i class="fa-solid fa-clock-rotate-left cursor-pointer text-xl" @click="showInfoChange(0)"></i>
  239. </el-tooltip>
  240. </div>
  241. </div>
  242. <audio ref="audio" muted v-show="false" :src="audioUrl" autoplay="" @ended="audioEnd" controls></audio>
  243. <div class="messages" ref="messages">
  244. <template v-for="(item, index) in returnMessage">
  245. <div v-if="item.role == 'assistant'" class="mb-4 flex" :key="index">
  246. <!-- ai返回的信息 -->
  247. <div class="pt-2 photo">
  248. <img :src="baseApi + info.picture" class="rounded-full w-14 h-14 object-cover"
  249. @error="$AIPohotoError" />
  250. </div>
  251. <div class="messageRight">
  252. <div v-show="!item.edit" :ref="`message${index}`"
  253. class="message mt-4 ml-4 p-4 rounded-r-md rounded-bl-md text-xs relative">
  254. <div class="yyPlayBg mb-2 w-16 p-1 cursor-pointer">
  255. <img v-if="audioPlayIndex == index" class="yyPlay" src="@/assets/images/播放/yyPlay1.png" alt="">
  256. <img v-else class="yyPlay" src="@/assets/images/播放/yyPlay.png" alt=""
  257. @click="historyGetVoice(item, index)">
  258. <!-- <svg-icon class="icon" class-name="speech-icon" icon-class="speech" @click.stop="click" /> -->
  259. </div>
  260. <div v-show="!item.content" class="loadingMessage">
  261. <div v-loading="!item.content" element-loading-background="rgba(0, 0, 0, 0.0)"></div>
  262. </div>
  263. <p style="white-space: pre-line;" v-show="item.content">{{ item.content }}</p>
  264. <div class="messageOptions" v-show="returnMessage.length == 1">
  265. <div class="option" v-for="(item, index) in messageOptions" :key="index"
  266. @click="messageOptionClick(item.chatGuidance)">
  267. {{ item.chatGuidance }}
  268. </div>
  269. </div>
  270. <el-dropdown v-if="index != 0" class="messageDropdown" @command="handleCommand4($event, item, index)"
  271. placement="top-end">
  272. <el-button class="messageButton" icon="el-icon-more" circle></el-button>
  273. <el-dropdown-menu slot="dropdown">
  274. <el-dropdown-item command="1">编辑</el-dropdown-item>
  275. <el-dropdown-item command="2">删除</el-dropdown-item>
  276. <el-dropdown-item v-if="index == returnMessage.length - 1" command="3">重新生成</el-dropdown-item>
  277. </el-dropdown-menu>
  278. </el-dropdown>
  279. </div>
  280. <div :ref="`messageEdit${index}`" v-show="item.edit"
  281. class="message messageEdit mt-4 ml-4 p-4 rounded-r-md rounded-bl-md text-xs relative">
  282. <el-input class="eidtTextarea" :ref="`messageEditInput${index}`" type="textarea" resize="none"
  283. v-model="item.editContent">
  284. </el-input>
  285. <div class="flex justify-end mt-2">
  286. <el-button @click="editCancel(item, index)">取消</el-button>
  287. <el-button @click="editSave(item, index)">保存</el-button>
  288. </div>
  289. </div>
  290. </div>
  291. </div>
  292. <!-- 用户发送的信息 -->
  293. <div v-if="item.role == 'user'" class="mb-4 flex me" :key="index">
  294. <div class="pt-2 photo">
  295. <img :src="$store.state.user.avatar" class="rounded-full w-14 h-14 object-cover"
  296. @error="$userPohotoError" />
  297. </div>
  298. <div class="messageRight">
  299. <div :ref="`message${index}`" v-show="!item.edit"
  300. class="messageUser mr-4 mt-4 p-4 rounded-l-md rounded-br-md text-xs relative">
  301. {{ item.content }}
  302. <el-dropdown v-if="index != 0" class="messageDropdown" @command="handleCommand4($event, item, index)"
  303. placement="top-end">
  304. <el-button class="messageButton" icon="el-icon-more" circle></el-button>
  305. <el-dropdown-menu slot="dropdown">
  306. <el-dropdown-item command="1">编辑</el-dropdown-item>
  307. <el-dropdown-item command="2">删除</el-dropdown-item>
  308. </el-dropdown-menu>
  309. </el-dropdown>
  310. </div>
  311. <div :ref="`messageEdit${index}`" v-show="item.edit"
  312. class="messageUser messageEdit mt-4 ml-4 p-4 rounded-r-md rounded-bl-md text-xs relative">
  313. <el-input class="eidtTextarea" :ref="`messageEditInput${index}`" type="textarea" resize="none"
  314. v-model="item.editContent">
  315. </el-input>
  316. <div class="flex justify-end mt-2">
  317. <el-button @click="editCancel(item, index)">取消</el-button>
  318. <el-button @click="editSave(item, index)">保存</el-button>
  319. </div>
  320. </div>
  321. </div>
  322. </div>
  323. </template>
  324. </div>
  325. <div class="inputBox absolute left-2 right-2 bottom-4 " @click="inputBoxClick">
  326. <input v-if="canChat" ref="input" type="text" placeholder="输入消息..."
  327. class="w-full flex-1 py-2 px-4 rounded-l-lg text-sm focus:outline-none" v-model="content"
  328. @keydown="Enterkey" />
  329. <div class="flex pt-6 justify-between" v-if="canChat" style="height: 70px;">
  330. <div class="tools w-36 flex justify-between">
  331. <i class="el-icon-setting text-2xl cursor-pointer" @click="showConfig = true"></i>
  332. <!-- <i class="el-icon-picture-outline text-2xl cursor-pointer"></i> -->
  333. <!-- <i class="el-icon-s-opportunity text-2xl cursor-pointer"></i> -->
  334. </div>
  335. <el-button type="text" :disabled="!content" :loading="notChatSend" @click.prevent="getStreamChatWithWeb"
  336. class="px-4 rounded-r-lg text-sm text-gray-400" :class="content && 'planeColor'">
  337. <v-icon v-show="!notChatSend" name="paper-plane" scale="1.5" />
  338. </el-button>
  339. </div>
  340. <div v-if="!canChat" class=" flex w-full text-sm text-gray-400">
  341. 当前角色或场景已被设定为私有或被创作者删除,但你任然可以查看历史对话。
  342. </div>
  343. </div>
  344. </div>
  345. </div>
  346. <div v-show="showNodata" class=" w-full h-full flex flex-col justify-center items-center">
  347. <el-empty description="暂无聊天记录,点击按钮并探索新角色。"></el-empty>
  348. <el-button type="primary" round @click="$router.push('/')">寻找一个角色</el-button>
  349. </div>
  350. <el-dialog title="配置" :visible.sync="showConfig" width="500px">
  351. <!-- <p class="text-base">身分</p>
  352. <p class="text-sm text-gray-400">选择你和角色聊天的身份</p>
  353. <div class="flex items-start py-3 border-b border-gray-200">
  354. <el-radio-group class="radio" v-model="configForm.radio1">
  355. <el-radio-button label="上海"></el-radio-button>
  356. </el-radio-group>
  357. <el-button>
  358. <i class="el-icon-sort" style="transform: rotate(90deg);"></i>
  359. </el-button>
  360. </div> -->
  361. <p class="text-base mt-3">选择模型</p>
  362. <p class="text-sm text-gray-400">每个模型可能有不同的效果,仅对当前对话有影响</p>
  363. <div class="flex items-start py-3 border-b border-gray-200">
  364. <el-radio-group class="radio" v-model="configForm.radio2">
  365. <el-radio-button v-for="(item, index) in modelList" :key="index" :label="item.id">{{ item.model
  366. }}</el-radio-button>
  367. </el-radio-group>
  368. </div>
  369. <!-- <p class="text-base mt-3">角色语言</p>
  370. <p class="text-sm text-gray-400">请选择角色的回复语言</p>
  371. <div class="flex items-start py-3">
  372. <el-radio-group class="radio" v-model="configForm.radio3">
  373. <el-radio-button label="自动"></el-radio-button>
  374. <el-radio-button label="简体中文"></el-radio-button>
  375. <el-radio-button label="繁體中文"></el-radio-button>
  376. <el-radio-button label="English"></el-radio-button>
  377. <el-radio-button label="Español"></el-radio-button>
  378. </el-radio-group>
  379. </div> -->
  380. <span slot="footer" class="dialog-footer">
  381. <el-button @click="showConfig = false">取 消</el-button>
  382. <el-button type="primary" @click="configConfirm">确 定</el-button>
  383. </span>
  384. </el-dialog>
  385. </div>
  386. </div>
  387. </template>
  388. <script>
  389. import Cookies from 'js-cookie'
  390. import {
  391. streamChatWithWebApi,
  392. getGuidanceApi,
  393. getModelListApi,
  394. addChatApi,
  395. getChatCharacterRecordsApi,
  396. getChatRecordApi,
  397. updateTitleApi,
  398. clearRecordApi,
  399. queryVoiceListApi,
  400. updateRecordApi,
  401. deleteRecordApi
  402. } from "@/api/chat.js"
  403. import { detailApi } from "@/api/detail.js"
  404. import { queryUserBalanceApi } from "@/api/user.js"
  405. import { Message, MessageBox, Notification, Loading } from 'element-ui'
  406. import 'vue-awesome/icons/paper-plane'
  407. import 'vue-awesome/icons/fire'
  408. export default {
  409. components: {
  410. },
  411. data() {
  412. return {
  413. // 对话请求超时参数
  414. timeOut: null,
  415. // 聊天等待
  416. chatLoading: false,
  417. // 是否全屏
  418. fullScreen: false,
  419. // 角色所有聊天列表
  420. allRecords: [],
  421. // 聊天记录id
  422. recordId: null,
  423. // 选中的角色聊天记录
  424. historyRes: [],
  425. recordsIndex: null,
  426. showDetail: true,
  427. showConfig: false,
  428. showInfo: 0,
  429. messageOptions: [],
  430. info: {},
  431. messageLoading: false,
  432. returnMessage: [],
  433. content: '',
  434. audioPlayIndex: null,
  435. configForm: {
  436. radio1: null,
  437. radio2: null,
  438. radio3: null,
  439. },
  440. // 对话接口是否返回错误状态
  441. resError: false,
  442. // 模型列表
  443. modelList: [],
  444. setting: {
  445. value1: false,
  446. value2: false,
  447. },
  448. //音频相关↓
  449. //音频地址
  450. audioUrl: '',
  451. audioUrlArr: [],
  452. audioUrlArrIndx: 0,
  453. videoLoop: null,
  454. videoLoopTime: 0,
  455. loopPlay: false,
  456. //场景下拉菜单
  457. dropdown3Show: false,
  458. // 场景对话相关
  459. sceneId: null,
  460. sceneInfo: {},
  461. }
  462. },
  463. computed: {
  464. canChat() {
  465. console.log(this.info, 'this.info');
  466. if (!this.sceneId) {
  467. if (this.info.isDelete == 1) {
  468. console.log(1);
  469. return false
  470. } else {
  471. console.log(2);
  472. return true
  473. }
  474. } else {
  475. if (this.sceneInfo.isDelete == 1) {
  476. return false
  477. } else {
  478. return true
  479. }
  480. }
  481. },
  482. notChatSend() {
  483. console.log(this.audioPlayIndex, 'this.audioPlayIndex>>>>>>>>>>>');
  484. return this.chatLoading != false || this.audioPlayIndex != null
  485. },
  486. showNodata() {
  487. return this.allRecords.length == 0 && !this.$route.query.characterId
  488. }
  489. },
  490. mounted() {
  491. this.init()
  492. this.$nextTick(() => {
  493. console.log(this.$refs.audio.paused, '>>>>>>>>>>>>>');
  494. })
  495. },
  496. watch: {
  497. '$route.query.characterId': {
  498. handler(newVal, lodVal) {
  499. console.log(newVal, 'newVal');
  500. if (!newVal) {
  501. this.$nextTick(() => {
  502. this.showInfoChange(0)
  503. })
  504. }
  505. },
  506. deep: true
  507. },
  508. returnMessage: {
  509. handler(newVal, lodVal) {
  510. this.$nextTick(() => {
  511. let messages = this.$refs.messages
  512. messages.scrollTop = messages.scrollHeight
  513. })
  514. },
  515. deep: true
  516. }
  517. },
  518. methods: {
  519. async init() {
  520. // 获取模型列表
  521. this.getModelList()
  522. // 获取用户所有聊天记录
  523. await this.getChatCharacterRecords()
  524. // 如果用角色id获取角色详情
  525. if (this.$route.query.characterId) {
  526. if (this.$route.query.sceneId) {
  527. this.sceneId = this.$route.query.sceneId
  528. }
  529. this.showInfoChange(1)
  530. await this.getDetail(this.$route.query.characterId)
  531. } else {
  532. this.allRecords[0].open = true
  533. await this.getDetail(this.allRecords[0].id)
  534. }
  535. // 查找当前角色是否有对话记录
  536. // this.searchHistory(this.info.id)
  537. },
  538. // 左侧面板变化
  539. showInfoChange(index) {
  540. this.fullScreen = false
  541. this.showInfo = index
  542. },
  543. // 全屏
  544. clickFullScreen() {
  545. this.fullScreen = !this.fullScreen
  546. },
  547. // 点击聊天记录切换聊天
  548. async clickDialogue(value1, value2, index) {
  549. console.log(value2, 'value2');
  550. let _this = this
  551. this.audioUrl = ""
  552. this.messageOptions = []
  553. this.returnMessage = []
  554. if (this.$route.query.characterId) {
  555. this.$route.query.characterId = value1.id
  556. }
  557. if (!value2) {
  558. return
  559. }
  560. // 如果点击的是场景聊天
  561. if (value2.sceneId) {
  562. this.sceneId = value2.sceneId
  563. await this.getDetail(value1.id)
  564. // 从角色详情中获取场景列表
  565. let sceneList = this.info.sceneList
  566. // 获取当前聊天对应的场景
  567. for (let i = 0; i < sceneList.length; i++) {
  568. const element = sceneList[i];
  569. if (element.sceneId == value2.sceneId) {
  570. _this.sceneInfo = element
  571. }
  572. }
  573. } else {
  574. this.sceneId = null
  575. this.sceneInfo = {}
  576. await this.getDetail(value1.id)
  577. }
  578. this.recordId = value2.id
  579. this.getChatRecord(value2.id)
  580. this.recordsIndex = index
  581. },
  582. // 场景下拉菜单
  583. handleCommand3(value) {
  584. if (value == 1) {
  585. this.showInfoChange(2)
  586. } else if (value == 2) {
  587. // 情况聊天记录
  588. this.returnMessage = []
  589. // 新建场景对话
  590. this.newStart()
  591. // 增加开场白
  592. this.returnMessage.push({
  593. role: 'assistant',
  594. content: this.sceneInfo.sceneWelcome
  595. })
  596. } else if (value == 3) {
  597. // 回到常规对话
  598. // 遍历当前聊天记录是否有常规对话
  599. let messages = null
  600. for (let i = 0; i < this.allRecords.length; i++) {
  601. const element = this.allRecords[i];
  602. if (element.id == this.info.id) {
  603. messages = element
  604. }
  605. }
  606. console.log(messages, 'messages');
  607. let flag = false
  608. for (let i = 0; i < messages.chatCharacterList.length; i++) {
  609. const element = messages.chatCharacterList[i];
  610. console.log(element, 'element');
  611. if (!element.sceneId) {
  612. flag = true
  613. this.clickDialogue(messages, element, i)
  614. break
  615. }
  616. }
  617. // 如果聊天记录里没有常规对话,则新增一个常规对话
  618. if (!flag) {
  619. this.sceneId = null
  620. this.sceneInfo = {}
  621. this.newStart()
  622. }
  623. }
  624. },
  625. // 聊天记录列表-角色下拉菜单
  626. handleCommand2(value, value1) {
  627. if (value == 2) {
  628. this.$confirm('此操作将永久删除该角色所有聊天记录, 是否继续?', '提示', {
  629. confirmButtonText: '确定',
  630. cancelButtonText: '取消',
  631. type: 'warning'
  632. }).then(() => {
  633. this.clearRecord(value1)
  634. })
  635. }
  636. },
  637. // 聊天记录列表-角色-聊天记录下拉菜单
  638. handleCommand(value, value1, value2) {
  639. console.log(value, 'value');
  640. if (value == 1) {
  641. this.editDialogueTitle(value1, value2)
  642. } else if (value == 2) {
  643. this.$confirm('此操作将永久删除该聊天记录, 是否继续?', '提示', {
  644. confirmButtonText: '确定',
  645. cancelButtonText: '取消',
  646. type: 'warning'
  647. }).then(() => {
  648. this.clearRecord(value1, value2)
  649. })
  650. }
  651. },
  652. // 聊天记录操作相关↓
  653. handleCommand4(value1, value2, value3) {
  654. console.log(value1, 'value1');
  655. console.log(value2, 'value2');
  656. if (value1 == 1) {
  657. // 编辑
  658. let height = this.$refs[`message${value3}`][0].offsetHeight
  659. let width = this.$refs[`message${value3}`][0].offsetWidth
  660. value2.edit = true
  661. this.$nextTick(() => {
  662. this.$refs[`messageEditInput${value3}`][0].$el.children[0].style.height = height + 'px'
  663. this.$refs[`messageEdit${value3}`][0].style.width = width + 'px'
  664. })
  665. } else if (value1 == 2) {
  666. // 删除
  667. this.$confirm('此操作将永久删除该聊天记录, 是否继续?', '提示', {
  668. confirmButtonText: '确定',
  669. cancelButtonText: '取消',
  670. type: 'warning'
  671. }).then(() => {
  672. this.deleteRecord(value2, value3)
  673. })
  674. } else if (value1 == 3) {
  675. // 重新生成最后一句对话
  676. this.resetChat(value2, value3)
  677. }
  678. },
  679. // 重新生成最后一条聊天记录
  680. resetChat(value, index) {
  681. let params = {
  682. recordId: value.id,
  683. role: value.role,
  684. }
  685. deleteRecordApi(params).then(res => {
  686. this.returnMessage.splice(index, 1)
  687. this.getStreamChatWithWeb()
  688. })
  689. },
  690. // 编辑单条聊天记录-取消
  691. editCancel(value, index) {
  692. value.edit = false
  693. value.editContent = value.content
  694. },
  695. // 编辑单条聊天记录-保存
  696. editSave(value, index) {
  697. console.log(value, 'value');
  698. let params = {
  699. content: value.editContent,
  700. recordId: value.id,
  701. role: value.role,
  702. }
  703. updateRecordApi(params).then(res => {
  704. console.log(res, 'ressss');
  705. value.edit = false
  706. this.getChatRecord(this.recordId)
  707. })
  708. },
  709. // 删除单条聊天记录
  710. deleteRecord(value, index) {
  711. let params = {
  712. recordId: value.id,
  713. role: value.role,
  714. }
  715. deleteRecordApi(params).then(res => {
  716. this.$message({
  717. message: '删除成功',
  718. type: 'success'
  719. });
  720. this.getChatRecord(this.recordId)
  721. })
  722. },
  723. // 聊天记录新增对话
  724. async newStart_juese(value) {
  725. console.log(value, 'value');
  726. this.audioUrl = ""
  727. this.sceneId = null
  728. this.sceneInfo = {}
  729. this.messageOptions = []
  730. this.returnMessage = []
  731. // 点击聊天记录切换聊天
  732. if (this.$route.query.characterId) {
  733. this.$route.query.characterId = value.id
  734. }
  735. await this.getDetail(value.id)
  736. this.recordId = await this.addChat()
  737. for (let i = 0; i < this.allRecords.length; i++) {
  738. const element = this.allRecords[i];
  739. if (element.id == this.info.id) {
  740. element.chatCharacterList.push({
  741. chatTitle: "常规聊天",
  742. createTime: '',
  743. id: this.recordId
  744. })
  745. this.recordsIndex = element.chatCharacterList.length - 1
  746. }
  747. }
  748. },
  749. // 修改聊天记录标题
  750. editDialogueTitle(value1, value2) {
  751. this.$prompt('新标题名称', '修改标题', {
  752. confirmButtonText: '确定',
  753. cancelButtonText: '取消',
  754. }).then(({ value }) => {
  755. let params = {
  756. chatTitle: value,
  757. id: value2.id
  758. }
  759. updateTitleApi(params).then(res => {
  760. console.log(res, '修改标题');
  761. this.$message({
  762. type: 'success',
  763. message: '修改成功!'
  764. });
  765. this.getChatCharacterRecords()
  766. })
  767. })
  768. },
  769. clearRecord(value1, value2) {
  770. let params = {
  771. characterId: value1.id,
  772. }
  773. if (value2) {
  774. params.recordId = value2.id
  775. }
  776. clearRecordApi(params).then(res => {
  777. this.$message({
  778. type: 'success',
  779. message: '删除成功!'
  780. });
  781. this.getChatCharacterRecords()
  782. })
  783. },
  784. async getChatRecord(recordId) {
  785. let params = {
  786. characterId: this.info.id,
  787. recordId: recordId
  788. }
  789. getChatRecordApi(params).then(res => {
  790. console.log(res, '查询到的聊天记录');
  791. let array = res.data
  792. // this.returnMessage
  793. this.historyRes = res.data
  794. if (this.returnMessage.length > 1) {
  795. this.returnMessage = this.returnMessage.splice(0, 1)
  796. }
  797. for (let i = 0; i < array.length; i++) {
  798. const element = array[i];
  799. let history = JSON.parse(element.history)
  800. history.map(item => {
  801. item.id = element.id
  802. item.uuid = element.uuid
  803. item.edit = false
  804. item.editContent = item.content
  805. if (item.role == 'assistant') {
  806. item.voiceFilePosition = element.voiceFilePosition
  807. }
  808. })
  809. this.returnMessage = [...this.returnMessage, ...history]
  810. }
  811. this.configForm.radio2 = this.modelList[0].id
  812. this.modelList.map(item => {
  813. if (item.id == array[array.length - 1].modelId) {
  814. this.configForm.radio2 = item.id
  815. }
  816. })
  817. })
  818. },
  819. searchHistory(characterId) {
  820. // 查看对话记录是否有选中的角色
  821. // console.log('查看对话记录是否有选中的角色');
  822. let flg = false
  823. for (let i = 0; i < this.allRecords.length; i++) {
  824. const element = this.allRecords[i];
  825. // 如果有则选中角色的最新一条记录并获取对话聊天记录
  826. if (element.id == characterId) {
  827. console.log(element, '查看对话记录是否有选中的角色');
  828. flg = true
  829. element.open = true
  830. // 二级对话判断当前是普通对话还是场景对话
  831. // 取对应的最后一条消息记录
  832. let chatCharacterList = element.chatCharacterList
  833. let chatCharacter = null
  834. let index = null
  835. for (let i = 0; i < chatCharacterList.length; i++) {
  836. const element2 = chatCharacterList[i];
  837. if (this.$route.query.sceneId == element2.sceneId) {
  838. chatCharacter = element2
  839. index = i
  840. } else if (!this.$route.query.sceneId && !element2.sceneId) {
  841. chatCharacter = element2
  842. index = i
  843. }
  844. }
  845. // 如果有聊天记录则点击
  846. if (chatCharacter) {
  847. this.clickDialogue(element, chatCharacter, index)
  848. } else {
  849. // 如果没有场景聊天记录则新增场景聊天
  850. // 从角色详情中获取场景列表
  851. let sceneList = this.info.sceneList
  852. // 获取当前聊天对应的场景
  853. for (let i = 0; i < sceneList.length; i++) {
  854. const element = sceneList[i];
  855. if (element.sceneId == this.$route.query.sceneId) {
  856. this.sceneInfo = element
  857. }
  858. }
  859. }
  860. // this.recordsIndex = element.chatCharacterList.length - 1
  861. // let recordId = element.chatCharacterList[element.chatCharacterList.length - 1].id
  862. // this.recordId = recordId
  863. // this.getChatRecord(recordId)
  864. }
  865. }
  866. // 如果没有聊天记录,则增加开场聊天引导
  867. if (!flg) {
  868. this.getGuidance(this.info.id)
  869. }
  870. },
  871. async getChatCharacterRecords() {
  872. let res = await getChatCharacterRecordsApi()
  873. console.log(res, '聊天记录');
  874. if (res.data) {
  875. for (let i = 0; i < res.data.length; i++) {
  876. const element = res.data[i];
  877. if (this.info.id == element.id) {
  878. element.open = true
  879. } else {
  880. element.open = false
  881. }
  882. }
  883. this.allRecords = res.data
  884. }
  885. },
  886. // 新增对话记录
  887. async addChat() {
  888. let params = {
  889. characterId: this.info.id,
  890. chatTitle: ""
  891. }
  892. if (this.sceneId) {
  893. params.sceneId = this.sceneId
  894. }
  895. let res = await addChatApi(params)
  896. console.log(res, '新增对话记录');
  897. return res.data
  898. },
  899. getModelList() {
  900. getModelListApi().then(res => {
  901. console.log(res, '模型列表');
  902. this.modelList = res.data
  903. })
  904. },
  905. inputBoxClick() {
  906. this.$refs.input.focus()
  907. },
  908. async getDetail(id) {
  909. let res = await detailApi(id)
  910. console.log(res, '角色详情');
  911. this.info = res.data
  912. // 模型选中角色默认模型
  913. this.configForm.radio2 = this.info.modelId
  914. // 如果用户有设置过模型使用用户设置模型
  915. let userConfig = JSON.parse(Cookies.get(`userConfig_${this.$store.state.user.userId}_${this.info.id}`))
  916. if (userConfig.radio2) {
  917. this.configForm.radio2 = userConfig.radio2
  918. }
  919. // let HistoryMessage = JSON.parse(localStorage.getItem(`[userId:${123},aiId:${this.info.id}]`));
  920. // if (HistoryMessage) {
  921. // this.returnMessage = HistoryMessage
  922. // } else {
  923. // this.returnMessage.push({
  924. // role: 'assistant',
  925. // content: this.info.firstContent
  926. // })
  927. // localStorage.setItem(`[userId:${123},aiId:${this.info.id}]`, JSON.stringify(this.returnMessage));
  928. // }
  929. if (this.sceneId) {
  930. console.log(this.sceneId);
  931. let sceneList = this.info.sceneList
  932. for (let i = 0; i < sceneList.length; i++) {
  933. const element = sceneList[i];
  934. if (element.sceneId == this.sceneId) {
  935. this.sceneInfo = element
  936. this.configForm.radio2 = element.modelId
  937. this.returnMessage.push({
  938. role: 'assistant',
  939. content: element.sceneWelcome
  940. })
  941. }
  942. }
  943. } else {
  944. this.returnMessage.push({
  945. role: 'assistant',
  946. content: this.info.firstContent
  947. })
  948. }
  949. },
  950. getGuidance(id) {
  951. getGuidanceApi(id).then(res => {
  952. console.log(res, '聊天引导');
  953. this.messageOptions = res.data
  954. })
  955. },
  956. goBack() {
  957. this.$router.back()
  958. },
  959. async newStart() {
  960. // 新业务,创建一个新的对话记录
  961. this.recordId = await this.addChat()
  962. for (let i = 0; i < this.allRecords.length; i++) {
  963. const element = this.allRecords[i];
  964. if (element.id == this.info.id) {
  965. if (this.sceneId) {
  966. element.chatCharacterList.push({
  967. chatTitle: "场景聊天",
  968. createTime: '',
  969. id: this.recordId,
  970. sceneIcon: true
  971. })
  972. } else {
  973. element.chatCharacterList.push({
  974. chatTitle: "常规聊天",
  975. createTime: '',
  976. id: this.recordId
  977. })
  978. }
  979. this.recordsIndex = element.chatCharacterList.length - 1
  980. }
  981. }
  982. this.returnMessage = []
  983. this.getDetail(this.info.id)
  984. // 废弃》》
  985. // 清空历史数据
  986. // localStorage.removeItem(`[userId:${123},aiId:${this.info.id}]`);
  987. // this.returnMessage = []
  988. // this.getDetail(this.info.id)
  989. },
  990. Enterkey(e) {
  991. if (e.keyCode == 13) {
  992. console.log(this.notChatSend, 'this.notChatSend');
  993. if (this.notChatSend) {
  994. return
  995. }
  996. if (!this.content) {
  997. return
  998. }
  999. this.getStreamChatWithWeb()
  1000. }
  1001. },
  1002. // 设置用户配置
  1003. configConfirm() {
  1004. console.log(this.info, 'this.info');
  1005. // 在浏览器缓存用户的配置
  1006. Cookies.set(`userConfig_${this.$store.state.user.userId}_${this.info.id}`, JSON.stringify(this.configForm))
  1007. this.showConfig = false
  1008. },
  1009. // 前往创建场景
  1010. toCreateScene() {
  1011. this.$router.push({
  1012. name: 'CreateScene',
  1013. query: {
  1014. characterId: this.info.id,
  1015. }
  1016. })
  1017. },
  1018. async getStreamChatWithWeb_old() {
  1019. this.messageLoading = true
  1020. if (!this.content) {
  1021. return
  1022. }
  1023. this.returnMessage.push({
  1024. role: 'user',
  1025. content: this.content
  1026. })
  1027. let HistoryMessage = JSON.parse(localStorage.getItem(`[userId:${123},aiId:${this.info.id}]`));
  1028. // 历史记录取最新的20条传给后端
  1029. if (HistoryMessage && HistoryMessage.length > 20) {
  1030. HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
  1031. }
  1032. let params = {
  1033. historyMessage: HistoryMessage || [],
  1034. characterId: this.info.id,
  1035. prompt: JSON.parse(JSON.stringify(this.content))
  1036. }
  1037. // 清空输入框的值
  1038. this.content = ''
  1039. // 新增一条ai信息
  1040. this.returnMessage.push({
  1041. role: 'assistant',
  1042. content: ''
  1043. })
  1044. let res = await streamChatWithWebApi(params)
  1045. console.log(res.data.message.content, 'res');
  1046. let content = res.data.message.content
  1047. let index = 0
  1048. let xing = 1
  1049. let messageInterval = setInterval(() => {
  1050. if (index > content.length - 1) {
  1051. console.log('结束');
  1052. // 消息全部显示后存到历史localStorage
  1053. let HistoryMessage = []
  1054. // 历史只存100条,长度超出截取最新的
  1055. if (this.returnMessage.length > 100) {
  1056. HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
  1057. } else {
  1058. HistoryMessage = JSON.stringify(this.returnMessage)
  1059. }
  1060. localStorage.setItem(`[userId:${123},aiId:${this.info.id}]`, HistoryMessage);
  1061. clearInterval(messageInterval)
  1062. return
  1063. }
  1064. console.log(content[index], 'content[index]');
  1065. if (content[index] == "*") {
  1066. if (xing == 1) {
  1067. xing = 2
  1068. this.returnMessage[this.returnMessage.length - 1].content += "("
  1069. } else if (xing == 2) {
  1070. xing = 1
  1071. this.returnMessage[this.returnMessage.length - 1].content += ")"
  1072. }
  1073. } else {
  1074. this.returnMessage[this.returnMessage.length - 1].content += content[index]
  1075. }
  1076. index += 1
  1077. }, 50)
  1078. },
  1079. async getStreamChatWithWeb() {
  1080. this.audioUrl = []
  1081. this.audioUrlArrIndx = 0
  1082. this.textCache = ''
  1083. this.resError = false
  1084. this.messageLoading = true
  1085. this.chatLoading = true
  1086. // 初始化定时器和audio的状态
  1087. if (this.videoLoop) {
  1088. clearInterval(this.videoLoop)
  1089. this.videoLoop = null
  1090. this.audioUrl = ''
  1091. this.$refs.audio.currentTime = 0;
  1092. this.$refs.audio.pause()
  1093. }
  1094. // 如果没有对话记录id,新增对话记录
  1095. if (!this.recordId) {
  1096. this.recordId = await this.addChat()
  1097. }
  1098. if (this.content) {
  1099. this.returnMessage.push({
  1100. role: 'user',
  1101. content: this.content
  1102. })
  1103. }
  1104. let HistoryMessage = JSON.parse(JSON.stringify(this.returnMessage))
  1105. console.log(HistoryMessage, 'HistoryMessage1');
  1106. HistoryMessage = HistoryMessage.splice(1, HistoryMessage.length)
  1107. console.log(HistoryMessage, 'HistoryMessage2');
  1108. // 历史记录取最新的20条传给后端
  1109. if (HistoryMessage && HistoryMessage.length > 20) {
  1110. HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
  1111. }
  1112. let params = {
  1113. historyMessage: HistoryMessage,
  1114. characterId: this.info.id,
  1115. prompt: this.content,
  1116. modelId: this.configForm.radio2,
  1117. recordId: this.recordId,
  1118. }
  1119. // 如果当前是场景对话则需要传sceneId
  1120. if (this.sceneId) {
  1121. params.sceneId = this.sceneId
  1122. }
  1123. // 新增一条ai信息
  1124. this.returnMessage.push({
  1125. role: 'assistant',
  1126. content: '',
  1127. voiceFilePosition: '',
  1128. })
  1129. // 清空输入框的值
  1130. this.content = ''
  1131. let res = await streamChatWithWebApi(params)
  1132. console.log(res, 'res');
  1133. this.messageLoading = false
  1134. if (res.status != 200) {
  1135. this.$message.error(res.statusText)
  1136. this.returnMessage.splice(this.returnMessage.length - 1, 2)
  1137. this.chatLoading = false
  1138. return
  1139. }
  1140. const reader = res.body.getReader()
  1141. const decoder = new TextDecoder()
  1142. while (1) {
  1143. const { done, value } = await reader.read()
  1144. // console.log(done, 'done');
  1145. if (done) {
  1146. console.log(value, '结束value');
  1147. this.chatLoading = false
  1148. // if (typeof(value) == "undefined") {
  1149. // this.$message.error('错误')
  1150. // this.returnMessage.splice(this.returnMessage.length - 1, 1)
  1151. // }
  1152. // 每次对话完刷新聊天记录
  1153. this.getChatCharacterRecords()
  1154. let params = {
  1155. characterId: this.info.id,
  1156. recordId: this.recordId
  1157. }
  1158. let historyRes = await getChatRecordApi(params)
  1159. this.historyRes = historyRes.data
  1160. // 如果开启自动播放,则查询并播放语音
  1161. // this.audioUrl = ""
  1162. // if (!this.resError) {
  1163. // }
  1164. console.log('结束');
  1165. break;
  1166. }
  1167. //txt就是一个一个的字 然后添加到页面上就可以了
  1168. const txt = decoder.decode(value).split('data:')
  1169. // 超时处理
  1170. if (this.timeOut) {
  1171. clearTimeout(this.timeOut)
  1172. this.timeOut = setTimeout(() => {
  1173. this.returnMessage[this.returnMessage.length - 1].content = 'AI智能累得打起了小盹儿,咱们让他缓一缓,再来试试!'
  1174. this.chatLoading = false
  1175. }, 10000)
  1176. }
  1177. // const txt = decoder.decode(value)
  1178. this.addMessage(txt)
  1179. // let data = JSON.parse(txt).message.content
  1180. // console.log(txt, 'txt');
  1181. // if (this.isJSON(txt) && JSON.parse(txt).code != 200) {
  1182. // let data = JSON.parse(txt)
  1183. // this.$message.error(data.msg)
  1184. // this.returnMessage.splice(this.returnMessage.length - 1, 1)
  1185. // break;
  1186. // }
  1187. }
  1188. },
  1189. addMessage(value) {
  1190. // console.log(value, 'value');
  1191. for (let i = 0; i < value.length; i++) {
  1192. const element = value[i];
  1193. if (this.isJSON(element)) {
  1194. let value = JSON.parse(element)
  1195. if (value.code == 401) {
  1196. this.returnMessage.splice(this.returnMessage.length - 1, 1)
  1197. this.noLogin(element)
  1198. this.chatLoading = false
  1199. break;
  1200. }
  1201. if (value.code == 500) {
  1202. console.log(value, 'JSON.parse(element)');
  1203. this.returnMessage.splice(this.returnMessage.length - 2, 2)
  1204. this.$message.error(value.content || '系统错误请联系管理员')
  1205. this.resError = true
  1206. this.chatLoading = false
  1207. break;
  1208. }
  1209. // 获取语音id
  1210. if (value.code == -1) {
  1211. this.returnMessage[this.returnMessage.length - 1].uuid = value.content
  1212. // 如果开启了自动播放开始轮巡查询语音
  1213. if (this.setting.value1) {
  1214. this.audioUrlArrIndx = 0
  1215. this.videoLoopTime = 0
  1216. this.audioUrlArr = []
  1217. this.loopPlay = false
  1218. this.audioPlayIndex = this.returnMessage.length - 1
  1219. this.loopGetVoice(value.content, this.returnMessage.length - 1)
  1220. }
  1221. break;
  1222. }
  1223. if (!value.done) {
  1224. let txt = value.content
  1225. this.returnMessage[this.returnMessage.length - 1].content += txt
  1226. } else {
  1227. // 对话流结束
  1228. // 临时插入的ai对话记录和用户记录插入聊天id
  1229. this.returnMessage[this.returnMessage.length - 1].id = value.content
  1230. this.returnMessage[this.returnMessage.length - 2].id = value.content
  1231. }
  1232. }
  1233. }
  1234. },
  1235. noLogin(value) {
  1236. let _this = this
  1237. MessageBox.confirm(
  1238. value.content,
  1239. "系统提示",
  1240. {
  1241. confirmButtonText: "前往登录",
  1242. cancelButtonText: "取消",
  1243. type: "warning",
  1244. }
  1245. )
  1246. .then(() => {
  1247. // isRelogin.show = false;
  1248. this.$store.dispatch("LogOut").then(() => {
  1249. // location.href = "/";
  1250. _this.$refs.Header.showLogin()
  1251. });
  1252. })
  1253. .catch(() => {
  1254. // isRelogin.show = false;
  1255. });
  1256. },
  1257. isJSON(str) {
  1258. if (typeof str == 'string') {
  1259. try {
  1260. JSON.parse(str);
  1261. return true;
  1262. } catch (e) {
  1263. // console.log(e);
  1264. return false;
  1265. }
  1266. }
  1267. },
  1268. messageOptionClick(value) {
  1269. this.content = value
  1270. this.getStreamChatWithWeb()
  1271. },
  1272. // 播放聊天语音
  1273. initAudio(index) {
  1274. // 从聊天记录获取id
  1275. console.log(this.returnMessage[index], 'swssssssss');
  1276. let id = this.returnMessage[index].id
  1277. let history = this.historyRes.map((item) => {
  1278. if (item.id == id) {
  1279. return item
  1280. }
  1281. })
  1282. console.log(history, 'history');
  1283. return
  1284. this.loopGetVoice(history.id, index)
  1285. },
  1286. playAudio(index) {
  1287. if (index) {
  1288. this.audioPlayIndex = index
  1289. }
  1290. // this.audioUrlArrIndx = 0
  1291. console.log(this.audioUrlArrIndx, 'this.audioUrlArrIndx');
  1292. console.log(this.audioUrlArr, 'this.audioUrlArr');
  1293. // console.log(audioUrlArr[this.audioUrlArrIndx].voiceAddress, 'audioUrlArr[this.audioUrlArrIndx].voiceAddress');
  1294. // 如果当前播放的语音地址为空,则等待2秒后重新获取
  1295. if (!this.audioUrlArr[this.audioUrlArrIndx].voiceAddress && this.videoLoopTime <= 50) {
  1296. setTimeout(() => {
  1297. this.playAudio(index)
  1298. }, 2000)
  1299. return
  1300. }
  1301. // 如果当前播放的语音地址为false,则跳过当前语音
  1302. if (this.audioUrlArr[this.audioUrlArrIndx].voiceAddress == 'false' && this.audioUrlArrIndx < this.audioUrlArr.length - 1) {
  1303. this.audioUrlArrIndx += 1
  1304. this.playAudio(index)
  1305. return
  1306. } else if (this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
  1307. this.audioUrl = ''
  1308. this.audioUrlArrIndx = 0
  1309. this.videoLoopTime = 0
  1310. this.audioUrlArr = []
  1311. return
  1312. }
  1313. this.audioUrl = this.audioUrlArr[this.audioUrlArrIndx].voiceAddress
  1314. setTimeout(() => {
  1315. if (this.audioUrl && this.audioUrl != 'false') {
  1316. this.$refs.audio.play()
  1317. } else {
  1318. this.audioClose()
  1319. }
  1320. }, 1000)
  1321. },
  1322. async historyGetVoice(value, index) {
  1323. console.log(value, 'value');
  1324. if (!value.uuid) {
  1325. return
  1326. }
  1327. let params = {
  1328. uuid: value.uuid
  1329. }
  1330. let fileRes = await queryVoiceListApi(params)
  1331. console.log(fileRes.data, 'file>>>');
  1332. this.audioUrlArr = fileRes.data.voices
  1333. this.audioUrlArrIndx = 0
  1334. this.videoLoopTime = 0
  1335. this.loopPlay = false
  1336. this.playAudio(index)
  1337. },
  1338. async loopGetVoice(id, index) {
  1339. this.videoLoopTime += 1
  1340. // 获取语音文件
  1341. let params = {
  1342. uuid: id
  1343. }
  1344. let fileRes = await queryVoiceListApi(params)
  1345. console.log(fileRes.data, 'file>>>');
  1346. let voices = fileRes.data.voices
  1347. let _continue = fileRes.data.continue
  1348. this.apiHaveVideo = _continue
  1349. // 判断语音文件是否获取完毕
  1350. let trueLength = 0
  1351. for (let i = 0; i < voices.length; i++) {
  1352. const element = voices[i];
  1353. if (element.voiceAddress) {
  1354. trueLength += 1
  1355. }
  1356. }
  1357. if (!_continue && trueLength == voices.length) {
  1358. if (this.videoLoop) {
  1359. clearTimeout(this.videoLoop)
  1360. this.videoLoop = null
  1361. }
  1362. // this.returnMessage[index].voiceFilePosition = fileRes.data.voiceFilePosition
  1363. // 有语音文件则开始播放
  1364. if (voices.length > 0) {
  1365. this.audioUrlArr = voices
  1366. console.log(this.audioUrlArr, '进入播放流程1');
  1367. if (this.$refs.audio.paused || this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
  1368. if (this.loopPlay == false) {
  1369. this.playAudio(index)
  1370. this.loopPlay = true
  1371. }
  1372. }
  1373. }
  1374. } else {
  1375. if (voices.length > 0) {
  1376. this.audioUrlArr = voices
  1377. console.log(this.audioUrlArr, '进入播放流程2');
  1378. if (this.$refs.audio.paused || this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
  1379. if (this.loopPlay == false) {
  1380. this.playAudio(index)
  1381. this.loopPlay = true
  1382. }
  1383. }
  1384. }
  1385. // 没有语音文件则开启定时器每两秒查询一次
  1386. if (this.videoLoopTime > 50) {
  1387. // 如果调用次数超过30次则停止
  1388. return
  1389. }
  1390. this.videoLoop = setTimeout(() => {
  1391. this.loopGetVoice(id, index)
  1392. }, 2000)
  1393. }
  1394. },
  1395. audioEnd() {
  1396. this.audioUrlArrIndx += 1
  1397. console.log(this.audioUrlArrIndx, 'audioUrlArrIndx');
  1398. if (this.audioUrlArrIndx < this.audioUrlArr.length && this.videoLoopTime <= 50) {
  1399. // 如果当前播放的语音地址为空,则等待2秒后重新获取
  1400. if (!this.audioUrlArr[this.audioUrlArrIndx].voiceAddress) {
  1401. setTimeout(() => {
  1402. this.playAudio()
  1403. }, 2000)
  1404. return
  1405. }
  1406. // 如果当前播放的语音地址为false,则跳过当前语音
  1407. if (this.audioUrlArr[this.audioUrlArrIndx].voiceAddress == 'false' && this.audioUrlArrIndx < this.audioUrlArr.length - 1) {
  1408. this.audioUrlArrIndx += 1
  1409. this.playAudio()
  1410. return
  1411. }
  1412. this.audioUrl = this.audioUrlArr[this.audioUrlArrIndx].voiceAddress
  1413. setTimeout(() => {
  1414. if (this.audioUrl && this.audioUrl != 'false') {
  1415. this.$refs.audio.play()
  1416. } else {
  1417. this.audioClose()
  1418. }
  1419. }, 100)
  1420. } else {
  1421. this.audioClose()
  1422. }
  1423. },
  1424. audioClose() {
  1425. this.audioUrlArrIndx = 0
  1426. this.audioPlayIndex = null
  1427. },
  1428. ChangeSettingValue1(value) {
  1429. console.log(value, 'value');
  1430. if (value) {
  1431. queryUserBalanceApi().then(res => {
  1432. console.log(res, '用户余额');
  1433. if (res.data <= 0) {
  1434. this.setting.value1 = false
  1435. this.$message({
  1436. message: '余额不足,无法开启次功能',
  1437. type: 'error'
  1438. });
  1439. }
  1440. })
  1441. }
  1442. },
  1443. // 场景业务相关↓
  1444. async sceneChange(item) {
  1445. console.log(item, 'item');
  1446. this.$message({
  1447. message: `已进入场景${item.sceneName}`,
  1448. type: 'success'
  1449. });
  1450. // 当用户选中场景
  1451. this.sceneInfo = item
  1452. // 切换当前场景id
  1453. this.sceneId = item.sceneId
  1454. // 情况聊天记录
  1455. this.returnMessage = []
  1456. // 新建场景对话
  1457. this.newStart()
  1458. // 增加开场白
  1459. // this.returnMessage.push({
  1460. // role: 'assistant',
  1461. // content: item.sceneWelcome
  1462. // })
  1463. }
  1464. }
  1465. }
  1466. </script>
  1467. <style lang="scss" scoped>
  1468. .fullScreenButton {
  1469. position: absolute;
  1470. top: 50%;
  1471. left: 0;
  1472. transform: translateX(-50%) translateY(-50%);
  1473. z-index: 50;
  1474. background: #fff;
  1475. display: flex;
  1476. justify-content: center;
  1477. align-items: center;
  1478. width: 1.6rem;
  1479. height: 1.6rem;
  1480. cursor: pointer;
  1481. }
  1482. .chat {
  1483. height: 100vh;
  1484. }
  1485. .leftInfo {
  1486. width: 375px;
  1487. height: 100%;
  1488. padding: 32px 24px;
  1489. overflow-y: auto;
  1490. >.history {
  1491. width: 100%;
  1492. height: 100%;
  1493. display: flex;
  1494. flex-direction: column;
  1495. >.historyBox {
  1496. padding: 8px;
  1497. border-radius: 15px;
  1498. border: solid 1px var(--color1);
  1499. margin-bottom: 10px;
  1500. .top {
  1501. >.photo {
  1502. >img {
  1503. width: 48px;
  1504. height: 48px;
  1505. border-radius: 50%;
  1506. }
  1507. }
  1508. >.info {
  1509. width: calc(100% - 60px - 16px);
  1510. >div {
  1511. overflow: hidden;
  1512. /* 确保超出容器的文本被裁剪 */
  1513. white-space: nowrap;
  1514. /* 确保文本在一行内显示 */
  1515. text-overflow: ellipsis;
  1516. /* 使用省略号表示文本超出 */
  1517. }
  1518. }
  1519. }
  1520. }
  1521. }
  1522. >.chatInfo {
  1523. overflow: hidden;
  1524. .chatInfo_photo {
  1525. >img {}
  1526. }
  1527. .hot {
  1528. color: #f03d3d;
  1529. background: #fff1f1;
  1530. }
  1531. .tags {
  1532. margin-top: 10px;
  1533. .tag {
  1534. display: inline-flex;
  1535. justify-content: center;
  1536. padding: 5px 10px;
  1537. border-radius: 4px;
  1538. border: solid 1px var(--color1);
  1539. margin-right: 5px;
  1540. }
  1541. }
  1542. }
  1543. }
  1544. .content {
  1545. z-index: 40;
  1546. position: absolute;
  1547. right: 0;
  1548. background-repeat: no-repeat;
  1549. // background-attachment: fixed;
  1550. background-size: cover;
  1551. background-color: #fff;
  1552. background-position: center center;
  1553. display: flex;
  1554. height: 100%;
  1555. justify-content: center;
  1556. width: calc(100% - 375px);
  1557. border-left: solid 1px var(--color1);
  1558. transition: width 0.5s ease;
  1559. >.chat-box {
  1560. position: relative;
  1561. background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4) 0%, rgba(255, 255, 255, 0) 20%, rgba(255, 255, 255, 0) 75%, rgba(0, 0, 0, 0.4) 100%);
  1562. background-size: auto;
  1563. // border: 1px solid #635677 !important;
  1564. box-sizing: border-box;
  1565. display: flex;
  1566. flex-direction: column;
  1567. height: 100%;
  1568. width: 100%;
  1569. padding: 55px 25px 170px 25px;
  1570. >.chatBoxTitle {
  1571. color: #fff;
  1572. margin-top: 15px;
  1573. width: calc(100% - 50px);
  1574. height: 30px;
  1575. display: flex;
  1576. justify-content: space-between;
  1577. align-items: center;
  1578. }
  1579. }
  1580. >.mask {
  1581. background: rgba(0, 0, 0, 0.5);
  1582. }
  1583. }
  1584. .contentFull {
  1585. width: 100%;
  1586. }
  1587. .historyActive {
  1588. background: var(--color1);
  1589. }
  1590. .dialogue:hover {
  1591. background: var(--color1);
  1592. .dialogueIconBg {
  1593. display: flex;
  1594. }
  1595. }
  1596. .dialogueIconBg {
  1597. display: none;
  1598. background: linear-gradient(90deg, rgba(0, 0, 0, 0) 10%, var(--color1) 50%, var(--color1) 100%);
  1599. }
  1600. .messages {
  1601. height: calc(100% - 68px);
  1602. overflow-y: auto;
  1603. }
  1604. .messages::-webkit-scrollbar {
  1605. width: 0px;
  1606. }
  1607. .messageRight {
  1608. max-width: calc(100% - 72px);
  1609. // min-width: 400px;
  1610. }
  1611. .yyPlayBg {
  1612. // background: rgba(103, 103, 168, 1);
  1613. // border-radius: 999px;
  1614. // position: absolute;
  1615. .icon {
  1616. height: 24px;
  1617. width: 96px;
  1618. }
  1619. }
  1620. .yyPlay {
  1621. width: 15px;
  1622. height: 15px;
  1623. // background: url('../../assets/images/播放/yyPlay.gif') no-repeat;
  1624. // background-size: 170% 170%;
  1625. // background-position: 50% 50%;
  1626. // animation: heart-burst steps(28) 0.9s infinite both;
  1627. // animation-play-state: paused;
  1628. // height: 50px;
  1629. // background: #fff;
  1630. }
  1631. .message {
  1632. // background: #ffffff0f;
  1633. // border: 1px solid #5b5b5e;
  1634. background: var(--bg-color3);
  1635. color: #fff;
  1636. font-size: 14px;
  1637. // flex: 1;
  1638. }
  1639. .messageUser {
  1640. background: rgba(255, 255, 255, 0.9);
  1641. border: 1px solid var(--color1);
  1642. // max-width: calc(100% - 72px);
  1643. font-size: 14px;
  1644. width: auto;
  1645. // min-width: 400px;
  1646. }
  1647. .photo {
  1648. width: 56px;
  1649. }
  1650. .me {
  1651. flex-direction: row-reverse;
  1652. }
  1653. .loadingMessage {
  1654. position: relative;
  1655. // display: flex;
  1656. // justify-content: center;
  1657. width: 120px;
  1658. height: 64px;
  1659. }
  1660. .messageOptions {
  1661. // margin-top: 20px;
  1662. display: flex;
  1663. flex-wrap: wrap;
  1664. justify-content: space-around;
  1665. min-width: 400px;
  1666. .option {
  1667. background: #ade4ff32;
  1668. // border: 1px solid #5b5b5e;
  1669. border-radius: 10px;
  1670. padding: 10px;
  1671. width: 40%;
  1672. height: 50px;
  1673. margin-top: 10px;
  1674. cursor: pointer;
  1675. }
  1676. }
  1677. .inputBox {
  1678. border: solid 1px var(--color1);
  1679. border-radius: 10px;
  1680. padding: 24px;
  1681. background: #fff;
  1682. max-width: 900px;
  1683. margin: 0 auto;
  1684. }
  1685. .planeColor {
  1686. color: var(--bg-color1);
  1687. }
  1688. </style>
  1689. <style scoped>
  1690. .loadingMessage>>>.el-loading-parent--relative {
  1691. width: 100%;
  1692. height: 100%;
  1693. }
  1694. .loadingMessage>>>.el-loading-spinner {
  1695. display: flex;
  1696. justify-content: center;
  1697. }
  1698. .mButton {
  1699. border: solid 0px;
  1700. }
  1701. .radio>>>.el-radio-button {
  1702. margin-bottom: 5px;
  1703. margin-right: 10px;
  1704. /* border-left: solid 1px #DCDFE6; */
  1705. }
  1706. .radio>>>.el-radio-button__inner {
  1707. border-left: solid 1px #DCDFE6;
  1708. border-radius: 4px;
  1709. }
  1710. .radio>>>.el-radio-button__orig-radio:checked+.el-radio-button__inner {
  1711. border-left: solid 1px var(--bg-color1);
  1712. background-color: var(--bg-color1);
  1713. border-color: var(--bg-color1);
  1714. -webkit-box-shadow: -1px 0 0 0 var(--bg-color1);
  1715. box-shadow: -1px 0 0 0 var(--bg-color1);
  1716. }
  1717. .message:hover>>>.messageButton,
  1718. .messageUser:hover>>>.messageButton {
  1719. display: block;
  1720. }
  1721. .messageButton {
  1722. display: none;
  1723. width: 25px;
  1724. height: 25px;
  1725. padding: 0;
  1726. }
  1727. .messageDropdown {
  1728. position: absolute;
  1729. bottom: -12px;
  1730. right: 10px;
  1731. }
  1732. .eidtTextarea>>>.el-textarea__inner {
  1733. padding: 0;
  1734. background: none;
  1735. border: 0;
  1736. color: #fff;
  1737. }
  1738. .messageUser>>>.el-textarea__inner {
  1739. color: #000;
  1740. }
  1741. .messageEdit {
  1742. min-width: 400px;
  1743. }
  1744. </style>