// 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');