Compare commits
No commits in common. "bc83efc14ee9e922310573e4ac4608225452fc9f" and "3bad5b423d5c18b4a59a7f115337ea002c313743" have entirely different histories.
bc83efc14e
...
3bad5b423d
@ -46,8 +46,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 学术产出 -->
|
||||
<div class="dashboard-panel" style="flex: 1.4;">
|
||||
<h2 style="margin-bottom: 0;">学术产出</h2>
|
||||
<div class="dashboard-panel" style="flex: 1 1 0;">
|
||||
<h2>学术产出</h2>
|
||||
<div class="output-content">
|
||||
<div ref="outputChartRef" class="chart-container-65"></div>
|
||||
<!-- <div class="international-impact">
|
||||
@ -138,18 +138,10 @@
|
||||
<div v-html="dashboardData2?.prompt"></div>
|
||||
</div>
|
||||
<div v-for="(message, index) in chatMessages" :key="index"
|
||||
:class="['message', message.role === 'user' ? 'user-message' : 'assistant-message']" v-show="!message.isHidden">
|
||||
<!-- 修改这里的显示逻辑 -->
|
||||
<div v-if="message.isThinking" class="thinking-message">
|
||||
<span class="loading-dot"></span>
|
||||
<span class="loading-dot"></span>
|
||||
<span class="loading-dot"></span>
|
||||
</div>
|
||||
<div v-else-if="message.isTyping" v-html="message.content"></div>
|
||||
<div v-else v-html="message.content"></div>
|
||||
:class="['message', message.role === 'user' ? 'user-message' : 'assistant-message']">
|
||||
<div v-html="message.content"></div>
|
||||
</div>
|
||||
<div v-if="isLoading && (!chatMessages.length || !chatMessages[chatMessages.length-1].isThinking)"
|
||||
class="assistant-message loading-message">
|
||||
<div v-if="isLoading" class="assistant-message loading-message">
|
||||
<span class="loading-dot"></span>
|
||||
<span class="loading-dot"></span>
|
||||
<span class="loading-dot"></span>
|
||||
@ -202,15 +194,14 @@ const redirectToResearchEvaluation = async () => {
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
// 跳转到目标网站
|
||||
// await localStorage.setItem('access_token', result.data.access);
|
||||
// await localStorage.setItem('refresh_token', result.data.refresh);
|
||||
// await localStorage.setItem('user_info', JSON.stringify({
|
||||
// user_id: result.data.user_id,
|
||||
// username: result.data.username,
|
||||
// permission: '2'
|
||||
// }));
|
||||
window.open(`http://82.156.236.221:10004/user/file?refresh_token=${result.data.refresh}`), '_blank';
|
||||
|
||||
await localStorage.setItem('access_token', result.data.access);
|
||||
await localStorage.setItem('refresh_token', result.data.refresh);
|
||||
await localStorage.setItem('user_info', JSON.stringify({
|
||||
user_id: result.data.user_id,
|
||||
username: result.data.username,
|
||||
permission: '2'
|
||||
}));
|
||||
window.location.href = 'http://82.156.236.221:10004/user/file'
|
||||
} else {
|
||||
console.error('代理登录失败:', result.error);
|
||||
}
|
||||
@ -227,14 +218,14 @@ const redirectToResearchEvaluation2 = async () => {
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
// 跳转到目标网站
|
||||
// await localStorage.setItem('access_token', result.data.access);
|
||||
// await localStorage.setItem('refresh_token', result.data.refresh);
|
||||
// await localStorage.setItem('user_info', JSON.stringify({
|
||||
// user_id: result.data.user_id,
|
||||
// username: result.data.username,
|
||||
// permission: '1'
|
||||
// }))
|
||||
window.open(`http://82.156.236.221:10004/admin/manage?refresh_token=${result.data.refresh}`), '_blank';
|
||||
await localStorage.setItem('access_token', result.data.access);
|
||||
await localStorage.setItem('refresh_token', result.data.refresh);
|
||||
await localStorage.setItem('user_info', JSON.stringify({
|
||||
user_id: result.data.user_id,
|
||||
username: result.data.username,
|
||||
permission: '1'
|
||||
}))
|
||||
window.location.href = 'http://82.156.236.221:10004/admin/manage'
|
||||
} else {
|
||||
console.error('代理登录失败:', result.error);
|
||||
}
|
||||
@ -375,8 +366,6 @@ const chatMessagesRef = ref(null)
|
||||
const userInput = ref('')
|
||||
const isLoading = ref(false)
|
||||
const chatMessages = ref([])
|
||||
let typingInterval = null
|
||||
let thinkingInterval = null
|
||||
|
||||
// DeepSeek API 调用函数
|
||||
const sendMessage = async () => {
|
||||
@ -384,41 +373,35 @@ const sendMessage = async () => {
|
||||
|
||||
// 添加用户消息到对话
|
||||
const userMessage = userInput.value.trim();
|
||||
chatMessages.value.push({
|
||||
role: 'user',
|
||||
content: userMessage,
|
||||
isTyping: false,
|
||||
isThinking: false
|
||||
});
|
||||
chatMessages.value.push({ role: 'user', content: userMessage });
|
||||
userInput.value = '';
|
||||
|
||||
// 立即滚动到底部
|
||||
// await nextTick();
|
||||
// scrollToBottom();
|
||||
// 立即滚动到底部(用户消息)
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
|
||||
// 设置加载状态并添加占位消息
|
||||
isLoading.value = true;
|
||||
chatMessages.value.push({
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
isTyping: false,
|
||||
isThinking: true,
|
||||
isHidden: true // 添加一个标志表示消息暂时隐藏
|
||||
});
|
||||
chatMessages.value.push({ role: 'assistant', content: '思考中...' });
|
||||
const aiMessageIndex = chatMessages.value.length - 1;
|
||||
|
||||
try {
|
||||
// 只保留最近5条消息以优化性能
|
||||
const apiMessages = chatMessages.value
|
||||
.slice(-5)
|
||||
.map(msg => ({ role: msg.role, content: msg.content }));
|
||||
|
||||
// 调用API(启用流式传输)
|
||||
const response = await fetch(`${getChatApiUrl()}/chat/chat`, {
|
||||
const response = await fetch('https://api.deepseek.com/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer sk-06801499dd52426fa7cf3b0670931e3a'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "deepseek-r1:7b",
|
||||
messages: [{ role: "user", content: userMessage }],
|
||||
options: { temperature: 0.6 },
|
||||
keep_thinking: false
|
||||
model: 'deepseek-chat',
|
||||
messages: apiMessages,
|
||||
stream: true // 关键优化:启用流式响应
|
||||
})
|
||||
});
|
||||
|
||||
@ -428,75 +411,37 @@ const sendMessage = async () => {
|
||||
|
||||
// 流式处理数据
|
||||
const reader = response.body.getReader();
|
||||
let fullResponse = '';
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let fullResponse = "";
|
||||
|
||||
// 替换思考中消息为实际响应消息
|
||||
chatMessages.value[aiMessageIndex] = {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
isTyping: false,
|
||||
isThinking: false,
|
||||
isHidden: true // 添加一个标志表示消息暂时隐藏
|
||||
};
|
||||
// 立即滚动到底部
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
// 等待API响应完成
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
|
||||
// 解析流式数据(假设API返回ndjson格式)
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n').filter(line => line.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
if (obj.message?.content) {
|
||||
fullResponse += obj.message.content;
|
||||
const data = JSON.parse(line.replace('data: ', ''));
|
||||
if (data.choices?.[0]?.delta?.content) {
|
||||
fullResponse += data.choices[0].delta.content;
|
||||
// 实时更新UI(逐字显示)
|
||||
chatMessages.value[aiMessageIndex].content = md.render(fullResponse);
|
||||
scrollToBottom();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析JSON失败:', e);
|
||||
console.warn('解析流数据失败:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API响应完成后,设置isLoading为false
|
||||
setTimeout(() => {
|
||||
isLoading.value = false;
|
||||
// 移除隐藏标志,开始打字机效果
|
||||
chatMessages.value[aiMessageIndex].isHidden = false;
|
||||
|
||||
// 然后开始打字机效果
|
||||
let typingIndex = 0;
|
||||
const typingInterval = setInterval(() => {
|
||||
if (typingIndex < fullResponse.length) {
|
||||
chatMessages.value[aiMessageIndex].content =
|
||||
fullResponse.substring(0, typingIndex + 1);
|
||||
typingIndex++;
|
||||
scrollToBottom();
|
||||
} else {
|
||||
clearInterval(typingInterval);
|
||||
}
|
||||
}, 20);
|
||||
}, 100)
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
chatMessages.value[aiMessageIndex].content = '抱歉,回答时遇到问题,请重试。';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
chatMessages.value[aiMessageIndex] = {
|
||||
role: 'assistant',
|
||||
content: '抱歉,回答时遇到问题,请重试。',
|
||||
isTyping: false,
|
||||
isThinking: false,
|
||||
isHidden: false // 确保错误消息显示
|
||||
};
|
||||
scrollToBottom();
|
||||
}
|
||||
};
|
||||
@ -505,7 +450,7 @@ const sendMessage = async () => {
|
||||
const scrollToBottom = () => {
|
||||
nextTick().then(() => {
|
||||
if (chatMessagesRef.value) {
|
||||
chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
|
||||
chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -558,7 +503,7 @@ const dashboardData2 = ref(null);
|
||||
const loading = ref(true);
|
||||
|
||||
// 引入API配置
|
||||
import { getApiBaseUrl, getChatApiUrl } from '../config';
|
||||
import { getApiBaseUrl } from '../config';
|
||||
|
||||
// 从API获取仪表盘数据
|
||||
const fetchDashboardData = async () => {
|
||||
@ -1141,27 +1086,14 @@ const initCharts = () => {
|
||||
'博士': outputLegendStatus.value[2],
|
||||
'硕士': outputLegendStatus.value[1],
|
||||
'学士': outputLegendStatus.value[0],
|
||||
},
|
||||
top: 10 // 调整图例位置
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '12%',
|
||||
bottom: '3%',
|
||||
top: '20%', // 增加顶部间距给图例
|
||||
containLabel: true
|
||||
}
|
||||
},
|
||||
grid: { left: '3%', right: '12%', bottom: '3%', containLabel: true },
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: outputData.value.months,
|
||||
axisLine: { lineStyle: { color: '#fff' } },
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
margin: 15 // 增加y轴标签边距
|
||||
},
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
axisLabel: { color: '#fff' }
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
@ -1173,8 +1105,7 @@ const initCharts = () => {
|
||||
type: 'dashed',
|
||||
color: 'rgba(255, 255, 255, 0.2)'
|
||||
}
|
||||
},
|
||||
interval: 1
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@ -1182,16 +1113,13 @@ const initCharts = () => {
|
||||
type: 'bar',
|
||||
data: outputData.value.doctor,
|
||||
itemStyle: { color: '#4080ff' },
|
||||
barWidth: '25%', // 减小柱宽度
|
||||
barGap: '20%', // 增加柱子间距
|
||||
barWidth: '20%',
|
||||
barGap: '20%',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff',
|
||||
formatter: function(params) {
|
||||
// 只显示非零值,避免0重叠
|
||||
return params.value > 0 ? params.value : '';
|
||||
}
|
||||
formatter: '{c}'
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -1199,16 +1127,13 @@ const initCharts = () => {
|
||||
type: 'bar',
|
||||
data: outputData.value.master,
|
||||
itemStyle: { color: 'rgb(107,187,196)' },
|
||||
barWidth: '25%',
|
||||
barWidth: '20%',
|
||||
barGap: '20%',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff',
|
||||
formatter: function(params) {
|
||||
// 只显示非零值,避免0重叠
|
||||
return params.value > 0 ? params.value : '';
|
||||
}
|
||||
formatter: '{c}'
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -1216,16 +1141,13 @@ const initCharts = () => {
|
||||
type: 'bar',
|
||||
data: outputData.value.bachelor,
|
||||
itemStyle: { color: 'rgb(107,187,19)' },
|
||||
barWidth: '25%',
|
||||
barWidth: '20%',
|
||||
barGap: '20%',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff',
|
||||
formatter: function(params) {
|
||||
// 只显示非零值,避免0重叠
|
||||
return params.value > 0 ? params.value : '';
|
||||
}
|
||||
formatter: '{c}'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1390,12 +1312,6 @@ const newsData = ref([]);
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
if (typingInterval) {
|
||||
clearInterval(typingInterval);
|
||||
}
|
||||
if (thinkingInterval) {
|
||||
clearInterval(thinkingInterval);
|
||||
}
|
||||
window.removeEventListener('resize', () => {});
|
||||
})
|
||||
</script>
|
||||
@ -1849,31 +1765,7 @@ onUnmounted(() => {
|
||||
.assistant-panel .panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 思考消息样式 */
|
||||
.thinking-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.thinking-dots::after {
|
||||
content: '...';
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
animation: blinkDots 1.5s infinite steps(4, end);
|
||||
|
||||
}
|
||||
|
||||
@keyframes blinkDots {
|
||||
0%, 100% { opacity: 0; }
|
||||
25% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.message {
|
||||
transition: all 0.3s ease;
|
||||
cursor: move; /* 整个头部可拖动 */
|
||||
user-select: none; /* 防止拖动时选中文本 */
|
||||
}
|
||||
</style>
|
@ -3,23 +3,16 @@ const env = import.meta.env.MODE || 'development';
|
||||
|
||||
const config = {
|
||||
development: {
|
||||
// apiBaseUrl: 'http://192.168.5.49:48080',
|
||||
apiBaseUrl: 'http://36.103.199.218:48088',
|
||||
chatApiUrl: 'http://36.103.199.218:8093'
|
||||
apiBaseUrl: 'http://192.168.5.102:48080',
|
||||
},
|
||||
production: {
|
||||
// apiBaseUrl: 'http://221.238.217.216:4162',
|
||||
// chatApiUrl: 'http://221.238.217.216:8093',
|
||||
apiBaseUrl: 'http://36.103.199.218:48088',
|
||||
chatApiUrl: 'http://36.103.199.218:8093',
|
||||
apiBaseUrl: 'http://221.238.217.216:4162',
|
||||
// apiBaseUrl: 'http://36.103.199.218:48088',
|
||||
}
|
||||
};
|
||||
|
||||
export const getApiBaseUrl = () => {
|
||||
return config[env].apiBaseUrl;
|
||||
};
|
||||
export const getChatApiUrl = () => {
|
||||
return config[env].chatApiUrl;
|
||||
}
|
||||
|
||||
export default config;
|
Loading…
x
Reference in New Issue
Block a user