/** * 应用核心逻辑 */ class ProductCanvasApp { constructor() { this.currentMode = 'canvas'; // 'canvas' 或 'swot' this.svgStorage = {}; this.currentSvgId = null; this.conversationHistory = {}; this.isProcessing = false; this.currentStreamingMessage = null; this.initElements(); this.initEventListeners(); this.loadSavedData(); this.updateModeUI(); } // 初始化DOM元素引用 initElements() { // 模式切换按钮 this.canvasBtn = document.getElementById('canvas-mode-btn'); this.swotBtn = document.getElementById('swot-mode-btn'); this.pageTitle = document.getElementById('page-title'); // 对话相关 this.chatInput = document.getElementById('chat-input'); this.sendButton = document.getElementById('send-button'); this.clearButton = document.getElementById('clear-button'); this.chatHistory = document.getElementById('chat-history'); // SVG显示 this.svgViewer = document.getElementById('svg-viewer'); this.placeholderText = document.getElementById('placeholder-text'); // 底部操作按钮 this.downloadSvgBtn = document.getElementById('download-svg-btn'); this.exportImageBtn = document.getElementById('export-image-btn'); this.viewCodeBtn = document.getElementById('view-code-btn'); // API配置模态窗 this.settingsBtn = document.getElementById('settings-btn'); this.configModal = document.getElementById('config-modal'); this.closeModalBtn = document.getElementById('close-modal-btn'); this.apiUrlInput = document.getElementById('api-url'); this.apiKeyInput = document.getElementById('api-key'); this.apiModelInput = document.getElementById('api-model'); this.testApiBtn = document.getElementById('test-api-btn'); this.saveConfigBtn = document.getElementById('save-config-btn'); this.configStatus = document.getElementById('config-status'); this.statusText = document.getElementById('status-text'); } // 初始化事件监听器 initEventListeners() { // 模式切换 this.canvasBtn.addEventListener('click', () => this.switchMode('canvas')); this.swotBtn.addEventListener('click', () => this.switchMode('swot')); // 发送消息 this.sendButton.addEventListener('click', () => this.sendMessage()); this.clearButton.addEventListener('click', () => this.clearCurrentConversation()); // 输入框事件 this.chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // 自动调整输入框高度 this.chatInput.addEventListener('input', () => { Utils.autoResizeTextarea(this.chatInput); }); // 底部操作按钮 this.downloadSvgBtn.addEventListener('click', () => this.downloadSVG()); this.exportImageBtn.addEventListener('click', () => this.exportAsImage()); this.viewCodeBtn.addEventListener('click', () => this.viewSVGCode()); // API配置模态窗 this.settingsBtn.addEventListener('click', () => this.openConfigModal()); this.closeModalBtn.addEventListener('click', () => this.closeConfigModal()); this.configModal.addEventListener('click', (e) => { if (e.target === this.configModal) { this.closeConfigModal(); } }); this.testApiBtn.addEventListener('click', () => this.testAPIConnection()); this.saveConfigBtn.addEventListener('click', () => this.saveAPIConfig()); } // 加载保存的数据 loadSavedData() { // 加载模式 const savedMode = Utils.storage.get('currentMode', 'canvas'); this.currentMode = savedMode; // 加载对话历史(按模式分别存储) const savedCanvasHistory = Utils.storage.get('canvasHistory', []); const savedSwotHistory = Utils.storage.get('swotHistory', []); this.conversationHistory = { canvas: savedCanvasHistory, swot: savedSwotHistory }; this.renderConversationHistory(); // 加载SVG存储(按模式分别存储) const savedCanvasSVGs = Utils.storage.get('canvasSVGs', {}); const savedSwotSVGs = Utils.storage.get('swotSVGs', {}); this.svgStorage = { canvas: savedCanvasSVGs, swot: savedSwotSVGs }; // 加载API配置 const apiConfig = window.apiClient.getConfig(); this.apiUrlInput.value = apiConfig.url || ''; this.apiKeyInput.value = apiConfig.key || ''; this.apiModelInput.value = apiConfig.model || ''; } // 切换模式 switchMode(mode) { if (this.currentMode === mode) return; this.currentMode = mode; Utils.storage.set('currentMode', mode); this.updateModeUI(); } // 更新模式UI updateModeUI() { if (this.currentMode === 'canvas') { this.canvasBtn.classList.add('mode-btn-active'); this.canvasBtn.classList.remove('mode-btn-inactive'); this.swotBtn.classList.remove('mode-btn-active'); this.swotBtn.classList.add('mode-btn-inactive'); this.pageTitle.textContent = '产品画布'; if (!this.currentSvgId) { this.placeholderText.textContent = '生成的产品画布将在此处显示'; } } else { this.swotBtn.classList.add('mode-btn-active'); this.swotBtn.classList.remove('mode-btn-inactive'); this.canvasBtn.classList.remove('mode-btn-active'); this.canvasBtn.classList.add('mode-btn-inactive'); this.pageTitle.textContent = 'SWOT分析'; if (!this.currentSvgId) { this.placeholderText.textContent = '生成的SWOT分析将在此处显示'; } } } // 发送消息 async sendMessage() { const message = this.chatInput.value.trim(); if (!message || this.isProcessing) return; // 检查API配置 if (!window.apiClient.isConfigValid()) { alert('⚠️ 请先配置API设置!点击右上角齿轮图标进行配置。'); this.openConfigModal(); return; } this.isProcessing = true; this.sendButton.disabled = true; this.sendButton.innerHTML = ''; // 添加用户消息 this.addUserMessage(message); this.chatInput.value = ''; Utils.autoResizeTextarea(this.chatInput); try { // 获取对话上下文 const contextMessages = this.conversationHistory[this.currentMode] .slice(-10) // 只取最近10条消息作为上下文 .map(msg => ({ role: msg.type === 'user' ? 'user' : 'assistant', content: msg.content })); // 开始流式接收消息 await this.startStreamingMessage(message, contextMessages); } catch (error) { console.error('发送消息失败:', error); this.addErrorMessage(error.message); this.isProcessing = false; this.sendButton.disabled = false; this.sendButton.innerHTML = ''; } } // 开始流式接收消息 async startStreamingMessage(userMessage, contextMessages) { // 创建流式消息容器 const messageId = Utils.generateId('msg'); const messageContainer = this.createStreamingMessageContainer(messageId); this.chatHistory.appendChild(messageContainer); Utils.scrollToBottom(this.chatHistory); let fullContent = ''; const onChunk = (chunk) => { if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta) { const content = chunk.choices[0].delta.content || ''; fullContent += content; this.updateStreamingMessage(messageContainer, fullContent); } }; const onComplete = () => { // 流式接收完成,处理完整消息 this.finalizeStreamingMessage(messageId, fullContent); this.isProcessing = false; this.sendButton.disabled = false; this.sendButton.innerHTML = ''; }; // 调用流式API if (this.currentMode === 'canvas') { await window.apiClient.generateProductCanvasStream(userMessage, contextMessages, onChunk, onComplete); } else { await window.apiClient.generateSWOTAnalysisStream(userMessage, contextMessages, onChunk, onComplete); } } // 创建流式消息容器 createStreamingMessageContainer(messageId) { const messageDiv = document.createElement('div'); messageDiv.className = 'flex justify-start'; messageDiv.innerHTML = `
`; return messageDiv; } // 更新流式消息内容 updateStreamingMessage(container, content) { const contentDiv = container.querySelector('.typing-cursor'); if (contentDiv) { contentDiv.textContent = content; Utils.scrollToBottom(this.chatHistory); } } // 完成流式消息 finalizeStreamingMessage(messageId, fullContent) { const container = document.querySelector(`[data-message-id="${messageId}"]`); if (!container) return; const parsed = Utils.parseSVGResponse(fullContent); const message = { id: messageId, type: 'ai', content: fullContent, timestamp: new Date().toISOString() }; this.conversationHistory[this.currentMode].push(message); // 如果包含SVG,存储SVG内容 if (parsed.svgContent) { const svgId = Utils.generateId('svg'); this.svgStorage[this.currentMode][svgId] = { content: parsed.svgContent, messageId: messageId, mode: this.currentMode, timestamp: new Date().toISOString() }; this.viewSVG(svgId); // 更新容器内容为包含SVG的消息 container.innerHTML = `
${Utils.escapeHtml(parsed.beforeText)}
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
${Utils.escapeHtml(parsed.afterText)}
`; } else { // 更新容器内容为普通消息 container.innerHTML = `
${Utils.escapeHtml(fullContent)}
`; } // 保存数据 Utils.storage.set(`conversationHistory`, this.conversationHistory); Utils.storage.set(`svgStorage`, this.svgStorage); } // 清空当前对话 clearCurrentConversation() { if (!confirm(`确定要清空当前的${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}对话吗?`)) { return; } // 清空当前模式的对话历史 this.conversationHistory[this.currentMode] = []; // 清空当前模式的SVG存储 this.svgStorage[this.currentMode] = {}; // 如果当前显示的是被清空的模式的SVG,清空显示 if (this.currentSvgId && this.svgStorage[this.currentMode][this.currentSvgId]) { this.currentSvgId = null; this.svgViewer.innerHTML = `

生成的${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}将在此处显示

`; } // 保存数据 Utils.storage.set('conversationHistory', this.conversationHistory); Utils.storage.set('svgStorage', this.svgStorage); // 重新渲染对话历史 this.renderConversationHistory(); } // 添加用户消息 addUserMessage(text) { const messageId = Utils.generateId('msg'); const message = { id: messageId, type: 'user', content: text, timestamp: new Date().toISOString() }; this.conversationHistory[this.currentMode].push(message); this.renderMessage(message); Utils.scrollToBottom(this.chatHistory); Utils.storage.set('conversationHistory', this.conversationHistory); } // 添加AI消息(非流式,保留用于错误情况) addAIMessage(text) { const messageId = Utils.generateId('msg'); const parsed = Utils.parseSVGResponse(text); const message = { id: messageId, type: 'ai', content: text, timestamp: new Date().toISOString() }; this.conversationHistory[this.currentMode].push(message); // 如果包含SVG,存储SVG内容 if (parsed.svgContent) { const svgId = Utils.generateId('svg'); this.svgStorage[this.currentMode][svgId] = { content: parsed.svgContent, messageId: messageId, mode: this.currentMode, timestamp: new Date().toISOString() }; Utils.storage.set('svgStorage', this.svgStorage); this.viewSVG(svgId); // 渲染包含SVG占位符的消息 this.renderMessageWithSVG(message, parsed, svgId); } else { // 渲染普通消息 this.renderMessage(message); } Utils.scrollToBottom(this.chatHistory); Utils.storage.set('conversationHistory', this.conversationHistory); } // 添加错误消息 addErrorMessage(errorText) { const messageId = Utils.generateId('msg'); const message = { id: messageId, type: 'error', content: errorText, timestamp: new Date().toISOString() }; this.conversationHistory[this.currentMode].push(message); this.renderMessage(message); Utils.scrollToBottom(this.chatHistory); Utils.storage.set('conversationHistory', this.conversationHistory); } // 渲染消息 renderMessage(message) { const messageDiv = document.createElement('div'); if (message.type === 'user') { messageDiv.className = 'flex justify-end'; messageDiv.innerHTML = `
${Utils.escapeHtml(message.content)}
`; } else if (message.type === 'error') { messageDiv.className = 'flex justify-start'; messageDiv.innerHTML = `
${Utils.escapeHtml(message.content)}
`; } else { messageDiv.className = 'flex justify-start'; messageDiv.innerHTML = `
${Utils.escapeHtml(message.content)}
`; } this.chatHistory.appendChild(messageDiv); } // 渲染包含SVG的消息 renderMessageWithSVG(message, parsed, svgId) { const messageDiv = document.createElement('div'); messageDiv.className = 'flex justify-start'; messageDiv.innerHTML = `
${Utils.escapeHtml(parsed.beforeText)}
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
${Utils.escapeHtml(parsed.afterText)}
`; this.chatHistory.appendChild(messageDiv); } // 渲染对话历史 renderConversationHistory() { this.chatHistory.innerHTML = ''; for (const message of this.conversationHistory) { if (message.type === 'ai') { const parsed = Utils.parseSVGResponse(message.content); // 查找对应的SVG let svgId = null; for (const [id, svg] of Object.entries(this.svgStorage)) { if (svg.messageId === message.id) { svgId = id; break; } } if (svgId && parsed.svgContent) { this.renderMessageWithSVG(message, parsed, svgId); } else { this.renderMessage(message); } } else { this.renderMessage(message); } } } // 显示SVG viewSVG(svgId) { if (!this.svgStorage[svgId]) { console.error('SVG not found:', svgId); return; } this.currentSvgId = svgId; const svgContent = this.svgStorage[svgId].content; this.svgViewer.innerHTML = svgContent; } // 退回到指定消息 rollbackToMessage(messageId) { const messageIndex = this.conversationHistory.findIndex(msg => msg.id === messageId); if (messageIndex === -1) return; // 删除指定消息之后的所有消息 const messagesToRemove = this.conversationHistory.slice(messageIndex + 1); // 删除相关的SVG for (const message of messagesToRemove) { for (const [svgId, svg] of Object.entries(this.svgStorage)) { if (svg.messageId === message.id) { delete this.svgStorage[svgId]; // 如果当前显示的是被删除的SVG,清空显示 if (this.currentSvgId === svgId) { this.currentSvgId = null; this.svgViewer.innerHTML = `

生成的${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}将在此处显示

`; } } } } // 更新对话历史 this.conversationHistory = this.conversationHistory.slice(0, messageIndex + 1); // 保存数据 Utils.storage.set('conversationHistory', this.conversationHistory); Utils.storage.set('svgStorage', this.svgStorage); // 重新渲染对话历史 this.renderConversationHistory(); } // 重新生成消息 async regenerateMessage(messageId) { if (this.isProcessing) return; this.isProcessing = true; this.sendButton.disabled = true; this.sendButton.innerHTML = ''; try { // 重新生成响应 const response = await window.apiClient.regenerateResponse(messageId, this.conversationHistory); // 退回到指定消息 this.rollbackToMessage(messageId); // 添加新的AI回复 this.addAIMessage(response); } catch (error) { console.error('重新生成失败:', error); this.addErrorMessage(error.message); } finally { this.isProcessing = false; this.sendButton.disabled = false; this.sendButton.innerHTML = ''; } } // 下载SVG downloadSVG() { if (!this.currentSvgId) { alert('请先生成SVG图表'); return; } const svgContent = this.svgStorage[this.currentSvgId].content; const filename = `${this.currentMode}-${Utils.formatDateTime().replace(/[/:]/g, '-')}.svg`; Utils.downloadFile(svgContent, filename, 'image/svg+xml'); } // 导出为图片 exportAsImage() { if (!this.currentSvgId) { alert('请先生成SVG图表'); return; } // 这里可以实现SVG转PNG的功能 // 由于需要额外的库,这里先提示用户 alert('SVG转PNG功能需要额外的库支持,您可以使用下载SVG功能,然后使用在线工具转换。'); } // 查看SVG代码 viewSVGCode() { if (!this.currentSvgId) { alert('请先生成SVG图表'); return; } const svgContent = this.svgStorage[this.currentSvgId].content; // 创建代码查看模态窗 const modal = document.createElement('div'); modal.className = 'modal-overlay active'; modal.innerHTML = ` `; document.body.appendChild(modal); // 关闭模态窗 const closeModal = () => { document.body.removeChild(modal); }; modal.querySelector('.close-modal').addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(); } }); // 复制代码 modal.querySelector('.copy-btn').addEventListener('click', () => { navigator.clipboard.writeText(svgContent).then(() => { const btn = modal.querySelector('.copy-btn'); const originalHTML = btn.innerHTML; btn.innerHTML = ' 已复制'; btn.classList.remove('bg-blue-500', 'hover:bg-blue-600'); btn.classList.add('bg-green-500', 'hover:bg-green-600'); setTimeout(() => { btn.innerHTML = originalHTML; btn.classList.remove('bg-green-500', 'hover:bg-green-600'); btn.classList.add('bg-blue-500', 'hover:bg-blue-600'); }, 2000); }); }); } // 打开API配置模态窗 openConfigModal() { this.configModal.classList.add('active'); const apiConfig = window.apiClient.getConfig(); this.apiUrlInput.value = apiConfig.url || ''; this.apiKeyInput.value = apiConfig.key || ''; this.apiModelInput.value = apiConfig.model || ''; } // 关闭API配置模态窗 closeConfigModal() { this.configModal.classList.remove('active'); } // 保存API配置 saveAPIConfig() { const config = { url: this.apiUrlInput.value.trim(), key: this.apiKeyInput.value.trim(), model: this.apiModelInput.value.trim() }; if (!config.url || !config.key || !config.model) { Utils.showStatus(this.configStatus, '⚠️ 请填写所有字段', 'error'); return; } window.apiClient.saveConfig(config); Utils.showStatus(this.configStatus, '✅ 配置已保存成功!', 'success'); setTimeout(() => { this.closeConfigModal(); }, 1500); } // 测试API连接 async testAPIConnection() { const config = { url: this.apiUrlInput.value.trim(), key: this.apiKeyInput.value.trim(), model: this.apiModelInput.value.trim() }; if (!config.url || !config.key || !config.model) { Utils.showStatus(this.configStatus, '⚠️ 请先填写所有字段', 'error'); return; } Utils.showStatus(this.configStatus, '🔄 正在测试连接...', 'loading'); try { // 临时保存配置进行测试 window.apiClient.saveConfig(config); await window.apiClient.testConnection(); Utils.showStatus(this.configStatus, '✅ 连接测试成功!', 'success'); } catch (error) { Utils.showStatus(this.configStatus, `❌ 连接失败: ${error.message}`, 'error'); } } } // 页面加载完成后初始化应用 document.addEventListener('DOMContentLoaded', () => { window.app = new ProductCanvasApp(); });