diff --git a/css/style.css b/css/style.css index 8d90aa5..bec8a1f 100644 --- a/css/style.css +++ b/css/style.css @@ -226,6 +226,15 @@ body::before { box-shadow: 0 4px 18px rgba(0, 245, 212, 0.4); } +.icon-btn.disabled, +.icon-btn:disabled { + opacity: 0.3; + cursor: not-allowed; + transform: none; + box-shadow: none; + pointer-events: none; +} + .icon-btn:active { transform: scale(0.95); } diff --git a/js/components/input-area.js b/js/components/input-area.js index d462c54..da3ffbf 100644 --- a/js/components/input-area.js +++ b/js/components/input-area.js @@ -5,7 +5,7 @@ import { state, KEYS } from '../state.js'; import { fileToBase64, fileToText, detectLanguage, debounce, truncate, escapeHtml, formatSize } from '../utils.js'; -import { getSelectedModel, isThinkEnabled } from './model-bar.js'; +import { getSelectedModel, isThinkEnabled, isVisionAvailable } from './model-bar.js'; import { renderMessages, appendAssistantPlaceholder, appendSystemMessage, updateLastAssistantMessage, clearMessages, safeMarkdown @@ -98,7 +98,13 @@ export function initInputArea() { chatInputEl.addEventListener('input', autoResizeTextarea); // 图片上传 - document.querySelector('#btnAttachImg').addEventListener('click', () => fileInputEl.click()); + document.querySelector('#btnAttachImg').addEventListener('click', () => { + if (!isVisionAvailable()) { + showToast('当前模型不支持图片分析', 'warning'); + return; + } + fileInputEl.click(); + }); fileInputEl.addEventListener('change', (e) => { handleImageSelect(e.target.files); e.target.value = ''; @@ -290,6 +296,12 @@ export async function sendMessage() { return; } + // 有图片但模型不支持 vision → 阻止发送 + if (pendingImages.length > 0 && !isVisionAvailable()) { + appendSystemMessage('⚠️ 当前模型不支持图片分析,请移除图片后重试'); + return; + } + const currentSession = state.get(KEYS.CURRENT_SESSION); if (!currentSession) return; diff --git a/js/components/model-bar.js b/js/components/model-bar.js index b05749a..947d87a 100644 --- a/js/components/model-bar.js +++ b/js/components/model-bar.js @@ -6,7 +6,7 @@ import { state, KEYS } from '../state.js'; import { formatSize } from '../utils.js'; -let modelSelectEl, badgeThinkEl, badgeVisionEl, thinkCheckbox, thinkWrap; +let modelSelectEl, badgeThinkEl, badgeVisionEl, thinkCheckbox, thinkWrap, btnAttachImg; /** 缓存模型能力 { modelName: { think: bool, vision: bool } } */ const modelCapabilityCache = new Map(); @@ -17,6 +17,7 @@ export function initModelBar() { badgeVisionEl = document.querySelector('#badgeVision'); thinkCheckbox = document.querySelector('#toggleThink'); thinkWrap = document.querySelector('#thinkToggleWrap'); + btnAttachImg = document.querySelector('#btnAttachImg'); // 模型切换 → 检测能力 + 保存设置 modelSelectEl.addEventListener('change', async () => { @@ -26,6 +27,7 @@ export function initModelBar() { await checkModelCapability(model); } else { setThinkAvailable(false); + setVisionAvailable(false); badgeThinkEl.style.display = 'none'; badgeVisionEl.style.display = 'none'; } @@ -48,6 +50,7 @@ export async function loadModels() { if (!data.models || data.models.length === 0) { modelSelectEl.innerHTML = ''; badgeThinkEl.style.display = 'none'; badgeVisionEl.style.display = 'none'; + setThinkAvailable(false); setVisionAvailable(false); return; } @@ -77,6 +80,7 @@ export async function loadModels() { console.error('[ModelBar] 加载模型失败:', err); modelSelectEl.innerHTML = ''; badgeThinkEl.style.display = 'none'; badgeVisionEl.style.display = 'none'; + setThinkAvailable(false); setVisionAvailable(false); } } @@ -117,6 +121,7 @@ async function checkModelCapability(modelName) { */ function applyCapability(modelName, caps) { setThinkAvailable(caps.think); + setVisionAvailable(caps.vision); // 更新能力标签 badgeThinkEl.style.display = caps.think ? '' : 'none'; @@ -139,6 +144,29 @@ function setThinkAvailable(available) { } } +/** + * 设置图片上传按钮是否可用 + */ +function setVisionAvailable(available) { + if (!btnAttachImg) return; + if (available) { + btnAttachImg.disabled = false; + btnAttachImg.title = '上传图片'; + btnAttachImg.classList.remove('disabled'); + } else { + btnAttachImg.disabled = true; + btnAttachImg.title = '当前模型不支持图片分析'; + btnAttachImg.classList.add('disabled'); + } +} + +/** + * 当前选择的模型是否支持视觉分析 + */ +export function isVisionAvailable() { + return btnAttachImg && !btnAttachImg.disabled; +} + /** * 获取当前选择的模型 */