123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446 |
- <template>
- <div class="chat bg-white">
- <div class="flex items-center h-screen relative" >
- <div class="leftInfo relative" v-show="!showNodata" >
- <div class="history">
- <!-- historyActive -->
- <div class="historyBox" v-for="(item, index) in allRecords" :key="index">
- <div class="top flex w-full cursor-pointer" @click="item.open = !item.open">
- <div class="photo">
- <img class="object-cover" :src="baseApi + item.picture" alt="">
- </div>
- <div class="info flex-1 ml-2 flex flex-col justify-around">
- <div class="text-sm font-semibold">{{ item.characterName }}</div>
- <div class="text-sm text-gray-400">{{ item.prologue }}</div>
- </div>
- <div class="flex items-center text-lg" style="height: 48px;">
- <i v-if="!item.open" class="el-icon-arrow-down" style="font-size: 16px;"></i>
- <i v-else class="el-icon-arrow-up" style="font-size: 16px;"></i>
- </div>
- </div>
- <!-- <transition name="el-zoom-in-top"> -->
- <div class="dialogueList mt-4" v-show="item.open">
- <div class="flex justify-between mb-2">
- <el-button v-if="item.isDelete == 0" type="primary" class="flex-1" round @click="newStart_juese(item)">新的对话</el-button>
- <div v-else class="flex-1 rounded border border-gray-200 text-sm p-2 text-gray-600">
- {{item.characterName}}已经被设定为私有或被创建者删除。
- </div>
- <el-dropdown
- class="flex justify-end items-center ml-2"
- trigger="click"
- @command="handleCommand2($event, item)"
- >
- <div class=" w-8 h-8 bg-gray-100 rounded flex justify-center items-center cursor-pointer">
- <i slot="reference" class="el-icon-more"></i>
- </div>
- <el-dropdown-menu slot="dropdown" >
- <el-dropdown-item command="2">删除</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </div>
- <div class="dialogue flex justify-between items-center w-full h-16 rounded-md px-2 mb-2 cursor-pointer relative"
- :class="info.id == item.id && index2 == recordsIndex && 'historyActive'"
- v-for="(item2, index2) in item.chatCharacterList"
- :key="index2"
- @click.self="clickDialogue(item, item2, index2)"
- >
- <span @click.self="clickDialogue(item, item2, index2)">
- <i v-if="item2.sceneId || item2.sceneIcon" class="el-icon-video-camera"></i>
- {{ item2.chatTitle }}
- </span>
- <span @click.self="clickDialogue(item, item2, index2)" class=" text-sm text-gray-500">{{ item2.createTime }}</span>
- <el-dropdown
- class="dialogueIconBg absolute h-16 w-20 right-0 flex justify-end items-center pr-4"
- trigger="click"
- @command="handleCommand($event, item, item2 )"
- >
- <div >
- <i slot="reference" class="el-icon-more"></i>
- </div>
- <el-dropdown-menu slot="dropdown" >
- <el-dropdown-item command="1">修改标题</el-dropdown-item>
- <el-dropdown-item command="2">删除</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </div>
- </div>
- <!-- </transition> -->
- </div>
- </div>
- <!-- 角色详情 -->
- <div class="chatInfo absolute top-0 left-0 w-full h-full z-30 bg-white" v-show="showInfo == 1">
- <div class="flex flex-col p-4 h-full">
- <div class="flex justify-between items-center text-base mb-3">
- <div>角色详情</div>
- <i class="el-icon-close cursor-pointer" @click="showInfoChange(0)"></i>
- </div>
- <div class="flex">
- <span class="chatInfo_photo">
- <img class="w-16 h-16 rounded-full overflow-hidden object-cover" :src="baseApi + info.picture">
- </span>
- <div class="w-0 flex-1 ml-2">
- <div class="text-xl font-medium">{{ info.characterName }}</div>
- <div class="mt-1 text-gray-500 text-sm">Rochat No.75680</div>
- </div>
- </div>
- <div class="flex mt-4">
- <div class="hot flex w-auto items-center px-2 py-1 text-sm mr-2 rounded-full">
- <v-icon name="fire" scale="1"/>
- <span class="ml-1">{{ info.hotCount }}</span>
- </div>
- </div>
- <div class="flex justify-around mt-4">
- <div class=" rounded-full w-32 text-center py-2 text-sm cursor-pointer" style="border: solid 1px #000">分享</div>
- <div class=" rounded-full w-32 text-center py-2 text-sm cursor-pointer" style="border: solid 1px #000" @click="showDetail = !showDetail">角色详情</div>
- </div>
- <transition name="el-zoom-in-top">
- <div v-show="showDetail" >
- <div class="mt-5 text-base pb-3 border-b border-gray-200">
- <div>
- <i class="el-icon-document"></i>
- 简介
- </div>
- <div class="mt-3 text-sm max-h-48">
- {{ info.prologue }}
- </div>
- </div>
- <div class="flex justify-between items-center mt-5 text-base pb-3 border-b border-gray-200">
- <div>
- <i class="el-icon-user"></i>
- 创作人
- </div>
- <div class="text-sm max-h-48">
- {{ info.author }}
- </div>
- </div>
- <div class="flex justify-between items-center mt-5 text-base pb-3 border-b border-gray-200">
- <div>
- <i class="el-icon-time"></i>
- 创建时间
- </div>
- <div class="text-sm max-h-48">
- {{ info.createTime }}
- </div>
- </div>
- <div class="mt-5 text-base">
- <i class="el-icon-discount"></i>
- 特征
- </div>
- <div class="tags mt-4 pb-3">
- <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
- {{ item }}
- </span>
- </div>
- </div>
- </transition>
- <div class="mt-5 text-base pb-3 flex-1 flex flex-col">
- <div>
- <!-- <i class="el-icon-document"></i> -->
- 查看场景
- </div>
- <div class="mt-3 text-sm max-h-48">
- <div v-if="info.sceneList && info.sceneList.length > 0" class="grid grid-cols-2 gap-2 mb-4 cursor-pointer">
- <template v-for="(item, index) in info.sceneList" >
- <div v-if="item.isDelete == 0" class="flex flex-col justify-center" :key="index" @click="sceneChange(item)">
- <img class=" rounded w-full h-24 object-cover" :src="baseApi + item.background" alt="">
- <div>{{ item.sceneName }}</div>
- <div class=" text-gray-400">{{ item.sceneDescription }}</div>
- </div>
- </template>
- </div>
- <el-empty v-else description="暂无场景"></el-empty>
- <div class="flex justify-center mt-10">
- <el-button type="primary" round @click="toCreateScene">创建场景</el-button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 场景详情 -->
- <div class="absolute top-0 left-0 w-full h-full z-30 bg-white overflow-hidden" v-show="showInfo == 2">
- <div class="flex flex-col p-4 h-full">
- <div class="flex justify-between items-center text-base mb-3">
- <div>场景详情</div>
- <i class="el-icon-close cursor-pointer" @click="showInfoChange(0)"></i>
- </div>
- <div class=" text-gray-500">{{ sceneInfo.sceneDescription }}</div>
- </div>
- </div>
- </div>
- <div class="content" :class="fullScreen && 'contentFull'" v-show="!showNodata" :style="{ backgroundImage: `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="fullScreenButton rounded-full border border-gray-200" @click="clickFullScreen">
- <i class="el-icon-arrow-left" v-if="!fullScreen"></i>
- <i class="el-icon-arrow-right" v-else></i>
- </div>
- <div class="chat-box" >
- <div class="chatBoxTitle absolute top-0">
- <div>
- {{ info.characterName }}
- <el-dropdown
- v-if="sceneId"
- class="flex justify-end items-center"
- trigger="click"
- @command="handleCommand3"
- @visible-change="dropdown3Show = !dropdown3Show"
- >
- <div>
- <div slot="reference" class="flex justify-center items-center cursor-pointer text-white">
- 已进入场景:{{ sceneInfo.sceneName }}
- <i class="el-icon-arrow-up ml-2" style="font-size: 16px;" v-if="dropdown3Show"></i>
- <i class="el-icon-arrow-down ml-2" style="font-size: 16px;" v-else></i>
- </div>
- </div>
- <el-dropdown-menu slot="dropdown" >
- <el-dropdown-item command="1">场景详情</el-dropdown-item>
- <el-dropdown-item command="2">新场景对话</el-dropdown-item>
- <el-dropdown-item command="3">回到常规对话</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </div>
- <div class="flex w-60 justify-end">
- <el-tooltip effect="dark" content="设定" placement="bottom">
- <el-popover
- placement="bottom"
- width="400"
- trigger="click">
- <div class="p-2">
- <div class="flex justify-between mb-4">
- <span>语音自动播放</span>
- <el-switch v-model="setting.value1">
- </el-switch>
- </div>
- <div class="flex justify-between">
- <span>全屏幕</span>
- <el-switch v-model="fullScreen">
- </el-switch>
- </div>
- </div>
- <i slot="reference" class="fa-solid fa-sliders cursor-pointer text-xl mr-6"></i>
- </el-popover>
- </el-tooltip>
- <el-tooltip effect="dark" content="角色详情" placement="bottom">
- <i class="fa-solid fa-file-invoice cursor-pointer text-xl mr-6" @click="showInfoChange(1)"></i>
- </el-tooltip>
- <el-tooltip v-if="!sceneId" effect="dark" content="开始新的对话" placement="bottom">
- <i class="fa-solid fa-comment-medical cursor-pointer text-xl mr-6" @click="newStart"></i>
- </el-tooltip>
- <el-tooltip effect="dark" content="历史对话" placement="bottom">
- <i class="fa-solid fa-clock-rotate-left cursor-pointer text-xl" @click="showInfoChange(0)"></i>
- </el-tooltip>
- </div>
- </div>
- <audio ref="audio" muted v-show="false" :src="audioUrl" autoplay="" @ended="audioEnd" controls></audio>
- <div class="messages" ref="messages">
- <template v-for="(item, index) in returnMessage">
- <div v-if="item.role == 'assistant'" class="mb-4 flex" :key="index">
- <!-- ai返回的信息 -->
- <div class="pt-2 photo">
- <img
- :src="baseApi + info.picture"
- class="rounded-full w-14 h-14 object-cover"
- />
- </div>
- <div class="messageRight">
- <div class="message mt-4 ml-4 p-2 rounded-r-md rounded-bl-md text-xs" >
- <div v-if="item.voiceFilePosition" class="yyPlayBg mb-2 w-16 p-1 cursor-pointer" >
- <img v-if="audioPlayIndex == index" class="yyPlay" src="@/assets/images/播放/yyPlay1.png" alt="" >
- <img v-else class="yyPlay" src="@/assets/images/播放/yyPlay.png" alt="" @click="playAudio(item.voiceFilePosition, index)">
- <!-- <svg-icon class="icon" class-name="speech-icon" icon-class="speech" @click.stop="click" /> -->
- </div>
- <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 class="messageOptions" v-show="returnMessage.length == 1">
- <div class="option" v-for="(item, index) in messageOptions" :key="index" @click="messageOptionClick(item.chatGuidance)">
- {{ item.chatGuidance }}
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 用户发送的信息 -->
- <div v-if="item.role == 'user'" class="mb-4 flex me" :key="index">
- <div class="pt-2 photo">
- <img
- :src="$store.state.user.avatar"
- class="rounded-full w-14 h-14 object-cover"
- />
- </div>
- <div class="messageRight">
- <div class="messageUser mr-4 mt-4 p-2 rounded-l-md rounded-br-md text-xs">
- {{ item.content }}
- </div>
- </div>
- </div>
- </template>
- </div>
- <div class="inputBox absolute left-2 right-2 bottom-4 " @click="inputBoxClick">
- <input
- v-if="canChat"
- ref="input"
- type="text"
- placeholder="输入消息..."
- class="w-full flex-1 py-2 px-4 rounded-l-lg text-sm focus:outline-none"
- v-model="content"
- @keydown="Enterkey"
- />
- <div class="flex pt-6 justify-between" v-if="canChat">
- <div class="tools w-36 flex justify-between">
- <i class="el-icon-setting text-2xl cursor-pointer" @click="showConfig = true"></i>
- <i class="el-icon-picture-outline text-2xl cursor-pointer"></i>
- <i class="el-icon-s-opportunity text-2xl cursor-pointer"></i>
- </div>
- <button @click.prevent="getStreamChatWithWeb" class="px-4 rounded-r-lg text-sm text-gray-400" :class="content && 'planeColor'">
- <v-icon name="paper-plane" scale="1.5"/>
- </button>
- </div>
- <div v-if="!canChat" class=" flex w-full text-sm text-gray-400">
- 当前角色或场景已被设定为私有或被创作者删除,但你任然可以查看历史对话。
- </div>
- </div>
- </div>
- </div>
- <div v-show="showNodata" class=" w-full h-full flex flex-col justify-center items-center">
- <el-empty description="暂无聊天记录,点击按钮并探索新角色。"></el-empty>
- <el-button type="primary" round @click="$router.push('/')">寻找一个角色</el-button>
- </div>
- <el-dialog
- title="配置"
- :visible.sync="showConfig"
- width="500px"
- >
- <p class="text-base">身分</p>
- <p class="text-sm text-gray-400">选择你和角色聊天的身份</p>
- <div class="flex items-start py-3 border-b border-gray-200">
- <el-radio-group class="radio" v-model="configForm.radio1">
- <el-radio-button label="上海"></el-radio-button>
- </el-radio-group>
- <el-button>
- <i class="el-icon-sort" style="transform: rotate(90deg);"></i>
- </el-button>
- </div>
- <p class="text-base mt-3">选择模型</p>
- <p class="text-sm text-gray-400">每个模型可能有不同的效果,仅对当前对话有影响</p>
- <div class="flex items-start py-3 border-b border-gray-200">
- <el-radio-group class="radio" v-model="configForm.radio2">
- <el-radio-button v-for="(item, index) in modelList" :key="index" :label="item.id">{{ item.model }}</el-radio-button>
- </el-radio-group>
- </div>
- <p class="text-base mt-3">角色语言</p>
- <p class="text-sm text-gray-400">请选择角色的回复语言</p>
- <div class="flex items-start py-3">
- <el-radio-group class="radio" v-model="configForm.radio3">
- <el-radio-button label="自动"></el-radio-button>
- <el-radio-button label="简体中文"></el-radio-button>
- <el-radio-button label="繁體中文"></el-radio-button>
- <el-radio-button label="English"></el-radio-button>
- <el-radio-button label="Español"></el-radio-button>
- </el-radio-group>
- </div>
- <span slot="footer" class="dialog-footer">
- <el-button @click="showConfig = false">取 消</el-button>
- <el-button type="primary" @click="showConfig = false">确 定</el-button>
- </span>
- </el-dialog>
- </div>
- </div>
- </template>
- <script>
- import Header from "@/views/homeComponents/Header.vue"
- import {
- streamChatWithWebApi,
- getGuidanceApi,
- getModelListApi,
- addChatApi,
- getChatCharacterRecordsApi,
- getChatRecordApi,
- updateTitleApi,
- clearRecordApi,
- getVoiceFileApi
- } from "@/api/chat.js"
- import { detailApi } from "@/api/detail.js"
- import { Message, MessageBox, Notification, Loading } from 'element-ui'
- import 'vue-awesome/icons/paper-plane'
- import 'vue-awesome/icons/fire'
- export default {
- components: {
- Header
- },
- data() {
- return {
- // 是否全屏
- fullScreen: false,
- // 角色所有聊天列表
- allRecords: [],
- // 聊天记录id
- recordId: null,
- // 选中的角色聊天记录
- historyRes: [],
- recordsIndex: null,
- showDetail: false,
- showConfig: false,
- showInfo: 0,
- messageOptions: [],
- info: {},
- messageLoading: false,
- returnMessage: [],
- content: '',
- audioPlayIndex: null,
- configForm: {
- radio1: null,
- radio2: null,
- radio3: null,
- },
- // 对话接口是否返回错误状态
- resError: false,
- // 模型列表
- modelList: [],
- setting: {
- value1: false,
- value2: false,
- },
- //音频相关↓
- //音频地址
- audioUrl: '',
- videoLoop: null,
- videoLoopTime: 0,
- //场景下拉菜单
- 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
- }
- }
- },
- showNodata() {
- return this.allRecords.length == 0 && !this.$route.query.characterId
- }
- },
- mounted() {
- this.init()
- },
- watch: {
- '$route.query.characterId': {
- handler(newVal, lodVal) {
- console.log(newVal, 'newVal');
- if (!newVal) {
- this.$nextTick(() => {
- this.showInfoChange(0)
- })
- }
- },
- deep: true
- },
- 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
- }
- this.showInfoChange(1)
- 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)
- },
- // 左侧面板变化
- showInfoChange(index) {
- this.fullScreen = false
- this.showInfo = index
- },
- // 全屏
- 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) {
- this.showInfoChange(2)
- } 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];
- console.log(element, 'element');
- 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)
- })
- }
- },
- // 聊天记录新增对话
- async newStart_juese(value) {
- console.log(value, 'value');
- this.audioUrl = ""
- 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
- for (let i = 0; i < array.length; i++) {
- const element = array[i];
- let history = JSON.parse(element.history)
- history.map(item => {
- if (item.role == 'assistant') {
- item.voiceFilePosition = element.voiceFilePosition
- }
- })
- this.returnMessage = [...this.returnMessage, ...history]
- }
- })
- },
- 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) {
- 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
- } else if (!this.$route.query.sceneId && !element2.sceneId) {
- chatCharacter = element2
- index = i
- }
- }
- // 如果有聊天记录则点击
- 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 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) {
- let sceneList = this.info.sceneList
- for (let i = 0; i < sceneList.length; i++) {
- const element = sceneList[i];
- if (element.sceneId == this.sceneId) {
- 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) {
- 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() {
- this.textCache = ''
- this.resError = false
- this.messageLoading = true
- if (!this.content) {
- return
- }
- // 初始化定时器和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()
- }
- this.returnMessage.push({
- role: 'user',
- content: this.content
- })
- let HistoryMessage = JSON.parse(JSON.stringify(this.returnMessage))
- HistoryMessage = HistoryMessage.slice(0, -1)
- console.log(HistoryMessage, 'HistoryMessage');
- // 历史记录取最新的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 = ''
- 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)
- 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');
- // 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) {
- let index = this.historyRes.length * 2
- this.initAudio(index)
- }
- console.log('结束');
- break;
- }
- //txt就是一个一个的字 然后添加到页面上就可以了
- const txt = decoder.decode(value).split('data:')
- // const txt = decoder.decode(value)
- 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(value) {
- // console.log(value, 'value');
- for (let i = 0; i < value.length; i++) {
- const element = value[i];
- if (this.isJSON(element)) {
- if(JSON.parse(element).code == 401) {
- this.returnMessage.splice(this.returnMessage.length - 1, 1)
- this.noLogin(element)
- break;
- }
- if(JSON.parse(element).code == 500) {
- console.log(JSON.parse(element), 'JSON.parse(element)');
- this.returnMessage.splice(this.returnMessage.length - 2, 2)
- this.$message.error(JSON.parse(element).content || '系统错误请联系管理员')
- this.resError = true
- break;
- }
- let txt = JSON.parse(element).content
- this.returnMessage[this.returnMessage.length - 1].content += txt
- }
- }
- },
- 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
- let history = this.historyRes[ index / 2 - 1]
- console.log(history, 'history');
- this.loopGetVoice(history.id, index)
- },
- playAudio(url, index) {
- this.audioUrl = url
- this.audioPlayIndex = index
- this.$refs.audio.volume = 1
- setTimeout(() => {
- if (this.audioUrl) {
- this.$refs.audio.play()
- }
- }, 1000)
- },
- async loopGetVoice(id, index) {
- // 获取语音文件
- let params = {
- id: id
- }
- let fileRes = await getVoiceFileApi(params)
- console.log(fileRes, 'file>>>');
- if (fileRes.data) {
- console.log(this.returnMessage[index], 'sssssssssss');
- this.returnMessage[index].voiceFilePosition = fileRes.data.voiceFilePosition
- // 有语音文件则直接播放
- if (this.setting.value1) {
- this.playAudio(fileRes.data.voiceFilePosition, index)
- }
- } else {
- // 没有语音文件则开启定时器每两秒查询一次
- this.videoLoopTime = 0
- this.videoLoop = setInterval(() => {
- console.log(this.videoLoopTime, '定时器');
- // 如果计时器超过20秒则关闭不再获取语音文件
- if (this.videoLoopTime >= 20) {
- clearInterval(this.videoLoop)
- this.videoLoop = null
- }
- this.videoLoopTime += 2
- getVoiceFileApi(params).then(res => {
- // 如果获取到语音文件则关闭计时器并播放语音
- if (res.data) {
- clearInterval(this.videoLoop)
- this.videoLoop = null
- this.returnMessage[index].voiceFilePosition = res.data.voiceFilePosition
- if (this.setting.value1) {
- this.playAudio(res.data.voiceFilePosition, index)
- }
- }
- })
- }, 2000)
- }
- },
- audioEnd() {
- // console.log('播放结束');
- this.audioPlayIndex = null
- },
- // 场景业务相关↓
- 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>
- .fullScreenButton {
- position: absolute;
- top: 50%;
- left: 0;
- transform: translateX(-50%) translateY(-50%);
- z-index: 50;
- background: #fff;
- display: flex;
- justify-content: center;
- align-items: center;
- width: 1.6rem;
- height: 1.6rem;
- cursor: pointer;
- }
- .chat {
- height: 100vh;
- }
- .leftInfo {
- width: 375px;
- height: 100%;
- padding: 32px 24px;
- overflow-y: auto;
- >.history {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- >.historyBox {
- padding: 8px;
- border-radius: 15px;
- border: solid 1px var(--color1);
- margin-bottom: 10px;
- .top {
- >.photo {
- >img {
- width: 48px;
- height: 48px;
- border-radius: 50%;
- }
- }
- >.info {
- width: calc(100% - 60px - 16px);
- >div {
- overflow: hidden; /* 确保超出容器的文本被裁剪 */
- white-space: nowrap; /* 确保文本在一行内显示 */
- text-overflow: ellipsis; /* 使用省略号表示文本超出 */
- }
- }
- }
- }
- }
- >.chatInfo {
- overflow: hidden;
- .chatInfo_photo {
- >img {
- }
- }
- .hot {
- color: #f03d3d;
- background: #fff1f1;
- }
- .tags {
- margin-top: 10px;
- .tag {
- display: inline-flex;
- justify-content: center;
- padding: 5px 10px;
- border-radius: 4px;
- border: solid 1px var(--color1);
- margin-right: 5px;
- }
- }
- }
- }
- .content {
- z-index: 40;
- position: absolute;
- right: 0;
- background-repeat: no-repeat;
- background-attachment: fixed;
- background-size: cover;
- background-color: #fff;
- display: flex;
- height: 100%;
- justify-content: center;
- width: calc(100% - 375px);
- border-left: solid 1px var(--color1);
- transition: width 0.5s ease;
- >.chat-box {
- position: relative;
- 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%);
- background-size: auto;
- // border: 1px solid #635677 !important;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- padding: 55px 25px 170px 25px;
- >.chatBoxTitle {
- color: #fff;
- margin-top: 15px;
- width: calc(100% - 50px);
- height: 30px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- }
- }
- .contentFull {
- width: 100%;
- }
- .historyActive {
- background: var(--color1);
- }
- .dialogue:hover {
- background: var(--color1);
- .dialogueIconBg {
- display: flex;
- }
- }
- .dialogueIconBg {
- display: none;
- background: linear-gradient(90deg, rgba(0, 0, 0, 0) 10%, var(--color1) 50%, var(--color1) 100%);
- }
- .messages {
- height: calc(100% - 68px );
- overflow-y: auto;
- }
- .messages::-webkit-scrollbar {
- width: 0px;
- }
- .messageRight {
- max-width: calc(100% - 72px);
- // min-width: 400px;
- }
- .yyPlayBg {
- // background: rgba(103, 103, 168, 1);
- // border-radius: 999px;
- // position: absolute;
- .icon {
- height: 24px;
- width: 96px;
- }
- }
- .yyPlay {
- width: 15px;
- height: 15px;
- // background: url('../../assets/images/播放/yyPlay.gif') no-repeat;
- // background-size: 170% 170%;
- // background-position: 50% 50%;
- // animation: heart-burst steps(28) 0.9s infinite both;
- // animation-play-state: paused;
- // height: 50px;
- // background: #fff;
- }
- .message {
- // background: #ffffff0f;
- // border: 1px solid #5b5b5e;
- background: var(--bg-color3);
- color: #fff;
- font-size: 14px;
- // flex: 1;
- }
- .messageUser {
- background: rgba(255, 255, 255, 0.9);
- border: 1px solid var(--color1);
- // max-width: calc(100% - 72px);
- font-size: 14px;
- width: auto;
- // min-width: 400px;
- }
- .photo {
- width: 56px;
- }
- .me {
- flex-direction: row-reverse;
- }
- .loadingMessage {
- position: relative;
- // display: flex;
- // justify-content: center;
- width: 120px;
- height: 64px;
- }
- .messageOptions {
- // margin-top: 20px;
- display: flex;
- flex-wrap: wrap;
- justify-content: space-around;
- min-width: 400px;
- .option {
- background: #ade4ff32;
- // border: 1px solid #5b5b5e;
- border-radius: 10px;
- padding: 10px;
- width: 40%;
- height: 50px;
- margin-top: 10px;
- cursor: pointer;
- }
- }
- .inputBox {
- border: solid 1px var(--color1);
- border-radius: 10px;
- padding: 24px;
- background: #fff;
- max-width: 900px;
- margin: 0 auto;
- }
- .planeColor {
- color: var(--bg-color1);
- }
- </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;
- }
- .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;
- border-radius: 4px;
- }
- .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>
|