|
|
@ -9,32 +9,57 @@
|
|
|
|
<aside class="sidebar">
|
|
|
|
<aside class="sidebar">
|
|
|
|
<h2 class="sidebar-title">历史对话</h2>
|
|
|
|
<h2 class="sidebar-title">历史对话</h2>
|
|
|
|
<ul class="history-list">
|
|
|
|
<ul class="history-list">
|
|
|
|
<li class="history-item" v-for="(item, index) in historyItems" :key="index">
|
|
|
|
<li class="history-item"
|
|
|
|
<span class="history-text">{{ item.text }}</span>
|
|
|
|
v-for="conversation in conversations"
|
|
|
|
|
|
|
|
:key="conversation.id"
|
|
|
|
|
|
|
|
@click="selectConversation(conversation)">
|
|
|
|
|
|
|
|
<span class="history-text">{{ conversation.title }}</span>
|
|
|
|
<div class="history-actions">
|
|
|
|
<div class="history-actions">
|
|
|
|
<!-- 编辑按钮 -->
|
|
|
|
<button class="action-btn" title="编辑" @click.stop="editConversationTitle(conversation)">
|
|
|
|
<button class="action-btn" title="编辑" @click.stop="editItem(item)">
|
|
|
|
|
|
|
|
<img src="@/assets/images/edit.png" alt="编辑" />
|
|
|
|
<img src="@/assets/images/edit.png" alt="编辑" />
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
<!-- 删除按钮 -->
|
|
|
|
<button class="action-btn" title="删除" @click.stop="deleteConversation(conversation)">
|
|
|
|
<button class="action-btn" title="删除" @click.stop="deleteItem(item)">
|
|
|
|
|
|
|
|
<img src="@/assets/images/delete.png" alt="删除" />
|
|
|
|
<img src="@/assets/images/delete.png" alt="删除" />
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
<!-- 新建对话按钮 -->
|
|
|
|
|
|
|
|
<button class="new-chat-btn" @click="createNewChat">
|
|
|
|
|
|
|
|
<span>新建对话</span>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
</aside>
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 聊天区域 -->
|
|
|
|
<!-- 聊天区域 -->
|
|
|
|
<main class="chat-area">
|
|
|
|
<main class="chat-area">
|
|
|
|
<div class="chat-content">
|
|
|
|
<div class="chat-content">
|
|
|
|
|
|
|
|
<!-- 欢迎消息始终显示在最上方 -->
|
|
|
|
<div class="welcome-message">
|
|
|
|
<div class="welcome-message">
|
|
|
|
<!-- 机器人头像 -->
|
|
|
|
|
|
|
|
<div class="bot-avatar">
|
|
|
|
<div class="bot-avatar">
|
|
|
|
<img src="@/assets/images/aiRobot.png" alt="机器人头像" />
|
|
|
|
<img src="@/assets/images/aiRobot.png" alt="机器人头像" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="welcome-text">我可以回答各种问题,欢迎提问</div>
|
|
|
|
<div class="welcome-text">我可以回答各种问题,欢迎提问</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 对话消息 -->
|
|
|
|
|
|
|
|
<template v-if="currentConversation">
|
|
|
|
|
|
|
|
<div v-for="message in currentConversation.messages"
|
|
|
|
|
|
|
|
:key="message.id"
|
|
|
|
|
|
|
|
:class="message.isUser ? 'user-message' : 'bot-message'">
|
|
|
|
|
|
|
|
<template v-if="message.isUser">
|
|
|
|
|
|
|
|
<div class="message-text">{{ message.text }}</div>
|
|
|
|
|
|
|
|
<div class="user-avatar">
|
|
|
|
|
|
|
|
<img src="@/assets/images/aiRobot.png" alt="用户头像" />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
|
|
<div class="bot-avatar">
|
|
|
|
|
|
|
|
<img src="@/assets/images/aiRobot.png" alt="机器人头像" />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="message-text">{{ message.text }}</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 输入区域 -->
|
|
|
|
<!-- 输入区域 -->
|
|
|
@ -59,6 +84,21 @@
|
|
|
|
text: string; // 对话内容
|
|
|
|
text: string; // 对话内容
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 定义消息类型
|
|
|
|
|
|
|
|
interface Message {
|
|
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
|
|
isUser: boolean; // true 表示用户消息,false 表示机器人消息
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 定义对话类型
|
|
|
|
|
|
|
|
interface Conversation {
|
|
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
|
|
title: string; // 对话的标题,可以是第一条用户消息的前20个字符
|
|
|
|
|
|
|
|
messages: Message[]; // 存储对话中的所有消息
|
|
|
|
|
|
|
|
timestamp: number; // 对话创建时间
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 历史对话列表
|
|
|
|
// 历史对话列表
|
|
|
|
const historyItems = ref<HistoryItem[]>([
|
|
|
|
const historyItems = ref<HistoryItem[]>([
|
|
|
|
{ id: 1, text: '这是一个历史对话' },
|
|
|
|
{ id: 1, text: '这是一个历史对话' },
|
|
|
@ -68,30 +108,115 @@ const historyItems = ref<HistoryItem[]>([
|
|
|
|
// 当前输入的消息
|
|
|
|
// 当前输入的消息
|
|
|
|
const message = ref('');
|
|
|
|
const message = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 聊天内容列表
|
|
|
|
|
|
|
|
const chatMessages = ref<HistoryItem[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息
|
|
|
|
// 发送消息
|
|
|
|
const sendMessage = () => {
|
|
|
|
const sendMessage = async () => {
|
|
|
|
const msg = message.value.trim();
|
|
|
|
const msg = message.value.trim();
|
|
|
|
if (msg) {
|
|
|
|
if (!msg) return;
|
|
|
|
const newItem: HistoryItem = {
|
|
|
|
|
|
|
|
id: Date.now(), // 使用时间戳作为唯一标识符
|
|
|
|
// 如果没有当前对话,创建新对话
|
|
|
|
|
|
|
|
if (!currentConversation.value) {
|
|
|
|
|
|
|
|
currentConversation.value = {
|
|
|
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
|
|
|
title: msg.slice(0, 20) + (msg.length > 20 ? '...' : ''),
|
|
|
|
|
|
|
|
messages: [],
|
|
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
conversations.value.push(currentConversation.value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加用户消息
|
|
|
|
|
|
|
|
const userMessage: Message = {
|
|
|
|
|
|
|
|
id: Date.now(),
|
|
|
|
text: msg,
|
|
|
|
text: msg,
|
|
|
|
|
|
|
|
isUser: true
|
|
|
|
};
|
|
|
|
};
|
|
|
|
historyItems.value.push(newItem); // 添加到历史对话列表
|
|
|
|
currentConversation.value.messages.push(userMessage);
|
|
|
|
message.value = ''; // 清空输入框
|
|
|
|
|
|
|
|
|
|
|
|
// 清空输入框
|
|
|
|
|
|
|
|
message.value = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟机器人回复
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// TODO: 这里替换为实际的 AI 接口调用
|
|
|
|
|
|
|
|
const botResponse = await mockBotResponse(msg);
|
|
|
|
|
|
|
|
const botMessage: Message = {
|
|
|
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
|
|
|
text: botResponse,
|
|
|
|
|
|
|
|
isUser: false
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
currentConversation.value.messages.push(botMessage);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('发送消息失败:', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 编辑历史对话
|
|
|
|
// 编辑历史对话
|
|
|
|
const editItem = (item: HistoryItem) => {
|
|
|
|
const editItem = (item: HistoryItem) => {
|
|
|
|
const newText = prompt('编辑对话内容:', item.text);
|
|
|
|
const newText = prompt('编辑对话内容:', item.text);
|
|
|
|
if (newText !== null) {
|
|
|
|
if (newText !== null) {
|
|
|
|
item.text = newText.trim();
|
|
|
|
item.text = newText.trim();
|
|
|
|
|
|
|
|
const chatItem = chatMessages.value.find((chat) => chat.id === item.id);
|
|
|
|
|
|
|
|
if (chatItem) {
|
|
|
|
|
|
|
|
chatItem.text = newText.trim(); // 同步更新聊天内容
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 删除历史对话
|
|
|
|
// 删除历史对话
|
|
|
|
const deleteItem = (item: HistoryItem) => {
|
|
|
|
const deleteItem = (item: HistoryItem) => {
|
|
|
|
historyItems.value = historyItems.value.filter((history) => history.id !== item.id);
|
|
|
|
historyItems.value = historyItems.value.filter((history) => history.id !== item.id);
|
|
|
|
|
|
|
|
chatMessages.value = chatMessages.value.filter((chat) => chat.id !== item.id); // 同步删除聊天内容
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 定义对话列表
|
|
|
|
|
|
|
|
const conversations = ref<Conversation[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 当前选中的对话
|
|
|
|
|
|
|
|
const currentConversation = ref<Conversation | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 选择对话
|
|
|
|
|
|
|
|
const selectConversation = (conversation: Conversation) => {
|
|
|
|
|
|
|
|
currentConversation.value = conversation;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 删除对话
|
|
|
|
|
|
|
|
const deleteConversation = (conversation: Conversation) => {
|
|
|
|
|
|
|
|
conversations.value = conversations.value.filter(c => c.id !== conversation.id);
|
|
|
|
|
|
|
|
if (currentConversation.value?.id === conversation.id) {
|
|
|
|
|
|
|
|
currentConversation.value = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新对话
|
|
|
|
|
|
|
|
const createNewChat = () => {
|
|
|
|
|
|
|
|
const newConversation: Conversation = {
|
|
|
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
|
|
|
title: '新对话',
|
|
|
|
|
|
|
|
messages: [],
|
|
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
conversations.value.push(newConversation);
|
|
|
|
|
|
|
|
currentConversation.value = newConversation;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 编辑对话标题
|
|
|
|
|
|
|
|
const editConversationTitle = (conversation: Conversation) => {
|
|
|
|
|
|
|
|
const newTitle = prompt('编辑对话标题:', conversation.title);
|
|
|
|
|
|
|
|
if (newTitle !== null && newTitle.trim() !== '') {
|
|
|
|
|
|
|
|
conversation.title = newTitle.trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟机器人回复
|
|
|
|
|
|
|
|
const mockBotResponse = async (msg: string): Promise<string> => {
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
resolve(`这是对 "${msg}" 的回复`);
|
|
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
@ -195,6 +320,29 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
color: #9c88ff;
|
|
|
|
color: #9c88ff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.new-chat-btn {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
|
|
background: linear-gradient(45deg, #9c88ff, #ff6b9d);
|
|
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.new-chat-btn:hover {
|
|
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
|
|
box-shadow: 0 4px 15px rgba(156, 136, 255, 0.3);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.new-chat-btn:active {
|
|
|
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 聊天区域 */
|
|
|
|
/* 聊天区域 */
|
|
|
|
.chat-area {
|
|
|
|
.chat-area {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
@ -214,8 +362,7 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
overflow-y: auto;
|
|
|
|
overflow-y: auto;
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: flex-start; /* 垂直方向上靠上 */
|
|
|
|
gap: 16px;
|
|
|
|
align-items: flex-start; /* 水平方向上靠左 */
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.welcome-message {
|
|
|
|
.welcome-message {
|
|
|
@ -226,6 +373,8 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
border-radius: 20px;
|
|
|
|
border-radius: 20px;
|
|
|
|
padding: 16px 24px;
|
|
|
|
padding: 16px 24px;
|
|
|
|
box-shadow: 0 4px 20px rgba(156, 136, 255, 0.1);
|
|
|
|
box-shadow: 0 4px 20px rgba(156, 136, 255, 0.1);
|
|
|
|
|
|
|
|
max-width: 60%;
|
|
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.bot-avatar {
|
|
|
|
.bot-avatar {
|
|
|
@ -238,6 +387,7 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
justify-content: center;
|
|
|
|
justify-content: center;
|
|
|
|
font-size: 18px;
|
|
|
|
font-size: 18px;
|
|
|
|
color: white;
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.bot-avatar img {
|
|
|
|
.bot-avatar img {
|
|
|
@ -251,6 +401,62 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
color: #333;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.user-message {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
|
|
border: 1px solid rgba(200, 180, 255, 0.3);
|
|
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
|
|
|
box-shadow: 0 4px 20px rgba(156, 136, 255, 0.1);
|
|
|
|
|
|
|
|
max-width: 60%;
|
|
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
align-self: flex-end;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.user-text {
|
|
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar img {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.bot-message {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
|
|
border: 1px solid rgba(200, 180, 255, 0.3);
|
|
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
|
|
|
box-shadow: 0 4px 20px rgba(156, 136, 255, 0.1);
|
|
|
|
|
|
|
|
max-width: 60%;
|
|
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
align-self: flex-start;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.message-text {
|
|
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
|
|
margin: 0 16px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 输入区域 */
|
|
|
|
/* 输入区域 */
|
|
|
|
.input-area {
|
|
|
|
.input-area {
|
|
|
|
padding: 30px;
|
|
|
|
padding: 30px;
|
|
|
@ -370,4 +576,3 @@ const deleteItem = (item: HistoryItem) => {
|
|
|
|
background: rgba(156, 136, 255, 0.5);
|
|
|
|
background: rgba(156, 136, 255, 0.5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|