stock-ai-agent/frontend/js/app.js
2026-02-03 10:08:15 +08:00

270 lines
8.0 KiB
JavaScript

// Vue 3 Application
const { createApp } = Vue;
createApp({
data() {
return {
messages: [],
userInput: '',
loading: false,
sessionId: null,
charts: {}
};
},
mounted() {
this.sessionId = this.generateSessionId();
this.autoResizeTextarea();
},
methods: {
async sendMessage() {
if (!this.userInput.trim() || this.loading) return;
const message = this.userInput.trim();
this.userInput = '';
// Add user message
this.messages.push({
role: 'user',
content: message,
timestamp: new Date()
});
this.$nextTick(() => {
this.scrollToBottom();
this.autoResizeTextarea();
});
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();
// Add assistant message
const assistantMessage = {
role: 'assistant',
content: data.message,
timestamp: new Date(),
metadata: data.metadata
};
this.messages.push(assistantMessage);
// Render chart if needed
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;
}
},
renderMarkdown(content) {
if (!content) return '';
// Configure marked options
marked.setOptions({
breaks: true,
gfm: true,
headerIds: false,
mangle: false
});
return marked.parse(content);
},
renderChart(index, data) {
const chartId = `chart-${index}`;
const container = document.getElementById(chartId);
if (!container || !data.kline_data) return;
const chart = LightweightCharts.createChart(container, {
width: container.clientWidth,
height: 400,
layout: {
background: { color: '#000000' },
textColor: '#a0a0a0'
},
grid: {
vertLines: { color: '#1a1a1a' },
horzLines: { color: '#1a1a1a' }
},
timeScale: {
borderColor: '#333333',
timeVisible: true
},
rightPriceScale: {
borderColor: '#333333'
}
});
const candlestickSeries = chart.addCandlestickSeries({
upColor: '#00ff41',
downColor: '#ff0040',
borderVisible: false,
wickUpColor: '#00ff41',
wickDownColor: '#ff0040'
});
const klineData = data.kline_data.map(item => ({
time: item.trade_date,
open: item.open,
high: item.high,
low: item.low,
close: item.close
}));
candlestickSeries.setData(klineData);
if (data.volume_data) {
const volumeSeries = chart.addHistogramSeries({
color: '#00ff4140',
priceFormat: {
type: 'volume'
},
priceScaleId: ''
});
const volumeData = data.volume_data.map(item => ({
time: item.trade_date,
value: item.vol,
color: item.close >= item.open ? '#00ff4140' : '#ff004040'
}));
volumeSeries.setData(volumeData);
}
chart.timeScale().fitContent();
this.charts[chartId] = chart;
// Handle resize
window.addEventListener('resize', () => {
if (this.charts[chartId]) {
chart.applyOptions({ width: container.clientWidth });
}
});
},
scrollToBottom() {
const container = this.$refs.chatContainer;
if (container) {
setTimeout(() => {
container.scrollTop = container.scrollHeight;
}, 100);
}
},
autoResizeTextarea() {
const textarea = this.$refs.textarea;
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
}
},
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
},
copyWechat() {
const wechatId = 'aaronlzhou';
// 使用现代的 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(wechatId).then(() => {
this.showCopyNotification();
}).catch(err => {
console.error('复制失败:', err);
this.fallbackCopy(wechatId);
});
} else {
this.fallbackCopy(wechatId);
}
},
fallbackCopy(text) {
// 降级方案:使用传统方法
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
this.showCopyNotification();
} catch (err) {
console.error('复制失败:', err);
}
document.body.removeChild(textarea);
},
showCopyNotification() {
// 创建临时提示
const notification = document.createElement('div');
notification.textContent = '已复制微信号';
notification.style.cssText = `
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background: #00ff41;
color: #000000;
padding: 8px 16px;
border-radius: 2px;
font-size: 13px;
font-weight: 500;
z-index: 10000;
animation: fadeInOut 2s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
}, 2000);
}
},
watch: {
userInput() {
this.$nextTick(() => {
this.autoResizeTextarea();
});
}
}
}).mount('#app');