fix: 优化对话

This commit is contained in:
Shu Guang 2025-05-17 17:09:25 +08:00
parent d0c70c62b3
commit 12a401ec8d
8 changed files with 2477 additions and 99 deletions

1160
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

1161
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
},
"dependencies": {
"@ant-design-vue/pro-layout": "^1.0.8",
"@kangc/v-md-editor": "^1.7.12",
"@tinymce/tinymce-vue": "^3.2.6",
"animate.css": "^4.1.1",
"ant-design-vue": "^1.7.4",

View File

@ -334,6 +334,24 @@ export function AnyncChart(data) {
}
});
}
// 搜索知识库
export function searchKnowledgeBase(query) {
return request({
url: '/api/knowledge/search',
method: 'get',
params: { query }
});
}
// 获取AI响应
export function getAIResponse(data) {
return request({
url: '/api/assistant/chat',
method: 'post',
data
});
}
// 获取所有教师
export const getTeacherCount = params => request.get('/user/getTeacherCount', { params });
// 获取所有学生

View File

@ -204,6 +204,7 @@ export default {
margin: 24px;
padding: 24px;
background: #fff;
height: 100vh;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
min-height: calc(100vh - 128px);

View File

@ -10,6 +10,14 @@ import './permission'; // permission control
import '@/plugins';
import '@/tool';
import VueAnimateNumber from 'vue-animate-number'
import VMdEditor from '@kangc/v-md-editor';
import VMdPreview from '@kangc/v-md-editor/lib/preview';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import '@kangc/v-md-editor/lib/theme/style/github.css';
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js';
VMdEditor.use(githubTheme);
VMdPreview.use(githubTheme);
Vue.use(VueAnimateNumber)
Vue.config.productionTip = false;

View File

@ -132,9 +132,15 @@ export const routes = [
{
path: '/account/Agent',
name: 'AccountAgent',
meta: { title: '竞赛助手', auth: 0 },
meta: { title: '编程助手', auth: 0 },
component: () => import('@/views/user/Agent.vue'),
},
{
path: '/account/CompetitionAssistant',
name: 'CompetitionAssistant',
meta: { title: '竞赛助手', auth: 0 },
component: () => import('@/views/CompetitionAssistant.vue'),
},
],
},
{

View File

@ -0,0 +1,219 @@
<template>
<div class="chat-container">
<!-- 聊天区域 -->
<div class="chat-main">
<!-- 聊天记录 -->
<div class="chat-messages" ref="messageContainer">
<div
v-for="(message, index) in chatHistory"
:key="index"
:class="['message', message.role]"
>
<div class="message-avatar">
<a-avatar :src="message.role === 'user' ? userAvatar : aiAvatar" />
</div>
<div class="message-content">
<v-md-preview :text="message.content" />
<div class="message-time">{{ message.time }}</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="chat-input-area">
<v-md-editor v-model="inputContent" height="150px" @save="handleSend" />
<div class="input-actions">
<a-button type="primary" :loading="loading" @click="handleSend"
>发送</a-button
>
<a-button @click="clearChat">清空对话</a-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, nextTick } from "vue";
import VMdEditor from "@kangc/v-md-editor";
import VMdPreview from "@kangc/v-md-editor/lib/preview";
import "@kangc/v-md-editor/lib/style/base-editor.css";
import "@kangc/v-md-editor/lib/theme/style/github.css";
import githubTheme from "@kangc/v-md-editor/lib/theme/github.js";
import { searchKnowledgeBase, getAIResponse } from "@/api";
VMdEditor.use(githubTheme);
VMdPreview.use(githubTheme);
export default {
name: "CompetitionAssistant",
components: {
VMdEditor,
VMdPreview,
},
setup() {
const inputContent = ref("");
const loading = ref(false);
const chatHistory = ref([]);
const messageContainer = ref(null);
const searchQuery = ref("");
const knowledgeList = ref([]);
const userAvatar = "path/to/user-avatar.png";
const aiAvatar = "path/to/ai-avatar.png";
//
const searchKnowledge = async (query) => {
try {
const result = await searchKnowledgeBase(query);
knowledgeList.value = result.data;
} catch (error) {
console.error("搜索知识库失败:", error);
}
};
//
const selectKnowledge = (item) => {
inputContent.value = `参考知识:${item.title}\n\n${inputContent.value}`;
};
//
const handleSend = async () => {
if (!inputContent.value.trim()) return;
const userMessage = {
role: "user",
content: inputContent.value,
time: new Date().toLocaleTimeString(),
};
chatHistory.value.push(userMessage);
loading.value = true;
try {
const response = await getAIResponse({
message: inputContent.value,
history: chatHistory.value,
});
chatHistory.value.push({
role: "assistant",
content: response.data,
time: new Date().toLocaleTimeString(),
});
inputContent.value = "";
scrollToBottom();
} catch (error) {
console.error("获取AI响应失败:", error);
} finally {
loading.value = false;
}
};
//
const scrollToBottom = async () => {
await nextTick();
if (messageContainer.value) {
messageContainer.value.scrollTop = messageContainer.value.scrollHeight;
}
};
//
const clearChat = () => {
chatHistory.value = [];
};
onMounted(() => {
searchKnowledge("");
});
return {
inputContent,
loading,
chatHistory,
messageContainer,
searchQuery,
knowledgeList,
userAvatar,
aiAvatar,
handleSend,
clearChat,
searchKnowledge,
selectKnowledge,
};
},
};
</script>
<style scoped>
.chat-container {
display: flex;
height: calc(100vh - 64px);
background: #fff;
}
.knowledge-panel {
width: 300px;
border-right: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 16px;
border-bottom: 1px solid #e8e8e8;
}
.knowledge-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 24px;
}
.message {
display: flex;
margin-bottom: 24px;
gap: 12px;
}
.message.assistant {
background: #f7f7f8;
border-radius: 8px;
padding: 16px;
}
.message-content {
flex: 1;
}
.message-time {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.chat-input-area {
border-top: 1px solid #e8e8e8;
padding: 16px;
}
.input-actions {
margin-top: 12px;
display: flex;
gap: 8px;
justify-content: flex-end;
}
</style>