|
|
@@ -2,83 +2,235 @@
|
|
|
<view class="container">
|
|
|
<scroll-view class="chat-messages" scroll-y="true" :scroll-with-animation="true"
|
|
|
:scroll-into-view="scrollToView"
|
|
|
- :style="{ width: $scrollViewWidth + 'px', padding: $viewPadding + 'px',maxHeight: $viewMaxHeight + 'px'}">
|
|
|
+ :style="{ width: $scrollViewWidth + 'px', padding: $viewPadding + 'px', maxHeight: $viewMaxHeight + 'px' }">
|
|
|
<!-- 循环渲染消息 -->
|
|
|
<view v-for="(message, index) in messages" :key="index" :id="'msg' + index" style="clear: both;">
|
|
|
<view :class="['message', message.sender === 'user' ? 'user-message' : 'ai-message']">
|
|
|
- <text>{{ message.content }}</text>
|
|
|
+ <text :user-select="true" :selectable="true">{{ message.content }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
<view class="chat-input">
|
|
|
<textarea @linechange="chatMsgMaxHeightChange" auto-height v-model="inputMessage" placeholder="输入消息..."
|
|
|
class="input-field" />
|
|
|
- <button class="send-button" @click="sendMessage">发送</button>
|
|
|
+ <button class="send-button" :disabled="!button_state" @click="sendMessage">发送</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
+ <view class="fab_button">
|
|
|
+ <view class="history_button">
|
|
|
+ <uni-fab :pattern="{ icon: 'list' }" :popMenu="false" horizontal="right" vertical="bottom"
|
|
|
+ @fabClick="showHistorySession()"></uni-fab>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="drawer_container">
|
|
|
+ <uni-drawer ref="showLeftDrawer" :width="220">
|
|
|
+ <view>
|
|
|
+ <scroll-view scroll-y="true" :style="{height: '100vh'}">
|
|
|
+ <uni-list>
|
|
|
+ <uni-list-item title="新建会话" :clickable="true" @click="createSession()">
|
|
|
+ <template v-slot:footer>
|
|
|
+ <uni-icons type="plus-filled" size="24"></uni-icons>
|
|
|
+ </template>
|
|
|
+ </uni-list-item>
|
|
|
+ <view v-for="(session,index) in sessionList" :key="index">
|
|
|
+ <uni-list-item :title="session.sessionName" :clickable="true"
|
|
|
+ @click="changeSession(session.sessionId)" :ellipsis="2">
|
|
|
+ <template v-slot:footer>
|
|
|
+ <uni-icons type="compose" size="24"
|
|
|
+ @click.stop.prevent="toEditSession(session)"></uni-icons>
|
|
|
+ </template>
|
|
|
+ </uni-list-item>
|
|
|
+ </view>
|
|
|
+ </uni-list>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </uni-drawer>
|
|
|
+ </view>
|
|
|
+ <view class="edit_popup">
|
|
|
+ <uni-popup ref="editPopup" type="center" :mask-click="true">
|
|
|
+ <uni-card title="编辑会话信息">
|
|
|
+ <uni-forms ref="editForm" :modelValue="editSession" :rules="rules">
|
|
|
+ <uni-forms-item label="会话名" name="sessionName">
|
|
|
+ <uni-easyinput type="text" v-model="editSession.sessionName" placeholder="请输入会话名" />
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item>
|
|
|
+ <button type="primary" :disabled="!editButtonState" @click="submitEditSession()">确认</button>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item>
|
|
|
+ <button type="warn" :disabled="!editButtonState" @click="deleteSession()">删除</button>
|
|
|
+ </uni-forms-item>
|
|
|
+ </uni-forms>
|
|
|
+ </uni-card>
|
|
|
+ </uni-popup>
|
|
|
+ </view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
- import {
|
|
|
- ref,
|
|
|
- nextTick,
|
|
|
- onMounted
|
|
|
- } from 'vue';
|
|
|
+import {
|
|
|
+ ref,
|
|
|
+ nextTick,
|
|
|
+ onMounted,
|
|
|
+ reactive
|
|
|
+} from 'vue';
|
|
|
import $modal from '@/plugins/modal.js'
|
|
|
- const inputMessage = ref('');
|
|
|
- const messages = ref([{
|
|
|
- sender: 'ai',
|
|
|
- content: '您好,我是ai助手'
|
|
|
- },
|
|
|
+import { useUserStore } from '@/store/user.js'
|
|
|
+import { sendMessageToAI, getSessionList, getMessageList, setSessionName, delSession } from '@/api/AI.js'
|
|
|
|
|
|
+onMounted(() => {
|
|
|
+ // 在组件挂载后获取窗口高度
|
|
|
+ const res = uni.getWindowInfo();
|
|
|
+ // 计算滚动视图宽度
|
|
|
+ $scrollViewWidth.value = res.windowWidth - $viewPadding.value * 2;
|
|
|
+ $windowHeight.value = res.windowHeight;
|
|
|
+ $viewMaxHeight.value = res.windowHeight - 100;
|
|
|
+ initHistorySession()
|
|
|
+});
|
|
|
+// 弹出会话列表侧边栏
|
|
|
+const showLeftDrawer = ref(null)
|
|
|
+function showHistorySession() {
|
|
|
+ showLeftDrawer.value.open()
|
|
|
+}
|
|
|
+// 初始化会话列表
|
|
|
+const userStore = useUserStore()
|
|
|
+const sessionList = reactive([])
|
|
|
+function initHistorySession() {
|
|
|
+ sessionList.length = 0
|
|
|
+ getSessionList(userStore.user.useId).then(({ returnParams }) => {
|
|
|
+ sessionList.push(...returnParams)
|
|
|
+ })
|
|
|
+}
|
|
|
+// 新建会话
|
|
|
+function createSession() {
|
|
|
+ messages.length = 0
|
|
|
+ thisSessionId.value = 0
|
|
|
+ showLeftDrawer.value.close()
|
|
|
+}
|
|
|
+// 切换会话
|
|
|
+function changeSession(sessionId) {
|
|
|
+ thisSessionId.value = sessionId
|
|
|
+ initHistoryMessage(sessionId)
|
|
|
+}
|
|
|
+// 修改会话信息
|
|
|
+const editSession = ref({})
|
|
|
+const editPopup = ref(null)
|
|
|
+const editButtonState = ref(true)
|
|
|
+function toEditSession(session) {
|
|
|
+ showLeftDrawer.value.close()
|
|
|
+ editSession.value = session
|
|
|
+ editPopup.value.open()
|
|
|
+}
|
|
|
+function submitEditSession() {
|
|
|
+ editButtonState.value = false
|
|
|
+ setSessionName(editSession.value).then(res => {
|
|
|
+ initHistorySession()
|
|
|
+ editPopup.value.close()
|
|
|
+ editButtonState.value = true
|
|
|
+ })
|
|
|
+}
|
|
|
+function deleteSession() {
|
|
|
+ editButtonState.value = false
|
|
|
+ $modal.confirm("确认删除").then(res => {
|
|
|
+ delSession(editSession.value.sessionId).then(res => {
|
|
|
+ initHistorySession()
|
|
|
+ createSession()
|
|
|
+ editPopup.value.close()
|
|
|
+ editButtonState.value = true
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .catch(res => {
|
|
|
+ editButtonState.value = true
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const messages = reactive([{
|
|
|
+ sender: 'assistant',
|
|
|
+ content: '您好,我是ai助手'
|
|
|
+ }
|
|
|
]);
|
|
|
+function initHistoryMessage(sessionId) {
|
|
|
+ getMessageList(sessionId).then(res => {
|
|
|
+ const returnParams = res.returnParams
|
|
|
+ messages.length = 0
|
|
|
+ for (let i = 0; i < returnParams.length; i++) {
|
|
|
+ const item = returnParams[i]
|
|
|
+ messages.push({
|
|
|
+ sender: 'user',
|
|
|
+ content: item.sendContent
|
|
|
+ })
|
|
|
+ messages.push({
|
|
|
+ sender: 'assistant',
|
|
|
+ content: item.receiveContent
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+ const thisSessionId = ref(0);
|
|
|
+ const inputMessage = ref('');
|
|
|
const scrollToView = ref('');// 滚动到特定消息的标识
|
|
|
+ const button_state = ref(true)
|
|
|
|
|
|
// 发送消息函数
|
|
|
function sendMessage() {
|
|
|
+ button_state.value = false
|
|
|
if (inputMessage.value.trim() === '') {
|
|
|
+ button_state.value = true
|
|
|
return;
|
|
|
}
|
|
|
- messages.value.push({
|
|
|
+ messages.push({
|
|
|
sender: 'user',
|
|
|
content: inputMessage.value
|
|
|
});
|
|
|
- inputMessage.value = ''; // 清空输入框
|
|
|
- scrollToBottom(); // 滚动到底部
|
|
|
- getAIResponse(); // 获取AI的回复
|
|
|
- };
|
|
|
-
|
|
|
- // 获取AI回复的函数
|
|
|
- function getAIResponse() {
|
|
|
- // 这里调用AI客服接口,获取回复
|
|
|
- uni.request({
|
|
|
- url: 'http://api.qingyunke.com/api.php?key=free&appid=0&msg=' + messages.value[messages.value.length -
|
|
|
- 1].content,
|
|
|
- success: (res) => {
|
|
|
- messages.value.push({
|
|
|
- sender: 'ai',
|
|
|
- content: res.data.content
|
|
|
- });
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.log('err', err);
|
|
|
- $modal.alert('err'+ JSON.stringify(err))
|
|
|
- // $modal.msg(err)
|
|
|
- messages.value.push({
|
|
|
- sender: 'ai',
|
|
|
- content: '小客服不在线'
|
|
|
- });
|
|
|
- },
|
|
|
- complete: () => {
|
|
|
- scrollToBottom();
|
|
|
+
|
|
|
+ const message = {
|
|
|
+ sessionId: thisSessionId.value,
|
|
|
+ prompt: inputMessage.value,
|
|
|
+ userId: userStore.user.useId,
|
|
|
+ sort: messages.filter(item => item.sender == 'user').length
|
|
|
+ }
|
|
|
+ sendMessageToAI(message).then((res) => {
|
|
|
+ const returnParams = res.returnParams
|
|
|
+ if (thisSessionId.value == 0) { // 添加新会话
|
|
|
+ thisSessionId.value = returnParams.sessionId // 新会话ID
|
|
|
+ sessionList.unshift({
|
|
|
+ sessionName: returnParams.sessionName,
|
|
|
+ sessionId: returnParams.sessionId
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ initHistorySession()
|
|
|
}
|
|
|
+ inputMessage.value = ''; // 清空输入框
|
|
|
+ if (thisSessionId.value != returnParams.sessionId) {
|
|
|
+ button_state.value = true;
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let content = ""
|
|
|
+ const index = messages.length // 新增消息索引
|
|
|
+ messages.push({ //
|
|
|
+ sender: 'assistant',
|
|
|
+ content: content
|
|
|
+ });
|
|
|
+ scrollToBottom(); // 滚动到底部
|
|
|
+ let i = 0
|
|
|
+ const charLength = 3 // 每次显示字数
|
|
|
+ const messageLength = returnParams.receiveContent.length
|
|
|
+ const interval = setInterval(() => { //模拟流式输出
|
|
|
+ if (i < messageLength) {
|
|
|
+ let sliceLength = i + charLength > messageLength?messageLength:i + charLength // 当前显示字数
|
|
|
+ messages[index].content += returnParams.receiveContent.slice(i, sliceLength); // 每次输出 3 个字符
|
|
|
+ i += charLength;
|
|
|
+ } else {
|
|
|
+ clearInterval(interval);
|
|
|
+ button_state.value = true;
|
|
|
+ }
|
|
|
+ }, 50); // 50ms 一次
|
|
|
})
|
|
|
};
|
|
|
|
|
|
// 滚动到底部的函数
|
|
|
function scrollToBottom() {
|
|
|
nextTick(() => {
|
|
|
- scrollToView.value = 'msg' + (messages.value.length - 1); // 更新滚动到的消息
|
|
|
+ scrollToView.value = 'msg' + (messages.length - 1); // 更新滚动到的消息
|
|
|
});
|
|
|
};
|
|
|
|
|
|
@@ -87,84 +239,83 @@ import $modal from '@/plugins/modal.js'
|
|
|
const $viewPadding = ref(10); // 滚动视图内边距
|
|
|
const $windowHeight = ref(0); // 窗口高度
|
|
|
|
|
|
- // 使用 onMounted 钩子在组件挂载后获取窗口高度
|
|
|
- onMounted(() => {
|
|
|
- const res = uni.getWindowInfo();
|
|
|
- // 计算滚动视图宽度
|
|
|
- $scrollViewWidth.value = res.windowWidth - $viewPadding.value * 2;
|
|
|
- $windowHeight.value = res.windowHeight;
|
|
|
- $viewMaxHeight.value = res.windowHeight - 100;
|
|
|
- });
|
|
|
-
|
|
|
// 输入框行数变化时更新最大高度
|
|
|
function chatMsgMaxHeightChange(e) {
|
|
|
- console.log($windowHeight,e.detail.height);
|
|
|
+ // console.log($windowHeight,e.detail.height);
|
|
|
$viewMaxHeight.value = $windowHeight.value - e.detail.height - 70;
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
|
|
|
-<style scoped>
|
|
|
- .container {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- height: 100vh;
|
|
|
- position: relative;
|
|
|
+<style lang="scss" scoped>
|
|
|
+.container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100vh;
|
|
|
+ position: relative;
|
|
|
|
|
|
- }
|
|
|
+}
|
|
|
|
|
|
- .chat-messages {
|
|
|
- flex: 1;
|
|
|
- background-color: #f3f3f3;
|
|
|
- overflow-y: auto;
|
|
|
-
|
|
|
- }
|
|
|
+.chat-messages {
|
|
|
+ flex: 1;
|
|
|
+ background-color: #f3f3f3;
|
|
|
+ overflow-y: auto;
|
|
|
|
|
|
- .message {
|
|
|
- max-width: 70%;
|
|
|
- margin: 5px 0;
|
|
|
- padding: 8px;
|
|
|
- border-radius: 10px;
|
|
|
- display: inline-block;
|
|
|
- word-wrap: break-word;
|
|
|
- }
|
|
|
+}
|
|
|
|
|
|
- .user-message {
|
|
|
- background-color: #DCF8C6;
|
|
|
- align-self: flex-end;
|
|
|
- float: right;
|
|
|
- }
|
|
|
+.message {
|
|
|
+ max-width: 70%;
|
|
|
+ margin: 5px 0;
|
|
|
+ padding: 8px;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: inline-block;
|
|
|
+ word-wrap: break-word;
|
|
|
+}
|
|
|
|
|
|
- .ai-message {
|
|
|
- background-color: #ECECEC;
|
|
|
- align-self: flex-start;
|
|
|
- }
|
|
|
+.user-message {
|
|
|
+ background-color: #DCF8C6;
|
|
|
+ align-self: flex-end;
|
|
|
+ float: right;
|
|
|
+}
|
|
|
|
|
|
- .chat-input {
|
|
|
- display: flex;
|
|
|
- padding: 10px;
|
|
|
- background-color: #FFFFFF;
|
|
|
- border-top: 1px solid #E0E0E0;
|
|
|
- position: absolute;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- }
|
|
|
+.ai-message {
|
|
|
+ background-color: #ECECEC;
|
|
|
+ align-self: flex-start;
|
|
|
+}
|
|
|
|
|
|
- .input-field {
|
|
|
- flex: 1;
|
|
|
- border: 1px solid #E0E0E0;
|
|
|
- border-radius: 20px;
|
|
|
- padding: 12px;
|
|
|
- margin-right: 10px;
|
|
|
- }
|
|
|
+.chat-input {
|
|
|
+ display: flex;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #FFFFFF;
|
|
|
+ border-top: 1px solid #E0E0E0;
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+}
|
|
|
|
|
|
- .send-button {
|
|
|
- border: none;
|
|
|
- border-radius: 20px;
|
|
|
- padding: 0 20px;
|
|
|
- background-color: #007AFF;
|
|
|
+.input-field {
|
|
|
+ flex: 1;
|
|
|
+ border: 1px solid #E0E0E0;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 12px;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.send-button {
|
|
|
+ border: none;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 0 20px;
|
|
|
+ background-color: #007AFF;
|
|
|
color: white;
|
|
|
height: 46px;
|
|
|
}
|
|
|
+
|
|
|
+ .fab_button {
|
|
|
+ ::v-deep .history_button {
|
|
|
+ .uni-fab__circle {
|
|
|
+ bottom: 100px !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|