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;
+}
+
/**
* 获取当前选择的模型
*/