window.ChatbotPresetQuestionsUI = { /** * 渲染预设问题区域。 * @param {HTMLElement} parentElement - presetContainer 将被添加到的父元素 (通常是 chatbotWindow 或 mainContentArea)。 * @param {boolean} isCustomModel - 当前是否为自定义模型。 * @param {string} currentDocId - 当前文档ID,用于自动收起逻辑。 * @param {function} updateChatbotUICallback - 用于在按钮点击时触发主UI更新的回调。 * @param {function} handlePresetQuestionCallback - 处理预设问题点击的回调。 * @returns {HTMLElement} 创建的 presetContainer 元素,或在失败时返回 null。 */ render: function(parentElement, isCustomModel, currentDocId, updateChatbotUICallback, handlePresetQuestionCallback) { // 确保清除旧的 presetContainer (如果存在) let existingPresetContainer = document.getElementById('chatbot-preset-container'); if (existingPresetContainer) existingPresetContainer.remove(); const presetContainer = document.createElement('div'); presetContainer.id = 'chatbot-preset-container'; presetContainer.style.position = 'absolute'; presetContainer.style.top = '58px'; // 稍微增加顶部距离,避免过于拥挤 (原53px) presetContainer.style.left = '0px'; presetContainer.style.right = '0px'; presetContainer.style.zIndex = '5'; // 确保在聊天内容之上但在模态框之内 presetContainer.style.padding = '6px 24px'; // 恢复适度的内边距,增加呼吸感 (原4px 20px) const newPresetHeader = document.createElement('div'); newPresetHeader.id = 'chatbot-preset-header'; newPresetHeader.style.display = 'flex'; newPresetHeader.style.alignItems = 'center'; newPresetHeader.style.justifyContent = 'space-between'; newPresetHeader.style.marginBottom = '6px'; // 稍微增加底部间距 (原4px) newPresetHeader.style.padding = '0'; const presetTitle = document.createElement('span'); presetTitle.textContent = '快捷指令'; presetTitle.style.fontWeight = '600'; presetTitle.style.fontSize = '0.9em'; presetTitle.style.color = '#4b5563'; // 深灰色标题 const presetToggleBtn = document.createElement('button'); presetToggleBtn.id = 'chatbot-preset-toggle-btn'; presetToggleBtn.style.background = 'none'; presetToggleBtn.style.border = 'none'; presetToggleBtn.style.cursor = 'pointer'; presetToggleBtn.style.padding = '4px'; presetToggleBtn.style.color = '#4b5563'; presetToggleBtn.innerHTML = window.isPresetQuestionsCollapsed ? '' // 向下箭头表示"显示" : ''; // 向上箭头表示"隐藏" presetToggleBtn.title = window.isPresetQuestionsCollapsed ? "展开快捷指令" : "收起快捷指令"; presetToggleBtn.onclick = function() { window.isPresetQuestionsCollapsed = !window.isPresetQuestionsCollapsed; updateChatbotUICallback(); // 调用主UI更新 }; const headerLeftGroup = document.createElement('div'); headerLeftGroup.style.display = 'flex'; headerLeftGroup.style.alignItems = 'center'; headerLeftGroup.style.gap = '8px'; headerLeftGroup.appendChild(presetTitle); headerLeftGroup.appendChild(presetToggleBtn); newPresetHeader.appendChild(headerLeftGroup); // 齿轮按钮(模型配置)- 始终显示 const gearBtn = document.createElement('button'); gearBtn.id = 'chatbot-model-gear-btn'; gearBtn.title = '模型配置'; gearBtn.innerHTML = ''; gearBtn.style.display = 'flex'; gearBtn.style.alignItems = 'center'; gearBtn.style.justifyContent = 'center'; gearBtn.style.background = 'none'; gearBtn.style.border = 'none'; gearBtn.style.cursor = 'pointer'; gearBtn.style.padding = '2.5px'; gearBtn.style.borderRadius = '50%'; gearBtn.style.transition = 'background 0.16s, box-shadow 0.16s'; gearBtn.onmouseover = function(){ this.style.background = '#e0f2fe'; this.style.boxShadow = '0 1.5px 6px 0 rgba(59,130,246,0.10)'; }; gearBtn.onmouseout = function(){ this.style.background = 'none'; this.style.boxShadow = 'none'; }; gearBtn.onclick = function(){ // 打开独立的chatbot模型配置弹窗 if (typeof window !== 'undefined' && window.ChatbotModelConfigModal) { window.ChatbotModelConfigModal.open(); } else { console.warn('[Chatbot Gear] ChatbotModelConfigModal 不可用,回退到旧版选择器'); window.isModelSelectorOpen = true; // 设置全局状态 updateChatbotUICallback(); // 调用主UI更新 } }; newPresetHeader.appendChild(gearBtn); presetContainer.appendChild(newPresetHeader); const newPresetBody = document.createElement('div'); newPresetBody.id = 'chatbot-preset-body'; newPresetBody.style.display = 'flex'; newPresetBody.style.flexWrap = 'wrap'; newPresetBody.style.gap = '6px 8px'; newPresetBody.style.transition = 'opacity 0.3s ease-out, max-height 0.4s ease-out, margin-bottom 0.4s ease-out, visibility 0.3s ease-out'; newPresetBody.style.overflow = 'hidden'; newPresetBody.style.width = '100%'; // 确保它占据其父容器的全部宽度 presetContainer.appendChild(newPresetBody); if (!parentElement) { console.error("ChatbotPresetQuestionsUI: Parent element for presetContainer is not provided or not found."); return null; } // 将 presetContainer 添加到指定的父元素 parentElement.appendChild(presetContainer); // 填充预设问题按钮 const presetQuestions = (window.ChatbotPreset && window.ChatbotPreset.PRESET_QUESTIONS) ? window.ChatbotPreset.PRESET_QUESTIONS : [ '总结本文', '有哪些关键公式?', '研究背景与意义?', '研究方法及发现?', '应用与前景?', '用通俗语言解释全文', '生成思维导图🧠', '生成流程图🔄', '生成更多配图🎨' ]; presetQuestions.forEach(q => { const button = document.createElement('button'); button.className = 'preset-btn'; // 使用 encodeURIComponent/decodeURIComponent 来处理特殊字符 button.onclick = function() { const text = decodeURIComponent(encodeURIComponent(q)); // 对“生成更多配图”做特殊处理:只帮用户打出前缀 [加入配图],不直接发送复杂提示 if (text.startsWith('生成更多配图')) { const input = document.getElementById('chatbot-input'); if (input) { input.value = '[加入配图] '; input.focus(); } return; } handlePresetQuestionCallback(text); }; button.textContent = q; newPresetBody.appendChild(button); }); // 自动收起逻辑 (依赖全局状态 window.isPresetQuestionsCollapsed, window.presetAutoCollapseTriggeredForDoc, window.isModelSelectorOpen 和 ChatbotCore) let userMessageCount = 0; if (window.ChatbotCore && window.ChatbotCore.chatHistory) { userMessageCount = window.ChatbotCore.chatHistory.filter(m => m.role === 'user').length; } if (userMessageCount >= 3 && currentDocId && window.presetAutoCollapseTriggeredForDoc && !window.presetAutoCollapseTriggeredForDoc[currentDocId] && !window.isPresetQuestionsCollapsed && !window.isModelSelectorOpen) { window.isPresetQuestionsCollapsed = true; window.presetAutoCollapseTriggeredForDoc[currentDocId] = true; // 按钮的图标和标题将在下一次 updateChatbotUICallback 调用时由 presetToggleBtn 自身逻辑更新 // 但为了即时性,如果状态因此改变,可以手动更新一下按钮 presetToggleBtn.innerHTML = ''; presetToggleBtn.title = "展开快捷指令"; } // 控制 chatbot-preset-body 和 presetContainer 的显隐与动画 // (依赖全局状态 window.isPresetQuestionsCollapsed, window.isModelSelectorOpen) if (window.isPresetQuestionsCollapsed || window.isModelSelectorOpen) { newPresetBody.style.opacity = '0'; newPresetBody.style.maxHeight = '0'; newPresetBody.style.marginBottom = '0'; newPresetBody.style.visibility = 'hidden'; presetContainer.style.boxShadow = 'none'; presetContainer.style.background = 'transparent'; presetContainer.style.paddingTop = '0px'; // 折叠时移除垂直内边距 presetContainer.style.paddingBottom = '0px'; } else { newPresetBody.style.opacity = '1'; newPresetBody.style.maxHeight = '150px'; // 或者一个更合适的计算值 newPresetBody.style.marginBottom = '0px'; // 或者根据需要调整 newPresetBody.style.visibility = 'visible'; presetContainer.style.boxShadow = 'none'; // 可以设置展开时的阴影 presetContainer.style.paddingTop = '6px'; // 恢复垂直内边距 presetContainer.style.paddingBottom = '6px'; // 背景渐变逻辑 (依赖父窗口背景色) const chatWindowBgElement = document.querySelector('#chatbot-modal .chatbot-window'); let chatWinBg = 'rgb(255,255,255)'; // 默认背景 if (chatWindowBgElement) { chatWinBg = getComputedStyle(chatWindowBgElement).getPropertyValue('background-color') || 'rgb(255,255,255)'; } let opaqueBg = chatWinBg; if (opaqueBg.startsWith('rgba')) { // 转换为不透明的rgb const parts = opaqueBg.match(/[\d.]+/g); if (parts && parts.length === 4) { // rgba(r,g,b,a) opaqueBg = `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`; } else if (parts && parts.length === 3) { // 可能已经是 rgb 字符串 opaqueBg = `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`; } } else if (opaqueBg === 'transparent') { opaqueBg = 'rgb(255,255,255)'; // 透明时的回退 } presetContainer.style.background = `linear-gradient(to bottom, ${opaqueBg} 0%, ${opaqueBg} 70%, transparent 100%)`; } return presetContainer; // 返回创建的容器,主UI函数可以用它来计算布局 } };