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