220 lines
7.0 KiB
Plaintext
220 lines
7.0 KiB
Plaintext
// Vue 3 应用
|
|
const { createApp } = Vue;
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
messages: [],
|
|
userInput: '',
|
|
loading: false,
|
|
sessionId: null,
|
|
showSkillPanel: false,
|
|
skills: [],
|
|
charts: {}
|
|
};
|
|
},
|
|
mounted() {
|
|
this.loadSkills();
|
|
// 生成会话ID
|
|
this.sessionId = this.generateSessionId();
|
|
},
|
|
methods: {
|
|
async sendMessage() {
|
|
if (!this.userInput.trim() || this.loading) return;
|
|
|
|
const message = this.userInput.trim();
|
|
this.userInput = '';
|
|
|
|
// 添加用户消息
|
|
this.messages.push({
|
|
role: 'user',
|
|
content: message,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
// 滚动到底部
|
|
this.$nextTick(() => {
|
|
this.scrollToBottom();
|
|
});
|
|
|
|
// 发送请求
|
|
this.loading = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/chat/message', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
message: message,
|
|
session_id: this.sessionId
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('请求失败');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// 添加助手消息
|
|
const assistantMessage = {
|
|
role: 'assistant',
|
|
content: data.message,
|
|
timestamp: new Date(),
|
|
metadata: data.metadata
|
|
};
|
|
|
|
this.messages.push(assistantMessage);
|
|
|
|
// 如果有图表数据,渲染图表
|
|
if (data.metadata && data.metadata.type === 'chart') {
|
|
this.$nextTick(() => {
|
|
const index = this.messages.length - 1;
|
|
this.renderChart(index, data.metadata.data);
|
|
});
|
|
}
|
|
|
|
// 滚动到底部
|
|
this.$nextTick(() => {
|
|
this.scrollToBottom();
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('发送消息失败:', error);
|
|
this.messages.push({
|
|
role: 'assistant',
|
|
content: '抱歉,发送消息失败,请稍后重试。',
|
|
timestamp: new Date()
|
|
});
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async loadSkills() {
|
|
try {
|
|
const response = await fetch('/api/skills/');
|
|
if (!response.ok) {
|
|
throw new Error('加载技能失败');
|
|
}
|
|
const data = await response.json();
|
|
this.skills = data.skills;
|
|
} catch (error) {
|
|
console.error('加载技能失败:', error);
|
|
}
|
|
},
|
|
|
|
async toggleSkill(skillName, enabled) {
|
|
try {
|
|
const endpoint = enabled ? 'enable' : 'disable';
|
|
const response = await fetch(`/api/skills/${skillName}/${endpoint}`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('切换技能失败');
|
|
}
|
|
|
|
// 重新加载技能列表
|
|
await this.loadSkills();
|
|
} catch (error) {
|
|
console.error('切换技能失败:', error);
|
|
// 恢复原状态
|
|
await this.loadSkills();
|
|
}
|
|
},
|
|
|
|
renderChart(index, chartData) {
|
|
const containerId = `chart-${index}`;
|
|
const container = document.getElementById(containerId);
|
|
|
|
if (!container || !chartData) return;
|
|
|
|
try {
|
|
// 创建图表
|
|
const chart = LightweightCharts.createChart(container, {
|
|
width: container.clientWidth,
|
|
height: 400,
|
|
layout: {
|
|
background: { color: '#ffffff' },
|
|
textColor: '#333',
|
|
},
|
|
grid: {
|
|
vertLines: { color: '#f0f0f0' },
|
|
horzLines: { color: '#f0f0f0' },
|
|
},
|
|
timeScale: {
|
|
borderColor: '#cccccc',
|
|
},
|
|
});
|
|
|
|
// 添加K线图
|
|
if (chartData.candlestick_data) {
|
|
const candlestickSeries = chart.addCandlestickSeries({
|
|
upColor: '#26a69a',
|
|
downColor: '#ef5350',
|
|
borderVisible: false,
|
|
wickUpColor: '#26a69a',
|
|
wickDownColor: '#ef5350',
|
|
});
|
|
candlestickSeries.setData(chartData.candlestick_data);
|
|
}
|
|
|
|
// 添加成交量
|
|
if (chartData.volume_data) {
|
|
const volumeSeries = chart.addHistogramSeries({
|
|
color: '#26a69a',
|
|
priceFormat: {
|
|
type: 'volume',
|
|
},
|
|
priceScaleId: '',
|
|
scaleMargins: {
|
|
top: 0.8,
|
|
bottom: 0,
|
|
},
|
|
});
|
|
volumeSeries.setData(chartData.volume_data);
|
|
}
|
|
|
|
// 自适应大小
|
|
chart.timeScale().fitContent();
|
|
|
|
// 保存图表实例
|
|
this.charts[containerId] = chart;
|
|
|
|
// 窗口大小改变时调整图表
|
|
window.addEventListener('resize', () => {
|
|
if (this.charts[containerId]) {
|
|
this.charts[containerId].applyOptions({
|
|
width: container.clientWidth
|
|
});
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('渲染图表失败:', error);
|
|
}
|
|
},
|
|
|
|
scrollToBottom() {
|
|
const container = this.$refs.messagesContainer;
|
|
if (container) {
|
|
container.scrollTop = container.scrollHeight;
|
|
}
|
|
},
|
|
|
|
formatTime(timestamp) {
|
|
const date = new Date(timestamp);
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
return `${hours}:${minutes}`;
|
|
},
|
|
|
|
generateSessionId() {
|
|
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
}
|
|
}
|
|
}).mount('#app');
|