diff --git a/web/frontend/src/components/agent/hub/market-skill-card.tsx b/web/frontend/src/components/agent/hub/market-skill-card.tsx index f3ee426a1..e04017812 100644 --- a/web/frontend/src/components/agent/hub/market-skill-card.tsx +++ b/web/frontend/src/components/agent/hub/market-skill-card.tsx @@ -36,6 +36,13 @@ export function MarketSkillCard({ }) { const { t } = useTranslation() + const installDisabledReason = (() => { + if (installPending) return t("pages.agent.skills.marketplace_installDisabled.installing") + if (result.installed) return t("pages.agent.skills.marketplace_installDisabled.installed") + if (!canInstall) return t("pages.agent.skills.marketplace_installDisabled.cannotInstall") + return t("pages.agent.skills.marketplace_install_action") + })() + return ( {installPending ? ( diff --git a/web/frontend/src/components/chat/chat-composer.tsx b/web/frontend/src/components/chat/chat-composer.tsx index b0b25d1db..9d9919a53 100644 --- a/web/frontend/src/components/chat/chat-composer.tsx +++ b/web/frontend/src/components/chat/chat-composer.tsx @@ -33,6 +33,12 @@ export function ChatComposer({ const { t } = useTranslation() const canInput = isConnected && hasDefaultModel + const inputDisabledReason = (() => { + if (!isConnected) return t("chat.inputDisabled.notConnected") + if (!hasDefaultModel) return t("chat.inputDisabled.noModel") + return null + })() + const handleKeyDown = (e: KeyboardEvent) => { if (e.nativeEvent.isComposing) return if (e.key === "Enter" && !e.shiftKey) { @@ -76,6 +82,7 @@ export function ChatComposer({ onKeyDown={handleKeyDown} placeholder={t("chat.placeholder")} disabled={!canInput} + title={inputDisabledReason || undefined} className={cn( "placeholder:text-muted-foreground/50 max-h-[200px] min-h-[60px] resize-none border-0 bg-transparent px-2 py-1 text-[15px] shadow-none transition-colors focus-visible:ring-0 focus-visible:outline-none dark:bg-transparent", !canInput && "cursor-not-allowed", @@ -83,6 +90,11 @@ export function ChatComposer({ minRows={1} maxRows={8} /> + {!canInput && inputDisabledReason && ( +
+ {inputDisabledReason} +
+ )}
diff --git a/web/frontend/src/components/models/model-card.tsx b/web/frontend/src/components/models/model-card.tsx index 3489f22e7..98fd3e02d 100644 --- a/web/frontend/src/components/models/model-card.tsx +++ b/web/frontend/src/components/models/model-card.tsx @@ -33,6 +33,18 @@ export function ModelCard({ const canSetDefault = model.available && !model.is_default && !model.is_virtual + const setDefaultDisabledReason = (() => { + if (settingDefault) return t("models.action.setDefaultDisabled.setting") + if (!model.available) return t("models.action.setDefaultDisabled.unavailable") + if (model.is_default) return t("models.action.setDefaultDisabled.isDefault") + if (model.is_virtual) return t("models.action.setDefaultDisabled.isVirtual") + return t("models.action.setDefault") + })() + + const deleteDisabledReason = model.is_default + ? t("models.action.deleteDisabled.isDefault") + : t("models.action.delete") + return (
onSetDefault(model)} disabled={settingDefault || !canSetDefault} - title={t("models.action.setDefault")} + title={setDefaultDisabledReason} > {settingDefault ? ( @@ -110,7 +122,7 @@ export function ModelCard({ size="icon-sm" onClick={() => onDelete(model)} disabled={model.is_default} - title={t("models.action.delete")} + title={deleteDisabledReason} className="text-muted-foreground hover:text-destructive hover:bg-destructive/10" > diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index b53abeb76..4546121e6 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -55,6 +55,10 @@ "deleteSession": "Delete session", "messagesCount": "{{count}} messages", "noModel": "Select model", + "inputDisabled": { + "notConnected": "Gateway is not running. Start it to chat.", + "noModel": "No default model configured. Go to Models page to set one." + }, "attachImage": "Add images", "removeImage": "Remove image", "uploadedImage": "Uploaded image", @@ -199,7 +203,16 @@ "action": { "edit": "Edit API key", "setDefault": "Set as default", - "delete": "Delete model" + "delete": "Delete model", + "setDefaultDisabled": { + "setting": "Setting as default...", + "unavailable": "Cannot set unavailable model as default", + "isDefault": "Already the default model", + "isVirtual": "Cannot set virtual model as default" + }, + "deleteDisabled": { + "isDefault": "Cannot delete the default model" + } }, "defaultOnSave": { "label": "Default Model", @@ -487,6 +500,11 @@ "version": "Installed Version", "lines": "Line Count", "characters": "Character Count" + }, + "marketplace_installDisabled": { + "installing": "Installing...", + "installed": "Already installed", + "cannotInstall": "Cannot install: related tool is not enabled" } }, "tools": { @@ -655,4 +673,4 @@ "description": "Need more help? Click the documentation button in the top right corner to view detailed guides and configuration docs." } } -} \ No newline at end of file +} diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index e2e8eae04..577b9a26b 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -55,6 +55,10 @@ "deleteSession": "删除会话", "messagesCount": "{{count}} 条消息", "noModel": "选择模型", + "inputDisabled": { + "notConnected": "服务未运行,请先启动以进行对话。", + "noModel": "未设置默认模型,请前往模型页面进行配置。" + }, "attachImage": "添加图片", "removeImage": "移除图片", "uploadedImage": "已上传图片", @@ -199,7 +203,16 @@ "action": { "edit": "编辑 API Key", "setDefault": "设为默认", - "delete": "删除模型" + "delete": "删除模型", + "setDefaultDisabled": { + "setting": "正在设为默认...", + "unavailable": "无法将不可用的模型设为默认", + "isDefault": "该模型已是默认模型", + "isVirtual": "无法将虚拟模型设为默认" + }, + "deleteDisabled": { + "isDefault": "无法删除默认模型" + } }, "defaultOnSave": { "label": "默认模型", @@ -487,6 +500,11 @@ "version": "已安装版本", "lines": "行数", "characters": "字符数" + }, + "marketplace_installDisabled": { + "installing": "正在安装...", + "installed": "已安装", + "cannotInstall": "无法安装:相关工具未启用" } }, "tools": { @@ -655,4 +673,4 @@ "description": "需要更多帮助?点击右上角的文档按钮,查看详细的使用文档和配置指南。" } } -} \ No newline at end of file +}