12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574 |
- <template>
- <div class="chat bg-white">
- <div class="flex flex-col items-center justify-start h-screen"
- style="height: 100vh; height: calc(var(--vh, 1vh) * 100);">
- <!-- <img class="absolute h-full" :src="baseApi + info.picture" alt=""> -->
- <div class="top bg-white px-2 flex justify-between">
- <el-button class="tm_button" type="info" icon="el-icon-arrow-left" circle @click="goBack"></el-button>
- <div v-if="sceneId" class=" text-sm" style="max-width: 50%;">已进入场景:{{ sceneInfo.sceneName }}</div>
- <div class="flex">
- <el-button class="tm_button" type="info" circle>
- <i class="fa-solid fa-volume-high text-sm"></i>
- <!-- <i class="fa-solid fa-volume-xmark text-sm"></i> -->
- </el-button>
- <el-dropdown trigger="click" @command="rtHandleCommand">
- <el-button class="tm_button ml-2" type="info" icon="el-icon-more" circle></el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="1" icon="el-icon-warning-outline">查看详情</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </div>
- </div>
- <audio ref="audio" muted v-show="false" :src="audioUrl" autoplay="" @ended="audioEnd" controls></audio>
- <div class="chatContent" :style="`background-image: url(${baseApi + sceneInfo.background})`">
- <!-- <div class="leftImg">
- <img :src="baseApi + info.picture" alt="">
- <div class="aiInfo">
- <span class="photo">
- <img :src="baseApi + info.picture">
- </span>
- <div class="info">
- <div class="name">{{ info.characterName }}</div>
- <div class="tags">
- <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
- {{ item }}
- </span>
- </div>
- <div class="infoContent">{{ info.prologue }}</div>
- </div>
- </div>
- </div> -->
- <div class="chat-box relative " :class="sceneId && 'mask'">
- <div class="messages" ref="messages">
- <template v-for="(item, index) in returnMessage ">
- <div v-if="item.role == 'assistant'" class="text-white mb-4 flex" :key="index">
- <!-- ai返回的信息 -->
- <div class="pt-2 photo">
- <img :src="baseApi + info.picture" class="rounded-full w-12 h-12 object-cover" @click="toDetail"
- @error="$AIPohotoError" />
- </div>
- <div class="message fex-1 ml-2 mt-3 p-2 rounded-r-md rounded-bl-md text-base">
- <div v-show="!item.content" class="loadingMessage">
- <div v-loading="!item.content" element-loading-background="rgba(0, 0, 0, 0.0)"></div>
- </div>
- <p style="white-space: pre-line;" v-show="item.content">{{ item.content }} </p>
- </div>
- </div>
- <!-- 用户发送的信息 -->
- <div v-if="item.role == 'user'" class="text-white mb-4 flex me" :key="index">
- <div class="pt-2 photo">
- <img src="@/assets/images/default_avatar_user.png" @error="$userPohotoError"
- class="rounded-full w-12 h-12 object-cover" />
- </div>
- <div class="message2 fex-1 mr-2 mt-3 p-2 rounded-l-md rounded-br-md text-base">
- <p>{{ item.content }}</p>
- </div>
- </div>
- </template>
- </div>
- </div>
- </div>
- <div class="fixed bottom-0 bg-white w-full px-2 pt-2 pb-4 border-t border-gray-100">
- <!-- <div class="flex items-center text-gray-400 text-xs mb-4 ">
- <span>@Leon S Kennedy - Resident Evil</span>
- </div> -->
- <div class="flex">
- <el-button class="bgColor1 mButton" @click="showBottomPopup = true" style="margin-right: 5px;" type="primary"
- round icon="el-icon-plus"></el-button>
- <input type="text" placeholder="输入消息..." class="flex-1 py-2 px-4 rounded-l-lg text-sm focus:outline-none"
- style="background: #ffffff0f; border: 1px solid #5b5b5e;" v-model="content" @keydown="Enterkey" />
- <button @click.prevent="getStreamChatWithWeb" class="bgColor1 text-white px-4 rounded-r-lg text-sm"
- style="width: 60px;">
- 发送
- </button>
- </div>
- </div>
- <el-dialog :visible.sync="showBottomPopup" :show-close="false" width="100%" class="phoneBottomPopup">
- <transition name="el-zoom-in-bottom">
- <div class="popupContent popup1" v-show="showBottomPopup">
- <div class="configForm w-full flex flex-col overflow-y-auto" style="height: calc(100% - 75px)">
- <div class="row">
- <p class="text-base mt-3">选择模型</p>
- <p class="text-sm text-gray-200">每个模型可能有不同的效果,仅对当前对话有影响</p>
- <div class="flex items-start py-3 border-b border-gray-200">
- <el-radio-group class="radio" v-model="configForm.radio2" @input="formModelChange">
- <el-radio-button v-for="(item, index) in modelList" :key="index" :label="item.id">{{ item.model
- }}</el-radio-button>
- </el-radio-group>
- </div>
- </div>
- </div>
- <div class="bottom px-2">
- <div v-if="!sceneId" class="button text-white" @click="() => {
- this.showBottomPopup = false
- this.newStart()
- }
- ">
- <i class="icon iconfont icon-xinduihua"></i>
- <div>新的对话</div>
- </div>
- <div v-if="sceneId" class="button text-white" @click="() => {
- this.showBottomPopup = false
- this.handleCommand3(2)
- }
- ">
- <i class="icon iconfont icon-xinduihua"></i>
- <div>新建场景对话</div>
- </div>
- <div v-if="sceneId" class="button text-white" @click="() => {
- this.showBottomPopup = false
- this.handleCommand3(3)
- }
- ">
- <i class="icon iconfont icon-xinduihua"></i>
- <div>返回常规对话</div>
- </div>
- <div class="button text-white" @click="() => {
- this.showBottomPopup = false
- this.showHistory()
- }
- ">
- <i class="icon iconfont icon-lishi"></i>
- <div>历史对话</div>
- </div>
- </div>
- </div>
- </transition>
- </el-dialog>
- <el-dialog :visible.sync="showBottomPopup2" :show-close="false" width="100%" class="phoneBottomPopup">
- <transition name="el-zoom-in-bottom">
- <div class="popupContent popup2" v-show="showBottomPopup2">
- <div class=" w-full text-center py-2 text-base font-semibold">历史对话</div>
- <div class="historyRow py-2 cursor-pointer" v-for="( item, index ) in historyList " :key="index" @click="() => {
- showBottomPopup2 = false
- clickDialogue(jsHistory, item, index)
- }
- ">
- <div class="flex justify-between">
- <div class="name text-base">{{ item.chatTitle }}</div>
- <div class="time">{{ item.createTime }}</div>
- </div>
- <!-- <div class="info">
- {{ item. }}
- </div> -->
- </div>
- </div>
- </transition>
- </el-dialog>
- </div>
- </div>
- </template>
- <script>
- import Cookies from 'js-cookie'
- import {
- streamChatWithWebApi,
- getGuidanceApi,
- getModelListApi,
- addChatApi,
- getChatCharacterRecordsApi,
- getChatRecordApi,
- updateTitleApi,
- clearRecordApi,
- queryVoiceListApi,
- updateRecordApi,
- deleteRecordApi
- } from "@/api/chat.js"
- import { detailApi } from "@/api/detail.js"
- import { queryUserBalanceApi } from "@/api/user.js"
- import { Message, MessageBox, Notification, Loading } from 'element-ui'
- import 'vue-awesome/icons/paper-plane'
- import 'vue-awesome/icons/fire'
- export default {
- components: {
- },
- data() {
- return {
- // 对话请求超时参数
- timeOut: null,
- showBottomPopup: false,
- showBottomPopup2: false,
- jsHistory: null,
- historyList: [],
- // 聊天等待
- chatLoading: false,
- // 是否全屏
- fullScreen: false,
- // 角色所有聊天列表
- allRecords: [],
- // 聊天记录id
- recordId: null,
- // 选中的角色聊天记录
- historyRes: [],
- recordsIndex: null,
- showDetail: true,
- showConfig: false,
- messageOptions: [],
- info: {},
- messageLoading: false,
- returnMessage: [],
- content: '',
- audioPlayIndex: null,
- configForm: {
- radio1: null,
- radio2: null,
- radio3: null,
- },
- // 对话接口是否返回错误状态
- resError: false,
- // 模型列表
- modelList: [],
- setting: {
- value1: false,
- value2: false,
- },
- //音频相关↓
- //音频地址
- audioUrl: '',
- audioUrlArr: [],
- audioUrlArrIndx: 0,
- videoLoop: null,
- videoLoopTime: 0,
- loopPlay: false,
- //场景下拉菜单
- dropdown3Show: false,
- // 场景对话相关
- sceneId: null,
- sceneInfo: {},
- }
- },
- computed: {
- canChat() {
- console.log(this.info, 'this.info');
- if (!this.sceneId) {
- if (this.info.isDelete == 1) {
- console.log(1);
- return false
- } else {
- console.log(2);
- return true
- }
- } else {
- if (this.sceneInfo.isDelete == 1) {
- return false
- } else {
- return true
- }
- }
- },
- notChatSend() {
- console.log(this.audioPlayIndex, 'this.audioPlayIndex>>>>>>>>>>>');
- // 对话结束切语音已经播放完毕
- // return this.chatLoading != false || this.audioPlayIndex != null
- return this.chatLoading != false
- },
- showNodata() {
- return this.allRecords.length == 0 && !this.$route.query.characterId
- }
- },
- mounted() {
- this.init()
- },
- watch: {
- returnMessage: {
- handler(newVal, lodVal) {
- this.$nextTick(() => {
- let messages = this.$refs.messages
- messages.scrollTop = messages.scrollHeight
- })
- },
- deep: true
- }
- },
- methods: {
- async init() {
- // 获取模型列表
- this.getModelList()
- // 获取用户所有聊天记录
- await this.getChatCharacterRecords()
- // 如果用角色id获取角色详情
- if (this.$route.query.characterId) {
- if (this.$route.query.sceneId) {
- this.sceneId = this.$route.query.sceneId
- }
- await this.getDetail(this.$route.query.characterId)
- } else {
- this.allRecords[0].open = true
- await this.getDetail(this.allRecords[0].id)
- }
- // 查找当前角色是否有对话记录
- this.searchHistory(this.info.id)
- },
- rtHandleCommand(value) {
- // console.log(value);
- if (value == 1) {
- this.toDetail()
- }
- },
- toDetail() {
- this.$router.push({
- path: '/detailH5',
- query: {
- id: this.info.id
- }
- })
- },
- // 显示历史聊天记录
- showHistory() {
- this.showBottomPopup2 = true
- },
- formModelChange() {
- // 在浏览器缓存用户的配置
- Cookies.set(`userConfig_${this.$store.state.user.userId}_${this.info.id}`, JSON.stringify(this.configForm))
- },
- // 全屏
- clickFullScreen() {
- this.fullScreen = !this.fullScreen
- },
- // 点击聊天记录切换聊天
- async clickDialogue(value1, value2, index) {
- console.log(value2, 'value2');
- let _this = this
- this.audioUrl = ""
- this.messageOptions = []
- this.returnMessage = []
- if (this.$route.query.characterId) {
- this.$route.query.characterId = value1.id
- }
- if (!value2) {
- return
- }
- // 如果点击的是场景聊天
- if (value2.sceneId) {
- this.sceneId = value2.sceneId
- await this.getDetail(value1.id)
- // 从角色详情中获取场景列表
- let sceneList = this.info.sceneList
- // 获取当前聊天对应的场景
- for (let i = 0; i < sceneList.length; i++) {
- const element = sceneList[i];
- if (element.sceneId == value2.sceneId) {
- _this.sceneInfo = element
- }
- }
- } else {
- this.sceneId = null
- this.sceneInfo = {}
- await this.getDetail(value1.id)
- }
- this.recordId = value2.id
- this.getChatRecord(value2.id)
- this.recordsIndex = index
- },
- // 场景下拉菜单
- handleCommand3(value) {
- if (value == 1) {
- } else if (value == 2) {
- // 清空聊天记录
- this.returnMessage = []
- // 新建场景对话
- this.newStart()
- // 增加开场白
- this.returnMessage.push({
- role: 'assistant',
- content: this.sceneInfo.sceneWelcome
- })
- } else if (value == 3) {
- // 回到常规对话
- // 遍历当前聊天记录是否有常规对话
- let messages = null
- for (let i = 0; i < this.allRecords.length; i++) {
- const element = this.allRecords[i];
- if (element.id == this.info.id) {
- messages = element
- }
- }
- console.log(messages, 'messages');
- let flag = false
- for (let i = 0; i < messages.chatCharacterList.length; i++) {
- const element = messages.chatCharacterList[i];
- if (!element.sceneId) {
- flag = true
- this.clickDialogue(messages, element, i)
- break
- }
- }
- // 如果聊天记录里没有常规对话,则新增一个常规对话
- if (!flag) {
- this.sceneId = null
- this.sceneInfo = {}
- this.newStart()
- }
- }
- },
- // 聊天记录列表-角色下拉菜单
- handleCommand2(value, value1) {
- if (value == 2) {
- this.$confirm('此操作将永久删除该角色所有聊天记录, 是否继续?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.clearRecord(value1)
- })
- }
- },
- // 聊天记录列表-角色-聊天记录下拉菜单
- handleCommand(value, value1, value2) {
- console.log(value, 'value');
- if (value == 1) {
- this.editDialogueTitle(value1, value2)
- } else if (value == 2) {
- this.$confirm('此操作将永久删除该聊天记录, 是否继续?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.clearRecord(value1, value2)
- })
- }
- },
- // 聊天记录操作相关↓
- handleCommand4(value1, value2, value3) {
- console.log(value1, 'value1');
- console.log(value2, 'value2');
- if (value1 == 1) {
- // 编辑
- let height = this.$refs[`message${value3}`][0].offsetHeight
- let width = this.$refs[`message${value3}`][0].offsetWidth
- value2.edit = true
- this.$nextTick(() => {
- this.$refs[`messageEditInput${value3}`][0].$el.children[0].style.height = height + 'px'
- this.$refs[`messageEdit${value3}`][0].style.width = width + 'px'
- })
- } else if (value1 == 2) {
- // 删除
- this.$confirm('此操作将永久删除该聊天记录, 是否继续?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.deleteRecord(value2, value3)
- })
- } else if (value1 == 3) {
- // 重新生成最后一句对话
- this.resetChat(value2, value3)
- }
- },
- // 重新生成最后一条聊天记录
- resetChat(value, index) {
- let params = {
- recordId: value.id,
- role: value.role,
- }
- deleteRecordApi(params).then(res => {
- this.returnMessage.splice(index, 1)
- this.getStreamChatWithWeb()
- })
- },
- // 编辑单条聊天记录-取消
- editCancel(value, index) {
- value.edit = false
- value.editContent = value.content
- },
- // 编辑单条聊天记录-保存
- editSave(value, index) {
- console.log(value, 'value');
- let params = {
- content: value.editContent,
- recordId: value.id,
- role: value.role,
- }
- updateRecordApi(params).then(res => {
- console.log(res, 'ressss');
- value.edit = false
- this.getChatRecord(this.recordId)
- })
- },
- // 删除单条聊天记录
- deleteRecord(value, index) {
- let params = {
- recordId: value.id,
- role: value.role,
- }
- deleteRecordApi(params).then(res => {
- this.$message({
- message: '删除成功',
- type: 'success'
- });
- this.getChatRecord(this.recordId)
- })
- },
- // 聊天记录新增对话
- async newStart_juese(value) {
- console.log(value, 'value');
- this.audioUrl = ""
- this.sceneId = null
- this.sceneInfo = {}
- this.messageOptions = []
- this.returnMessage = []
- // 点击聊天记录切换聊天
- if (this.$route.query.characterId) {
- this.$route.query.characterId = value.id
- }
- await this.getDetail(value.id)
- this.recordId = await this.addChat()
- for (let i = 0; i < this.allRecords.length; i++) {
- const element = this.allRecords[i];
- if (element.id == this.info.id) {
- element.chatCharacterList.push({
- chatTitle: "常规聊天",
- createTime: '',
- id: this.recordId
- })
- this.recordsIndex = element.chatCharacterList.length - 1
- }
- }
- },
- // 修改聊天记录标题
- editDialogueTitle(value1, value2) {
- this.$prompt('新标题名称', '修改标题', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- }).then(({ value }) => {
- let params = {
- chatTitle: value,
- id: value2.id
- }
- updateTitleApi(params).then(res => {
- console.log(res, '修改标题');
- this.$message({
- type: 'success',
- message: '修改成功!'
- });
- this.getChatCharacterRecords()
- })
- })
- },
- clearRecord(value1, value2) {
- let params = {
- characterId: value1.id,
- }
- if (value2) {
- params.recordId = value2.id
- }
- clearRecordApi(params).then(res => {
- this.$message({
- type: 'success',
- message: '删除成功!'
- });
- this.getChatCharacterRecords()
- })
- },
- async getChatRecord(recordId) {
- let params = {
- characterId: this.info.id,
- recordId: recordId
- }
- getChatRecordApi(params).then(res => {
- console.log(res, '查询到的聊天记录');
- let array = res.data
- // this.returnMessage
- this.historyRes = res.data
- if (this.returnMessage.length > 1) {
- this.returnMessage = this.returnMessage.splice(0, 1)
- }
- for (let i = 0; i < array.length; i++) {
- const element = array[i];
- let history = JSON.parse(element.history)
- history.map(item => {
- item.id = element.id
- item.uuid = element.uuid
- item.edit = false
- item.editContent = item.content
- if (item.role == 'assistant') {
- item.voiceFilePosition = element.voiceFilePosition
- }
- })
- this.returnMessage = [...this.returnMessage, ...history]
- }
- this.configForm.radio2 = this.modelList[0].id
- this.modelList.map(item => {
- if (item.id == array[array.length - 1].modelId) {
- this.configForm.radio2 = item.id
- }
- })
- })
- },
- searchHistory(characterId) {
- // 查看对话记录是否有选中的角色
- // console.log('查看对话记录是否有选中的角色');
- let flg = false
- for (let i = 0; i < this.allRecords.length; i++) {
- const element = this.allRecords[i];
- // 如果有则选中角色的最新一条记录并获取对话聊天记录
- if (element.id == characterId) {
- // 当前角色的聊天记录列表
- this.historyList = element.chatCharacterList
- this.jsHistory = element
- console.log(element, '查看对话记录是否有选中的角色');
- flg = true
- element.open = true
- // 二级对话判断当前是普通对话还是场景对话
- // 取对应的最后一条消息记录
- let chatCharacterList = element.chatCharacterList
- let chatCharacter = null
- let index = null
- for (let i = 0; i < chatCharacterList.length; i++) {
- const element2 = chatCharacterList[i];
- if (this.$route.query.sceneId == element2.sceneId) {
- chatCharacter = element2
- index = i
- break;
- } else if (!this.$route.query.sceneId && !element2.sceneId) {
- chatCharacter = element2
- index = i
- break;
- }
- }
- // 如果有聊天记录则点击
- if (chatCharacter) {
- this.clickDialogue(element, chatCharacter, index)
- } else {
- // 如果没有场景聊天记录则新增场景聊天
- // 从角色详情中获取场景列表
- let sceneList = this.info.sceneList
- // 获取当前聊天对应的场景
- for (let i = 0; i < sceneList.length; i++) {
- const element = sceneList[i];
- if (element.sceneId == this.$route.query.sceneId) {
- this.sceneInfo = element
- }
- }
- }
- // this.recordsIndex = element.chatCharacterList.length - 1
- // let recordId = element.chatCharacterList[element.chatCharacterList.length - 1].id
- // this.recordId = recordId
- // this.getChatRecord(recordId)
- }
- }
- // 如果没有聊天记录,则增加开场聊天引导
- if (!flg) {
- this.getGuidance(this.info.id)
- }
- },
- async getChatCharacterRecords() {
- let res = await getChatCharacterRecordsApi()
- console.log(res, '聊天记录');
- if (res.data) {
- for (let i = 0; i < res.data.length; i++) {
- const element = res.data[i];
- if (this.info.id == element.id) {
- element.open = true
- } else {
- element.open = false
- }
- }
- this.allRecords = res.data
- }
- },
- // 新增对话记录
- async addChat() {
- let params = {
- characterId: this.info.id,
- chatTitle: ""
- }
- if (this.sceneId) {
- params.sceneId = this.sceneId
- }
- let res = await addChatApi(params)
- console.log(res, '新增对话记录');
- return res.data
- },
- getModelList() {
- getModelListApi().then(res => {
- console.log(res, '模型列表');
- this.modelList = res.data
- })
- },
- inputBoxClick() {
- this.$refs.input.focus()
- },
- async getDetail(id) {
- let res = await detailApi(id)
- console.log(res, '角色详情');
- this.info = res.data
- // 模型选中角色默认模型
- this.configForm.radio2 = this.info.modelId
- // 如果用户有设置过模型使用用户设置模型
- let userConfig = Cookies.get(`userConfig_${this.$store.state.user.userId}_${this.info.id}`)
- if (userConfig) {
- userConfig = JSON.parse(userConfig)
- }
- if (userConfig && userConfig.radio2) {
- this.configForm.radio2 = userConfig.radio2
- }
- // let HistoryMessage = JSON.parse(localStorage.getItem(`[userId:${123},aiId:${this.info.id}]`));
- // if (HistoryMessage) {
- // this.returnMessage = HistoryMessage
- // } else {
- // this.returnMessage.push({
- // role: 'assistant',
- // content: this.info.firstContent
- // })
- // localStorage.setItem(`[userId:${123},aiId:${this.info.id}]`, JSON.stringify(this.returnMessage));
- // }
- if (this.sceneId) {
- console.log(this.sceneId);
- let sceneList = this.info.sceneList
- for (let i = 0; i < sceneList.length; i++) {
- const element = sceneList[i];
- if (element.sceneId == this.sceneId) {
- this.sceneInfo = element
- this.configForm.radio2 = element.modelId
- this.returnMessage.push({
- role: 'assistant',
- content: element.sceneWelcome
- })
- }
- }
- } else {
- this.returnMessage.push({
- role: 'assistant',
- content: this.info.firstContent
- })
- }
- },
- getGuidance(id) {
- getGuidanceApi(id).then(res => {
- console.log(res, '聊天引导');
- this.messageOptions = res.data
- })
- },
- goBack() {
- this.$router.back()
- },
- async newStart() {
- // 新业务,创建一个新的对话记录
- this.recordId = await this.addChat()
- for (let i = 0; i < this.allRecords.length; i++) {
- const element = this.allRecords[i];
- if (element.id == this.info.id) {
- if (this.sceneId) {
- element.chatCharacterList.push({
- chatTitle: "场景聊天",
- createTime: '',
- id: this.recordId,
- sceneIcon: true
- })
- } else {
- element.chatCharacterList.push({
- chatTitle: "常规聊天",
- createTime: '',
- id: this.recordId
- })
- }
- this.recordsIndex = element.chatCharacterList.length - 1
- }
- }
- this.returnMessage = []
- this.getDetail(this.info.id)
- // 废弃》》
- // 清空历史数据
- // localStorage.removeItem(`[userId:${123},aiId:${this.info.id}]`);
- // this.returnMessage = []
- // this.getDetail(this.info.id)
- },
- Enterkey(e) {
- if (e.keyCode == 13) {
- console.log(this.notChatSend, 'this.notChatSend');
- if (this.notChatSend) {
- return
- }
- if (!this.content) {
- return
- }
- this.getStreamChatWithWeb()
- }
- },
- // 前往创建场景
- toCreateScene() {
- this.$router.push({
- name: 'CreateScene',
- query: {
- characterId: this.info.id,
- }
- })
- },
- async getStreamChatWithWeb_old() {
- this.messageLoading = true
- if (!this.content) {
- return
- }
- this.returnMessage.push({
- role: 'user',
- content: this.content
- })
- let HistoryMessage = JSON.parse(localStorage.getItem(`[userId:${123},aiId:${this.info.id}]`));
- // 历史记录取最新的20条传给后端
- if (HistoryMessage && HistoryMessage.length > 20) {
- HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
- }
- let params = {
- historyMessage: HistoryMessage || [],
- characterId: this.info.id,
- prompt: JSON.parse(JSON.stringify(this.content))
- }
- // 清空输入框的值
- this.content = ''
- // 新增一条ai信息
- this.returnMessage.push({
- role: 'assistant',
- content: ''
- })
- let res = await streamChatWithWebApi(params)
- console.log(res.data.message.content, 'res');
- let content = res.data.message.content
- let index = 0
- let xing = 1
- let messageInterval = setInterval(() => {
- if (index > content.length - 1) {
- console.log('结束');
- // 消息全部显示后存到历史localStorage
- let HistoryMessage = []
- // 历史只存100条,长度超出截取最新的
- if (this.returnMessage.length > 100) {
- HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
- } else {
- HistoryMessage = JSON.stringify(this.returnMessage)
- }
- localStorage.setItem(`[userId:${123},aiId:${this.info.id}]`, HistoryMessage);
- clearInterval(messageInterval)
- return
- }
- console.log(content[index], 'content[index]');
- if (content[index] == "*") {
- if (xing == 1) {
- xing = 2
- this.returnMessage[this.returnMessage.length - 1].content += "("
- } else if (xing == 2) {
- xing = 1
- this.returnMessage[this.returnMessage.length - 1].content += ")"
- }
- } else {
- this.returnMessage[this.returnMessage.length - 1].content += content[index]
- }
- index += 1
- }, 50)
- },
- async getStreamChatWithWeb() {
- if (this.chatLoading) {
- return
- }
- this.audioUrl = []
- this.audioUrlArrIndx = 0
- this.textCache = ''
- this.resError = false
- this.messageLoading = true
- this.chatLoading = true
- // 初始化定时器和audio的状态
- if (this.videoLoop) {
- clearInterval(this.videoLoop)
- this.videoLoop = null
- this.audioUrl = ''
- this.$refs.audio.currentTime = 0;
- this.$refs.audio.pause()
- }
- // 如果没有对话记录id,新增对话记录
- if (!this.recordId) {
- this.recordId = await this.addChat()
- }
- if (this.content) {
- this.returnMessage.push({
- role: 'user',
- content: this.content
- })
- }
- let HistoryMessage = JSON.parse(JSON.stringify(this.returnMessage))
- console.log(HistoryMessage, 'HistoryMessage1');
- HistoryMessage = HistoryMessage.splice(1, HistoryMessage.length)
- console.log(HistoryMessage, 'HistoryMessage2');
- // 历史记录取最新的20条传给后端
- if (HistoryMessage && HistoryMessage.length > 20) {
- HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
- }
- let params = {
- historyMessage: HistoryMessage,
- characterId: this.info.id,
- prompt: this.content,
- modelId: this.configForm.radio2,
- recordId: this.recordId,
- }
- // 如果当前是场景对话则需要传sceneId
- if (this.sceneId) {
- params.sceneId = this.sceneId
- }
- // 新增一条ai信息
- this.returnMessage.push({
- role: 'assistant',
- content: '',
- voiceFilePosition: '',
- })
- // 清空输入框的值
- this.content = ''
- this.timeOut = 'start'
- let res = await streamChatWithWebApi(params)
- console.log(res, 'res');
- this.messageLoading = false
- if (res.status != 200) {
- this.$message.error(res.statusText)
- this.returnMessage.splice(this.returnMessage.length - 1, 2)
- this.chatLoading = false
- return
- }
- const reader = res.body.getReader()
- const decoder = new TextDecoder()
- while (1) {
- const { done, value } = await reader.read()
- // console.log(done, 'done');
- if (done) {
- console.log(value, '结束value');
- this.chatLoading = false
- // if (typeof(value) == "undefined") {
- // this.$message.error('错误')
- // this.returnMessage.splice(this.returnMessage.length - 1, 1)
- // }
- // 每次对话完刷新聊天记录
- this.getChatCharacterRecords()
- let params = {
- characterId: this.info.id,
- recordId: this.recordId
- }
- let historyRes = await getChatRecordApi(params)
- this.historyRes = historyRes.data
- // 如果开启自动播放,则查询并播放语音
- // this.audioUrl = ""
- // if (!this.resError) {
- // }
- console.log('结束');
- if (this.timeOut) {
- clearTimeout(this.timeOut)
- this.timeOut = null
- }
- break;
- }
- //txt就是一个一个的字 然后添加到页面上就可以了
- const txt = decoder.decode(value).split('data:')
- // const txt = decoder.decode(value)
- if (this.timeOut) {
- clearTimeout(this.timeOut)
- this.timeOut = setTimeout(() => {
- this.returnMessage[this.returnMessage.length - 1].content = 'AI智能累得打起了小盹儿,咱们让他缓一缓,再来试试!'
- this.chatLoading = false
- }, 10000)
- }
- // console.log(txt, 'txt');
- this.addMessage(txt)
- // let data = JSON.parse(txt).message.content
- // console.log(txt, 'txt');
- // if (this.isJSON(txt) && JSON.parse(txt).code != 200) {
- // let data = JSON.parse(txt)
- // this.$message.error(data.msg)
- // this.returnMessage.splice(this.returnMessage.length - 1, 1)
- // break;
- // }
- }
- },
- addMessage(text) {
- for (let i = 0; i < text.length; i++) {
- const element = text[i];
- if (this.isJSON(element)) {
- let value = JSON.parse(element)
- if (value.code == 401) {
- this.returnMessage.splice(this.returnMessage.length - 1, 1)
- this.noLogin(element)
- this.chatLoading = false
- break;
- }
- if (value.code == 500) {
- // console.log(value, 'JSON.parse(element)');
- this.returnMessage.splice(this.returnMessage.length - 2, 2)
- // this.$message.error(value.content || 'AI智能没有理解你的意思,换个说法试试吧!')
- this.returnMessage[this.returnMessage.length - 1].content = 'AI智能可能没有理解你的意思,换个说法试试吧!'
- this.resError = true
- this.chatLoading = false
- break;
- }
- // 获取语音id
- if (value.code == -1) {
- this.returnMessage[this.returnMessage.length - 1].uuid = value.content
- // 如果开启了自动播放开始轮巡查询语音
- if (this.setting.value1) {
- this.audioUrlArrIndx = 0
- this.videoLoopTime = 0
- this.audioUrlArr = []
- this.loopPlay = false
- this.audioPlayIndex = this.returnMessage.length - 1
- this.loopGetVoice(value.content, this.returnMessage.length - 1)
- }
- break;
- }
- if (!value.done) {
- let txt = value.content
- this.returnMessage[this.returnMessage.length - 1].content += txt
- } else {
- // 对话流结束
- // 临时插入的ai对话记录和用户记录插入聊天id
- this.returnMessage[this.returnMessage.length - 1].id = value.content
- this.returnMessage[this.returnMessage.length - 2].id = value.content
- }
- }
- }
- },
- noLogin(value) {
- let _this = this
- MessageBox.confirm(
- value.content,
- "系统提示",
- {
- confirmButtonText: "前往登录",
- cancelButtonText: "取消",
- type: "warning",
- }
- )
- .then(() => {
- // isRelogin.show = false;
- this.$store.dispatch("LogOut").then(() => {
- // location.href = "/";
- _this.$refs.Header.showLogin()
- });
- })
- .catch(() => {
- // isRelogin.show = false;
- });
- },
- isJSON(str) {
- if (typeof str == 'string') {
- try {
- JSON.parse(str);
- return true;
- } catch (e) {
- // console.log(e);
- return false;
- }
- }
- },
- messageOptionClick(value) {
- this.content = value
- this.getStreamChatWithWeb()
- },
- // 播放聊天语音
- initAudio(index) {
- // 从聊天记录获取id
- console.log(this.returnMessage[index], 'swssssssss');
- let id = this.returnMessage[index].id
- let history = this.historyRes.map((item) => {
- if (item.id == id) {
- return item
- }
- })
- console.log(history, 'history');
- return
- this.loopGetVoice(history.id, index)
- },
- playAudio(index) {
- if (index) {
- this.audioPlayIndex = index
- }
- // this.audioUrlArrIndx = 0
- console.log(this.audioUrlArrIndx, 'this.audioUrlArrIndx');
- console.log(this.audioUrlArr, 'this.audioUrlArr');
- // console.log(audioUrlArr[this.audioUrlArrIndx].voiceAddress, 'audioUrlArr[this.audioUrlArrIndx].voiceAddress');
- // 如果当前播放的语音地址为空,则等待2秒后重新获取
- if (!this.audioUrlArr[this.audioUrlArrIndx].voiceAddress && this.videoLoopTime <= 50) {
- setTimeout(() => {
- this.playAudio(index)
- }, 2000)
- return
- }
- // 如果当前播放的语音地址为false,则跳过当前语音
- if (this.audioUrlArr[this.audioUrlArrIndx].voiceAddress == 'false' && this.audioUrlArrIndx < this.audioUrlArr.length - 1) {
- this.audioUrlArrIndx += 1
- this.playAudio(index)
- return
- } else if (this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
- this.audioUrl = ''
- this.audioUrlArrIndx = 0
- this.videoLoopTime = 0
- this.audioUrlArr = []
- return
- }
- this.audioUrl = this.audioUrlArr[this.audioUrlArrIndx].voiceAddress
- console.log(this.audioUrl, 'this.audioUrl');
- setTimeout(() => {
- if (this.audioUrl) {
- this.$refs.audio.play()
- }
- }, 1000)
- },
- async historyGetVoice(value, index) {
- console.log(value, 'value');
- if (!value.uuid) {
- return
- }
- let params = {
- uuid: value.uuid
- }
- let fileRes = await queryVoiceListApi(params)
- console.log(fileRes.data, 'file>>>');
- this.audioUrlArr = fileRes.data.voices
- this.audioUrlArrIndx = 0
- this.videoLoopTime = 0
- this.loopPlay = false
- this.playAudio(index)
- },
- async loopGetVoice(id, index) {
- this.videoLoopTime += 1
- // 获取语音文件
- let params = {
- uuid: id
- }
- let fileRes = await queryVoiceListApi(params)
- console.log(fileRes.data, 'file>>>');
- let voices = fileRes.data.voices
- let _continue = fileRes.data.continue
- this.apiHaveVideo = _continue
- // 判断语音文件是否获取完毕
- let trueLength = 0
- for (let i = 0; i < voices.length; i++) {
- const element = voices[i];
- if (element.voiceAddress) {
- trueLength += 1
- }
- }
- if (!_continue && trueLength == voices.length) {
- if (this.videoLoop) {
- clearTimeout(this.videoLoop)
- this.videoLoop = null
- }
- // this.returnMessage[index].voiceFilePosition = fileRes.data.voiceFilePosition
- // 有语音文件则开始播放
- if (voices.length > 0) {
- this.audioUrlArr = voices
- console.log(this.audioUrlArr, '进入播放流程1');
- if (this.$refs.audio.paused || this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
- if (this.loopPlay == false) {
- this.playAudio(index)
- this.loopPlay = true
- }
- }
- }
- } else {
- if (voices.length > 0) {
- this.audioUrlArr = voices
- console.log(this.audioUrlArr, '进入播放流程2');
- if (this.$refs.audio.paused || this.audioUrlArrIndx > this.audioUrlArr.length - 1) {
- if (this.loopPlay == false) {
- this.playAudio(index)
- this.loopPlay = true
- }
- }
- }
- // 没有语音文件则开启定时器每两秒查询一次
- if (this.videoLoopTime > 50) {
- // 如果调用次数超过30次则停止
- return
- }
- this.videoLoop = setTimeout(() => {
- this.loopGetVoice(id, index)
- }, 2000)
- }
- },
- audioEnd() {
- this.audioUrlArrIndx += 1
- console.log(this.audioUrlArrIndx, 'audioUrlArrIndx');
- if (this.audioUrlArrIndx < this.audioUrlArr.length && this.videoLoopTime <= 50) {
- // 如果当前播放的语音地址为空,则等待2秒后重新获取
- if (!this.audioUrlArr[this.audioUrlArrIndx].voiceAddress) {
- setTimeout(() => {
- this.playAudio()
- }, 2000)
- return
- }
- // 如果当前播放的语音地址为false,则跳过当前语音
- if (this.audioUrlArr[this.audioUrlArrIndx].voiceAddress == 'false' && this.audioUrlArrIndx < this.audioUrlArr.length - 1) {
- this.audioUrlArrIndx += 1
- this.playAudio()
- return
- }
- this.audioUrl = this.audioUrlArr[this.audioUrlArrIndx].voiceAddress
- setTimeout(() => {
- if (this.audioUrl) {
- this.$refs.audio.play()
- }
- }, 100)
- } else {
- this.audioUrlArrIndx = []
- this.audioUrlArrIndx = 0
- console.log('播放结束');
- this.audioPlayIndex = null
- }
- },
- ChangeSettingValue1(value) {
- console.log(value, 'value');
- if (value) {
- queryUserBalanceApi().then(res => {
- console.log(res, '用户余额');
- if (res.data <= 0) {
- this.setting.value1 = false
- this.$message({
- message: '余额不足,无法开启次功能',
- type: 'error'
- });
- }
- })
- }
- },
- // 场景业务相关↓
- async sceneChange(item) {
- console.log(item, 'item');
- this.$message({
- message: `已进入场景${item.sceneName}`,
- type: 'success'
- });
- // 当用户选中场景
- this.sceneInfo = item
- // 切换当前场景id
- this.sceneId = item.sceneId
- // 情况聊天记录
- this.returnMessage = []
- // 新建场景对话
- this.newStart()
- // 增加开场白
- // this.returnMessage.push({
- // role: 'assistant',
- // content: item.sceneWelcome
- // })
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .chat {
- min-height: 100vh;
- }
- .top {
- position: relative;
- width: 100%;
- height: 50px;
- display: flex;
- align-items: center;
- }
- .chatContent {
- // margin-top: 30px;
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- display: flex;
- height: calc(100% - 50px - 63px);
- justify-content: center;
- width: 100vw;
- // margin-bottom: 63px;
- >.leftImg {
- border-radius: 16px 16px 16px 16px;
- height: 95%;
- position: relative;
- min-width: 380px;
- >img {
- border: 6px solid;
- border-image: linear-gradient(180deg, #b48733, #e8cf97, #b48733) 6 6;
- height: 99%;
- margin-right: 5px;
- }
- >.aiInfo {
- background-color: #ffffff1a;
- border-radius: 24px 24px 24px 24px;
- bottom: -70px;
- display: flex;
- height: 193px;
- justify-content: center;
- left: 50%;
- position: absolute;
- transform: translate(-50%, -50%);
- width: 100%;
- >.photo {
- width: 64px;
- height: 64px;
- font-size: 18px;
- position: absolute;
- top: -20px;
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- color: #fff;
- line-height: 1.5714285714285714;
- list-style: none;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
- display: inline-flex;
- justify-content: center;
- align-items: center;
- overflow: hidden;
- white-space: nowrap;
- text-align: center;
- vertical-align: middle;
- background: rgba(0, 0, 0, 0.25);
- border: 1px solid transparent;
- border-radius: 50%;
- }
- >.info {
- padding-top: 50px;
- width: 80%;
- >.name {
- color: #fff;
- font-size: 22px;
- font-style: normal;
- font-weight: 700;
- line-height: 31px;
- text-align: center;
- text-transform: none;
- }
- >.tags {
- padding-bottom: 8px;
- text-align: center;
- >.tag {
- margin-right: 4px;
- background: #ffffff1a;
- border-radius: 4px 4px 4px 4px;
- color: #fff;
- font-size: 12px;
- font-weight: 500;
- line-height: 17px;
- border-color: transparent;
- }
- }
- >.infoContent {
- color: #fff;
- font-size: 16px;
- font-style: normal;
- font-weight: 500;
- height: 80px;
- line-height: 22px;
- overflow-y: auto;
- text-align: center;
- text-transform: none;
- }
- }
- }
- }
- >.chat-box {
- position: relative;
- // background: linear-gradient(rgba(255, 255, 255, 0) 75%, rgb(34, 34, 34));
- background-position: 50%;
- background-repeat: no-repeat;
- background-size: auto;
- // border: 1px solid #635677 !important;
- box-sizing: border-box;
- // color: #fff !important;
- display: flex;
- flex-direction: column;
- height: 100%;
- // margin: 0 auto;
- width: 100%;
- padding: 10px;
- }
- >.mask {
- background: rgba(0, 0, 0, 0.5);
- }
- }
- .messages {
- // height: calc(100% - 68px );
- overflow-y: auto;
- }
- .messages::-webkit-scrollbar {
- width: 0px;
- }
- .message {
- background: var(--bg-color3);
- // border: 1px solid #5b5b5e;
- max-width: calc(100% - 72px);
- // flex: 1;
- min-width: 80px;
- height: max-content;
- }
- .message2 {
- color: #000;
- background: rgba(200, 200, 200, 0.6);
- // border: 1px solid #5b5b5e;
- max-width: calc(100% - 72px);
- // flex: 1;
- min-width: 80px;
- height: max-content;
- }
- .photo {
- width: 56px;
- }
- .me {
- flex-direction: row-reverse;
- }
- .loadingMessage {
- position: relative;
- // display: flex;
- // justify-content: center;
- width: 60px;
- height: 32px;
- }
- .popup1 {
- .configForm {
- margin-bottom: 75px;
- }
- .bottom {
- position: absolute;
- bottom: 0;
- height: 75px;
- padding-bottom: 15px;
- background: var(--bg-color5);
- width: calc(100% - 30px);
- overflow-x: auto;
- .button {
- height: 100%;
- width: 80px;
- display: inline-flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- font-size: 11px;
- .icon {
- width: 34px;
- height: 34px;
- background: #6a6a6a;
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 50%;
- font-size: 18px;
- margin-bottom: 10px;
- }
- }
- .button:not(:first-child) {
- margin-left: 20px;
- }
- }
- }
- .popup2 {
- height: 100%;
- }
- </style>
- <style scoped>
- .loadingMessage>>>.el-loading-parent--relative {
- width: 100%;
- height: 100%;
- }
- .loadingMessage>>>.el-loading-spinner {
- display: flex;
- justify-content: center;
- }
- .mButton {
- border: solid 0px;
- }
- .tm_button {
- width: 26px;
- height: 26px;
- font-size: 20px;
- padding: 0px;
- }
- .phoneBottomPopup>>>.el-dialog {
- margin-top: 0 !important;
- position: absolute;
- bottom: 0;
- min-height: 60%;
- background: rgba(255, 255, 255, 0);
- }
- .phoneBottomPopup>>>.el-dialog__header {
- padding: 0;
- }
- .phoneBottomPopup>>>.el-dialog__body {
- color: #fff;
- padding: 0;
- width: 100%;
- height: 100%;
- position: absolute;
- bottom: 0;
- }
- .phoneBottomPopup .popupContent {
- background: var(--bg-color5);
- color: #fff;
- width: 100%;
- height: 100%;
- padding: 10px 15px;
- position: relative;
- }
- .radio>>>.el-radio-button {
- margin-bottom: 5px;
- margin-right: 10px;
- /* border-left: solid 1px #DCDFE6; */
- }
- .radio>>>.el-radio-button__inner {
- border-left: solid 1px #DCDFE6;
- background-color: var(--bg-color5);
- border-radius: 4px;
- color: #fff;
- }
- .radio>>>.el-radio-button__orig-radio:checked+.el-radio-button__inner {
- border-left: solid 1px var(--bg-color1);
- background-color: var(--bg-color1);
- border-color: var(--bg-color1);
- -webkit-box-shadow: -1px 0 0 0 var(--bg-color1);
- box-shadow: -1px 0 0 0 var(--bg-color1);
- }
- </style>
|