|
|
|
@ -0,0 +1,317 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="argument-component">
|
|
|
|
|
<view class="content-card">
|
|
|
|
|
<view class="card-title">辩论功能</view>
|
|
|
|
|
<view class="card-content">在这里可以和AI进行激烈的交流~</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 聊天展示区 -->
|
|
|
|
|
<scroll-view
|
|
|
|
|
class="chat-area"
|
|
|
|
|
scroll-y
|
|
|
|
|
:scroll-into-view="scrollToView"
|
|
|
|
|
scroll-with-animation
|
|
|
|
|
>
|
|
|
|
|
<view
|
|
|
|
|
v-for="(msg, index) in messages"
|
|
|
|
|
:key="index"
|
|
|
|
|
:id="'msg' + index"
|
|
|
|
|
class="chat-message"
|
|
|
|
|
:class="msg.role === 'user' ? 'from-user' : 'from-ai'"
|
|
|
|
|
>
|
|
|
|
|
<view class="bubble">
|
|
|
|
|
<block v-if="msg.loading">
|
|
|
|
|
<view class="dot-flashing"></view>
|
|
|
|
|
</block>
|
|
|
|
|
<block v-else>
|
|
|
|
|
{{ msg.content }}
|
|
|
|
|
</block>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
<!-- 输入框与发送按钮 -->
|
|
|
|
|
<view class="chat-input">
|
|
|
|
|
<view class="input-group">
|
|
|
|
|
<textarea
|
|
|
|
|
class="textarea-box"
|
|
|
|
|
v-model="content"
|
|
|
|
|
placeholder="请输入辩论的过程"
|
|
|
|
|
auto-height
|
|
|
|
|
maxlength="1000"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
<button class="send-button" @click="sendMessage()">发送</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
export default {
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
input: "",
|
|
|
|
|
messages: [
|
|
|
|
|
{
|
|
|
|
|
role: "ai",
|
|
|
|
|
content: "在这里你可以和我进行辩论,现在,辩论开始!"
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
scrollToView: "",
|
|
|
|
|
content: "",
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async sendMessage() {
|
|
|
|
|
// 添加用户消息
|
|
|
|
|
this.messages.push({
|
|
|
|
|
role: "user",
|
|
|
|
|
content: this.content,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
|
|
|
|
// 添加 AI loading 消息(占位)
|
|
|
|
|
const aiIndex = this.messages.length;
|
|
|
|
|
this.messages.push({ role: "ai", content: "", loading: true });
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
|
|
|
|
// 构建历史记录
|
|
|
|
|
let history = this.messages
|
|
|
|
|
.slice(0, aiIndex) // 不包括刚插入的 loading 消息
|
|
|
|
|
.filter((msg) => !msg.loading)
|
|
|
|
|
.map((msg) => {
|
|
|
|
|
if (msg.role === "user") {
|
|
|
|
|
return `用户:${msg.content}`;
|
|
|
|
|
} else if (msg.role === "ai") {
|
|
|
|
|
return `AI:${msg.content}`;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
})
|
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
|
|
|
// 调用 AI 接口
|
|
|
|
|
const reply = await this.callAI(history, this.content);
|
|
|
|
|
|
|
|
|
|
// 替换 loading 消息为真实 AI 回答
|
|
|
|
|
this.messages.splice(aiIndex, 1, { role: "ai", content: reply });
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
},
|
|
|
|
|
async callAI(history, content) {
|
|
|
|
|
this.content = "";
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
console.log("callAI:", history, content);
|
|
|
|
|
|
|
|
|
|
uni.request({
|
|
|
|
|
url: "http://localhost:8080/api/ai/debate",
|
|
|
|
|
method: "POST",
|
|
|
|
|
header: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
history: history,
|
|
|
|
|
content: content,
|
|
|
|
|
},
|
|
|
|
|
success: (res) => {
|
|
|
|
|
console.log("res:", res);
|
|
|
|
|
let reviewJson = JSON.parse(res.data.data.debate);
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
if (res.statusCode === 200 && res.data.code === 200) {
|
|
|
|
|
resolve(
|
|
|
|
|
reviewJson.choices[0].message.content || "AI 没有返回有效内容"
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
reject("请求失败:" + (res.data.msg || "未知错误"));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
reject("请求失败:" + err.errMsg);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
scrollToBottom() {
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.scrollToView = "msg" + (this.messages.length - 1);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.argument-component {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 78vh;
|
|
|
|
|
background: linear-gradient(135deg, #2c3e50, #c6c333);
|
|
|
|
|
|
|
|
|
|
overflow-x: hidden; /* 禁止横向滚动 */
|
|
|
|
|
box-sizing: border-box; /* 避免 padding 导致溢出 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-card {
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
width: 90%;
|
|
|
|
|
margin: 0 auto 30rpx;
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-title {
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-content {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 聊天区域 */
|
|
|
|
|
.chat-area {
|
|
|
|
|
flex: 1;
|
|
|
|
|
width: 90%;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 消息通用样式 */
|
|
|
|
|
.chat-message {
|
|
|
|
|
display: flex;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 用户消息靠右 */
|
|
|
|
|
.from-user {
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* AI消息靠左 */
|
|
|
|
|
.from-ai {
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 气泡样式 */
|
|
|
|
|
.bubble {
|
|
|
|
|
max-width: 70%;
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 用户气泡颜色 */
|
|
|
|
|
.from-user .bubble {
|
|
|
|
|
background-color: #00c4ff;
|
|
|
|
|
color: white;
|
|
|
|
|
border-bottom-right-radius: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* AI气泡颜色 */
|
|
|
|
|
.from-ai .bubble {
|
|
|
|
|
background-color: #ffd54f;
|
|
|
|
|
color: #333;
|
|
|
|
|
border-bottom-left-radius: 0;
|
|
|
|
|
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 输入区域 */
|
|
|
|
|
.chat-input {
|
|
|
|
|
display: flex;
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
|
|
|
border-top: 1rpx solid #ccc;
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.input-group {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.input-box {
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.textarea-box {
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
min-height: 80rpx;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.send-button {
|
|
|
|
|
margin-left: 20rpx;
|
|
|
|
|
background-color: #00bcd4;
|
|
|
|
|
color: #fff;
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* AI loading 动画 */
|
|
|
|
|
.dot-flashing {
|
|
|
|
|
width: 20rpx;
|
|
|
|
|
height: 20rpx;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background-color: #333;
|
|
|
|
|
animation: dotFlashing 1s infinite linear alternate;
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
margin-right: auto;
|
|
|
|
|
}
|
|
|
|
|
.dot-flashing::before,
|
|
|
|
|
.dot-flashing::after {
|
|
|
|
|
content: "";
|
|
|
|
|
display: inline-block;
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
width: 20rpx;
|
|
|
|
|
height: 20rpx;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background-color: #333;
|
|
|
|
|
}
|
|
|
|
|
.dot-flashing::before {
|
|
|
|
|
left: -30rpx;
|
|
|
|
|
animation: dotFlashing 1s infinite linear alternate;
|
|
|
|
|
animation-delay: 0.2s;
|
|
|
|
|
}
|
|
|
|
|
.dot-flashing::after {
|
|
|
|
|
left: 30rpx;
|
|
|
|
|
animation: dotFlashing 1s infinite linear alternate;
|
|
|
|
|
animation-delay: 0.4s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes dotFlashing {
|
|
|
|
|
0% {
|
|
|
|
|
background-color: #333;
|
|
|
|
|
}
|
|
|
|
|
50%,
|
|
|
|
|
100% {
|
|
|
|
|
background-color: rgba(51, 51, 51, 0.3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|