feat: add skill panel and fix skill icon (#13666)

* fix: custom agent skill icon

* feat: support skill detail

* chore: remove unnecessary custom tag
This commit is contained in:
Rdmclin2 2026-04-08 18:51:01 +08:00 committed by GitHub
parent c68dfa00df
commit e10265fadd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1047 additions and 130 deletions

View file

@ -844,31 +844,83 @@
"tab.uploadZip.desc": "Upload a local .zip or .skill file",
"tab.usage": "Usage",
"tools.add": "Add Skill",
"tools.builtins.find-skills.description": "Helps users discover and install agent skills when they ask \"how do I do X\", \"find a skill for X\", or want to extend capabilities",
"tools.builtins.find-skills.title": "Find Skills",
"tools.builtins.groupName": "Built-ins",
"tools.builtins.install": "Install",
"tools.builtins.installed": "Installed",
"tools.builtins.lobe-activator.description": "Discover and activate tools and skills",
"tools.builtins.lobe-activator.title": "Tools & Skills Activator",
"tools.builtins.lobe-agent-browser.description": "Browser automation CLI for AI agents. Use when tasks involve website or Electron interaction such as navigation, form filling, clicking, screenshot capture, scraping data, login flows, and end-to-end app testing.",
"tools.builtins.lobe-agent-browser.title": "Agent Browser",
"tools.builtins.lobe-agent-builder.description": "Configure agent metadata, model settings, plugins, and the system prompt",
"tools.builtins.lobe-agent-builder.title": "Agent Builder",
"tools.builtins.lobe-agent-documents.description": "Manage agent-scoped documents (list, create, read, edit, remove, rename) and load rules",
"tools.builtins.lobe-agent-documents.title": "Documents",
"tools.builtins.lobe-agent-management.description": "Create, manage, and orchestrate AI agents",
"tools.builtins.lobe-agent-management.title": "Agent Management",
"tools.builtins.lobe-artifacts.description": "Generate and preview interactive UI components and visualizations",
"tools.builtins.lobe-artifacts.readme": "Generate and live-preview interactive UI components, data visualizations, charts, SVG graphics, and web applications. Create rich visual content that users can interact with directly.",
"tools.builtins.lobe-artifacts.title": "Artifacts",
"tools.builtins.lobe-brief.description": "Report progress, deliver results, and request user decisions",
"tools.builtins.lobe-brief.title": "Brief Tools",
"tools.builtins.lobe-calculator.description": "Perform mathematical calculations, solve equations, and work with symbolic expressions",
"tools.builtins.lobe-calculator.readme": "Advanced mathematical calculator supporting basic arithmetic, algebraic equations, calculus operations, and symbolic math. Includes base conversion, equation solving, differentiation, integration, and more.",
"tools.builtins.lobe-calculator.title": "Calculator",
"tools.builtins.lobe-cloud-sandbox.description": "Execute code, run commands, and manage files in a secure cloud environment",
"tools.builtins.lobe-cloud-sandbox.readme": "Execute Python, JavaScript, and TypeScript code in an isolated cloud environment. Run shell commands, manage files, search content with regex, and export results securely.",
"tools.builtins.lobe-cloud-sandbox.title": "Cloud Sandbox",
"tools.builtins.lobe-creds.description": "Manage user credentials for authentication, environment variable injection, and API verification — handle API keys, OAuth tokens, and secrets for third-party integrations.",
"tools.builtins.lobe-creds.title": "Credentials",
"tools.builtins.lobe-cron.description": "Manage scheduled tasks that run automatically at specified times. Create, update, enable/disable, and monitor recurring tasks for your agents.",
"tools.builtins.lobe-cron.title": "Scheduled Tasks",
"tools.builtins.lobe-group-agent-builder.description": "Configure group metadata, members, and shared content for multi-agent groups",
"tools.builtins.lobe-group-agent-builder.title": "Group Agent Builder",
"tools.builtins.lobe-group-management.description": "Orchestrate and manage multi-agent group conversations",
"tools.builtins.lobe-group-management.title": "Group Management",
"tools.builtins.lobe-gtd.description": "Plan goals and track progress with GTD methodology",
"tools.builtins.lobe-gtd.readme": "Plan goals and track progress using GTD methodology. Create strategic plans, manage todo lists with status tracking, and execute long-running async tasks.",
"tools.builtins.lobe-gtd.title": "GTD Tools",
"tools.builtins.lobe-knowledge-base.description": "Search uploaded documents and domain knowledge via semantic vector search — for persistent, reusable reference",
"tools.builtins.lobe-knowledge-base.title": "Knowledge Base",
"tools.builtins.lobe-local-system.description": "Access and manage local files, run shell commands on your desktop",
"tools.builtins.lobe-local-system.readme": "Access your local filesystem on desktop. Read, write, search, and organize files. Execute shell commands with background task support and grep content with regex patterns.",
"tools.builtins.lobe-local-system.title": "Local System",
"tools.builtins.lobe-message.description": "Send, read, edit, and manage messages across multiple messaging platforms with a unified interface",
"tools.builtins.lobe-message.readme": "Cross-platform messaging tool supporting Discord, Telegram, Slack, Google Chat, and IRC. Provides unified APIs for message operations, reactions, pins, threads, channel management, and platform-specific features like polls.",
"tools.builtins.lobe-message.title": "Message",
"tools.builtins.lobe-notebook.description": "Create and manage documents in the topic notebook",
"tools.builtins.lobe-notebook.readme": "Create and manage persistent documents within conversation topics. Save notes, reports, articles, and markdown content that stays accessible across sessions.",
"tools.builtins.lobe-notebook.title": "Notebook",
"tools.builtins.lobe-page-agent.description": "Create, read, update, and delete nodes in XML-structured documents",
"tools.builtins.lobe-page-agent.readme": "Create and edit structured documents with precise node-level control. Initialize from Markdown, perform batch insert/modify/remove operations, and find-and-replace text across documents.",
"tools.builtins.lobe-page-agent.title": "Document",
"tools.builtins.lobe-remote-device.description": "Discover and manage remote desktop device connections",
"tools.builtins.lobe-remote-device.readme": "Manage connections to your desktop devices. List online devices, activate a device for remote operations, and check connection status.",
"tools.builtins.lobe-remote-device.title": "Remote Device",
"tools.builtins.lobe-skill-store.description": "Browse and install agent skills from the LobeHub marketplace. Use this when you need extended capabilities or want to install a specific skill.",
"tools.builtins.lobe-skill-store.title": "Skill Store",
"tools.builtins.lobe-skills.description": "Activate and use reusable skill packages",
"tools.builtins.lobe-skills.title": "Skills",
"tools.builtins.lobe-task.description": "Create, list, edit, and delete tasks with dependencies and review configuration",
"tools.builtins.lobe-task.title": "Task Tools",
"tools.builtins.lobe-topic-reference.description": "Retrieve context from referenced topic conversations",
"tools.builtins.lobe-topic-reference.title": "Topic Reference",
"tools.builtins.lobe-user-interaction.description": "Ask users questions through UI interactions and observe their lifecycle outcomes",
"tools.builtins.lobe-user-interaction.title": "User Interaction",
"tools.builtins.lobe-user-memory.description": "Remember user preferences, activities, and experiences across conversations",
"tools.builtins.lobe-user-memory.readme": "Build a personalized knowledge base about you. Remember preferences, track activities and experiences, store identity information, and recall relevant context in future conversations.",
"tools.builtins.lobe-user-memory.title": "Memory",
"tools.builtins.lobe-web-browsing.description": "Search the web for current information and crawl web pages to extract content. Supports multiple search engines, categories, and time ranges.",
"tools.builtins.lobe-web-browsing.readme": "Search the web for current information and crawl web pages to extract content. Supports multiple search engines, categories, and time ranges for comprehensive research.",
"tools.builtins.lobe-web-browsing.title": "Web Browsing",
"tools.builtins.lobe-web-onboarding.description": "Drive the web onboarding flow with a controlled agent runtime",
"tools.builtins.lobe-web-onboarding.title": "Web Onboarding",
"tools.builtins.lobehub.description": "Manage the LobeHub platform via CLI — knowledge bases, memory, agents, files, search, generation, and more.",
"tools.builtins.lobehub.title": "LobeHub",
"tools.builtins.notInstalled": "Not Installed",
"tools.builtins.task.description": "Task management and execution — create, track, review, and complete tasks via CLI.",
"tools.builtins.task.title": "Task",
"tools.builtins.uninstall": "Uninstall",
"tools.builtins.uninstallConfirm.desc": "Are you sure you want to uninstall {{name}}? This skill will be removed from the current agent.",
"tools.builtins.uninstallConfirm.title": "Uninstall {{name}}",
@ -950,12 +1002,16 @@
"tools.lobehubSkill.disconnectConfirm.title": "Disconnect {{name}}?",
"tools.lobehubSkill.disconnected": "Disconnected",
"tools.lobehubSkill.error": "Error",
"tools.lobehubSkill.providers.github.description": "GitHub is a platform for version control and collaboration, enabling developers to host, review, and manage code repositories.",
"tools.lobehubSkill.providers.github.readme": "Connect to GitHub to access your repositories, create and manage issues, review pull requests, and collaborate on code—all through natural conversation with your AI assistant.",
"tools.lobehubSkill.providers.linear.description": "Linear is a modern issue tracking and project management tool designed for high-performance teams to build better software faster",
"tools.lobehubSkill.providers.linear.readme": "Bring the power of Linear directly into your AI assistant. Create and update issues, manage sprints, track project progress, and streamline your development workflow—all through natural conversation.",
"tools.lobehubSkill.providers.microsoft.description": "Outlook Calendar is an integrated scheduling tool within Microsoft Outlook that enables users to create appointments, organize meetings with others, and manage their time and events effectively.",
"tools.lobehubSkill.providers.microsoft.readme": "Integrate with Outlook Calendar to view, create, and manage your events seamlessly. Schedule meetings, check availability, set reminders, and coordinate your time—all through natural language commands.",
"tools.lobehubSkill.providers.twitter.description": "X (Twitter) is a social media platform for sharing real-time updates, news, and engaging with your audience through posts, replies, and direct messages.",
"tools.lobehubSkill.providers.twitter.readme": "Connect to X (Twitter) to post tweets, manage your timeline, and engage with your audience. Create content, schedule posts, monitor mentions, and build your social media presence through conversational AI.",
"tools.lobehubSkill.providers.vercel.description": "Vercel is a cloud platform for frontend developers, providing hosting and serverless functions to deploy web applications with ease.",
"tools.lobehubSkill.providers.vercel.readme": "Connect to Vercel to manage your deployments, monitor project status, and control your infrastructure. Deploy applications, check build logs, manage environment variables, and scale your projects through conversational AI.",
"tools.notInstalled": "Not Installed",
"tools.notInstalledWarning": "This skill is not currently installed, which may affect agent functionality.",
"tools.plugins.enabled": "Enabled: {{num}}",

View file

@ -844,31 +844,83 @@
"tab.uploadZip.desc": "上传本地 .zip 或 .skill 文件",
"tab.usage": "用量",
"tools.add": "集成技能",
"tools.builtins.find-skills.description": "当用户询问“我该如何做 X”、“帮我找一个能做 X 的技能”或希望扩展能力时,帮助用户发现并安装助手技能",
"tools.builtins.find-skills.title": "查找技能",
"tools.builtins.groupName": "内置技能",
"tools.builtins.install": "安装",
"tools.builtins.installed": "已安装",
"tools.builtins.lobe-activator.description": "发现并启用工具与技能",
"tools.builtins.lobe-activator.title": "工具与技能激活器",
"tools.builtins.lobe-agent-browser.description": "面向 AI 助手的浏览器自动化命令行工具。当任务涉及网站或 Electron 交互(如导航、表单填写、点击、截图、数据抓取、登录流程和端到端应用测试)时使用。",
"tools.builtins.lobe-agent-browser.title": "助手浏览器",
"tools.builtins.lobe-agent-builder.description": "配置助手元信息、模型设置、插件以及系统提示词",
"tools.builtins.lobe-agent-builder.title": "助手构建器",
"tools.builtins.lobe-agent-documents.description": "管理助手范围内的文档(列出、创建、读取、编辑、删除、重命名)并加载规则",
"tools.builtins.lobe-agent-documents.title": "文档",
"tools.builtins.lobe-agent-management.description": "创建、管理并编排 AI 助手",
"tools.builtins.lobe-agent-management.title": "助手管理",
"tools.builtins.lobe-artifacts.description": "生成并预览交互式 UI 组件和可视化内容",
"tools.builtins.lobe-artifacts.readme": "生成并实时预览交互式 UI 组件、数据可视化、图表、SVG 图形和 Web 应用。创建用户可直接交互的丰富可视化内容。",
"tools.builtins.lobe-artifacts.title": "Artifacts",
"tools.builtins.lobe-brief.description": "汇报进度、交付结果,并请求用户决策",
"tools.builtins.lobe-brief.title": "简报工具",
"tools.builtins.lobe-calculator.description": "执行数学计算、解方程,并处理符号表达式",
"tools.builtins.lobe-calculator.readme": "高级数学计算器,支持基础算术、代数方程、微积分运算和符号数学。包括进制转换、方程求解、微分、积分等更多功能。",
"tools.builtins.lobe-calculator.title": "计算器",
"tools.builtins.lobe-cloud-sandbox.description": "在安全的云端环境中执行代码、运行命令和管理文件",
"tools.builtins.lobe-cloud-sandbox.readme": "在隔离的云端环境中执行 Python、JavaScript 和 TypeScript 代码。运行 Shell 命令、管理文件、使用正则搜索内容并安全导出结果。",
"tools.builtins.lobe-cloud-sandbox.title": "云沙盒",
"tools.builtins.lobe-creds.description": "管理用于身份验证、环境变量注入和 API 调用的用户凭据 — 处理 API 密钥、OAuth 令牌以及第三方集成所需的密钥。",
"tools.builtins.lobe-creds.title": "凭据管理",
"tools.builtins.lobe-cron.description": "管理在指定时间自动运行的定时任务。可为助手创建、更新、启用/禁用并监控周期性任务。",
"tools.builtins.lobe-cron.title": "定时任务",
"tools.builtins.lobe-group-agent-builder.description": "配置群组的元信息、成员和共享内容,用于多助手群组",
"tools.builtins.lobe-group-agent-builder.title": "群组助手构建器",
"tools.builtins.lobe-group-management.description": "编排并管理多助手群组对话",
"tools.builtins.lobe-group-management.title": "群组管理",
"tools.builtins.lobe-gtd.description": "使用 GTD 方法规划目标并追踪进度",
"tools.builtins.lobe-gtd.readme": "使用 GTD 方法规划目标并追踪进度。创建战略计划、管理带状态跟踪的待办列表,并执行长时间运行的异步任务。",
"tools.builtins.lobe-gtd.title": "GTD 工具",
"tools.builtins.lobe-knowledge-base.description": "通过语义向量检索搜索已上传的文档与领域知识 — 适用于持久、可复用的参考资料",
"tools.builtins.lobe-knowledge-base.title": "知识库",
"tools.builtins.lobe-local-system.description": "访问和管理本地文件,在桌面端运行 Shell 命令",
"tools.builtins.lobe-local-system.readme": "访问桌面端的本地文件系统。读取、写入、搜索和组织文件。执行 Shell 命令,支持后台任务和正则内容搜索。",
"tools.builtins.lobe-local-system.title": "本地系统",
"tools.builtins.lobe-message.description": "通过统一接口在多个消息平台上发送、读取、编辑和管理消息",
"tools.builtins.lobe-message.readme": "跨平台消息工具,支持 Discord、Telegram、Slack、Google Chat 和 IRC。提供统一 API 用于消息操作、表情回应、置顶、话题、频道管理以及投票等平台特定功能。",
"tools.builtins.lobe-message.title": "消息",
"tools.builtins.lobe-notebook.description": "在对话主题中创建和管理文档",
"tools.builtins.lobe-notebook.readme": "在对话主题中创建和管理持久化文档。保存笔记、报告、文章和 Markdown 内容,可跨会话持续访问。",
"tools.builtins.lobe-notebook.title": "笔记本",
"tools.builtins.lobe-page-agent.description": "在 XML 结构化文档中创建、读取、更新和删除节点",
"tools.builtins.lobe-page-agent.readme": "通过精确的节点级控制创建和编辑结构化文档。可从 Markdown 初始化、批量执行插入/修改/删除操作,并跨文档查找替换文本。",
"tools.builtins.lobe-page-agent.title": "文档编辑",
"tools.builtins.lobe-remote-device.description": "发现并管理远程桌面设备连接",
"tools.builtins.lobe-remote-device.readme": "管理与桌面设备的连接。列出在线设备、激活设备以执行远程操作并查看连接状态。",
"tools.builtins.lobe-remote-device.title": "远程设备",
"tools.builtins.lobe-skill-store.description": "从 LobeHub 技能市场浏览并安装助手技能。当你需要扩展能力或想安装特定技能时使用。",
"tools.builtins.lobe-skill-store.title": "技能商店",
"tools.builtins.lobe-skills.description": "激活并使用可复用的技能包",
"tools.builtins.lobe-skills.title": "技能",
"tools.builtins.lobe-task.description": "创建、列出、编辑和删除任务,支持依赖关系和审核配置",
"tools.builtins.lobe-task.title": "任务工具",
"tools.builtins.lobe-topic-reference.description": "从被引用的话题对话中检索上下文",
"tools.builtins.lobe-topic-reference.title": "话题引用",
"tools.builtins.lobe-user-interaction.description": "通过界面交互向用户提问,并观察其生命周期结果",
"tools.builtins.lobe-user-interaction.title": "用户交互",
"tools.builtins.lobe-user-memory.description": "记住用户的偏好、活动和经历",
"tools.builtins.lobe-user-memory.readme": "构建关于你的个性化知识库。记住偏好、追踪活动和经历、存储身份信息,并在未来对话中回忆相关上下文。",
"tools.builtins.lobe-user-memory.title": "记忆",
"tools.builtins.lobe-web-browsing.description": "搜索网页获取最新信息,并抓取网页内容。支持多种搜索引擎、分类和时间范围。",
"tools.builtins.lobe-web-browsing.readme": "搜索网页获取最新信息,并抓取网页内容。支持多种搜索引擎、分类和时间范围,满足全面的研究需求。",
"tools.builtins.lobe-web-browsing.title": "网页浏览",
"tools.builtins.lobe-web-onboarding.description": "通过受控的助手运行时驱动 Web 引导流程",
"tools.builtins.lobe-web-onboarding.title": "Web 引导",
"tools.builtins.lobehub.description": "通过命令行管理 LobeHub 平台 — 包括知识库、记忆、助手、文件、搜索、生成等。",
"tools.builtins.lobehub.title": "LobeHub",
"tools.builtins.notInstalled": "未安装",
"tools.builtins.task.description": "任务管理与执行 — 通过命令行创建、追踪、审核并完成任务。",
"tools.builtins.task.title": "任务",
"tools.builtins.uninstall": "卸载",
"tools.builtins.uninstallConfirm.desc": "确定要卸载 {{name}} 吗?此技能将从当前助手中移除。",
"tools.builtins.uninstallConfirm.title": "卸载 {{name}}",
@ -950,12 +1002,16 @@
"tools.lobehubSkill.disconnectConfirm.title": "断开 {{name}} 的连接?",
"tools.lobehubSkill.disconnected": "已断开连接",
"tools.lobehubSkill.error": "错误",
"tools.lobehubSkill.providers.github.description": "GitHub 是一个面向版本控制和协作的平台,开发者可以在此托管、审查并管理代码仓库。",
"tools.lobehubSkill.providers.github.readme": "连接 GitHub 以访问您的代码仓库,创建并管理 Issue、审查 Pull Request并通过与 AI 助手的自然对话协作开发。",
"tools.lobehubSkill.providers.linear.description": "Linear 是一款现代化的问题跟踪和项目管理工具,专为高效团队打造,助力更快构建更优软件。",
"tools.lobehubSkill.providers.linear.readme": "将 Linear 的强大功能引入您的 AI 助手。创建和更新问题、管理冲刺、跟踪项目进度,并通过自然对话优化开发流程。",
"tools.lobehubSkill.providers.microsoft.description": "Outlook 日历是 Microsoft Outlook 中集成的日程安排工具,用户可创建约会、组织会议并高效管理时间和事件。",
"tools.lobehubSkill.providers.microsoft.readme": "集成 Outlook 日历以无缝查看、创建和管理事件。安排会议、查看可用时间、设置提醒,并通过自然语言指令协调时间。",
"tools.lobehubSkill.providers.twitter.description": "X原 Twitter是一个社交媒体平台用于分享实时动态、新闻并通过推文、回复和私信与受众互动。",
"tools.lobehubSkill.providers.twitter.readme": "连接 X原 Twitter以发布推文、管理时间线并与受众互动。创建内容、安排发布、监控提及并通过对话式 AI 构建社交媒体影响力。",
"tools.lobehubSkill.providers.vercel.description": "Vercel 是一个面向前端开发者的云平台,提供托管服务和无服务器函数,可轻松部署 Web 应用。",
"tools.lobehubSkill.providers.vercel.readme": "连接 Vercel 以管理部署、监控项目状态并控制基础设施。通过对话式 AI 部署应用、查看构建日志、管理环境变量并扩展项目。",
"tools.notInstalled": "未安装",
"tools.notInstalledWarning": "当前技能暂未安装,可能会影响助理使用",
"tools.plugins.enabled": "已启用 {{num}}",

View file

@ -0,0 +1,50 @@
import { Avatar } from '@lobehub/ui';
import { memo } from 'react';
import { useDiscoverStore } from '@/store/discover';
import ToolItemDetailPopover from './ToolItemDetailPopover';
interface MarketAgentSkillPopoverContentProps {
description?: string | null;
identifier: string;
name: string;
sourceLabel: string;
}
/**
* Popover content for market-installed agent skills.
*
* Looks up the market metadata via SWR (cached) so the popover can show the
* publisher icon and the marketplace description / version when available,
* falling back to the locally stored description.
*/
const MarketAgentSkillPopoverContent = memo<MarketAgentSkillPopoverContentProps>(
({ identifier, name, description, sourceLabel }) => {
const useFetchSkillDetail = useDiscoverStore((s) => s.useFetchSkillDetail);
const { data } = useFetchSkillDetail({ identifier });
const iconSource = data?.icon || name;
return (
<ToolItemDetailPopover
description={data?.description || description || undefined}
identifier={identifier}
sourceLabel={sourceLabel}
title={data?.name || name}
icon={
<Avatar
avatar={iconSource}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
}
/>
);
},
);
MarketAgentSkillPopoverContent.displayName = 'MarketAgentSkillPopoverContent';
export default MarketAgentSkillPopoverContent;

View file

@ -0,0 +1,41 @@
import { Avatar } from '@lobehub/ui';
import { memo } from 'react';
import { useDiscoverStore } from '@/store/discover';
export const SKILL_ICON_SIZE = 20;
interface MarketSkillIconProps {
identifier: string;
name?: string;
size?: number;
}
/**
* Market agent skill icon component.
*
* Looks up the skill in the LobeHub Market via SWR (cached/dedup'd) so the
* panel displays the same publisher icon shown in the Skill Store. Falls back
* to an Avatar generated from the skill name while loading, or when the skill
* is not present in the market (e.g. user-imported skills from a custom URL).
*/
const MarketSkillIcon = memo<MarketSkillIconProps>(
({ identifier, name, size = SKILL_ICON_SIZE }) => {
const useFetchSkillDetail = useDiscoverStore((s) => s.useFetchSkillDetail);
const { data } = useFetchSkillDetail({ identifier });
return (
<Avatar
avatar={data?.icon || name || identifier}
shape={'square'}
size={size}
style={{ flex: 'none', marginInlineEnd: 0 }}
title={name}
/>
);
},
);
MarketSkillIcon.displayName = 'MarketSkillIcon';
export default MarketSkillIcon;

View file

@ -6,6 +6,7 @@ import { memo, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ScrollSignalProvider } from './ScrollSignalContext';
import SkillActivateMode from './SkillActivateMode';
import ToolsList, { toolsListStyles } from './ToolsList';
@ -81,14 +82,14 @@ const PopoverContent = memo<PopoverContentProps>(({ items, onOpenStore }) => {
/>
<SkillActivateMode />
</Flexbox>
<div
<ScrollSignalProvider
style={{
height: 480,
overflowY: 'auto',
}}
>
<ToolsList items={filteredItems} />
</div>
</ScrollSignalProvider>
<div className={styles.footer}>
<div
className={toolsListStyles.item}

View file

@ -0,0 +1,78 @@
'use client';
import {
createContext,
type CSSProperties,
type ReactNode,
use,
useCallback,
useEffect,
useRef,
} from 'react';
type ScrollSubscriber = () => void;
type Subscribe = (cb: ScrollSubscriber) => () => void;
const ScrollSignalContext = createContext<Subscribe | null>(null);
interface ScrollSignalProviderProps {
children: ReactNode;
className?: string;
style?: CSSProperties;
}
/**
* Wraps a scrollable container and broadcasts a signal to descendants whenever
* the user scrolls. Mirrors the pattern used by the model selector list so that
* any hover popover anchored to a row inside the scroller can close itself when
* its trigger has presumably moved out from under the cursor.
*/
export const ScrollSignalProvider = ({
ref,
children,
className,
style,
}: ScrollSignalProviderProps & { ref?: React.RefObject<HTMLDivElement | null> }) => {
const listenersRef = useRef<Set<ScrollSubscriber>>(new Set());
const subscribe = useCallback<Subscribe>((cb) => {
listenersRef.current.add(cb);
return () => {
listenersRef.current.delete(cb);
};
}, []);
const handleScroll = useCallback(() => {
listenersRef.current.forEach((cb) => cb());
}, []);
return (
<ScrollSignalContext value={subscribe}>
<div className={className} ref={ref} style={style} onScroll={handleScroll}>
{children}
</div>
</ScrollSignalContext>
);
};
ScrollSignalProvider.displayName = 'ScrollSignalProvider';
/**
* Subscribe to scroll events of the nearest ScrollSignalProvider ancestor.
* If no provider is present this is a no-op.
*/
export const useScrollSignal = (cb: ScrollSubscriber) => {
const subscribe = use(ScrollSignalContext);
// Re-attach when the callback identity changes.
const cbRef = useRef(cb);
useEffect(() => {
cbRef.current = cb;
}, [cb]);
useEffect(() => {
if (!subscribe) return;
return subscribe(() => cbRef.current());
}, [subscribe]);
};
export type { Subscribe as ScrollSignalSubscribe };

View file

@ -2,16 +2,11 @@ import { Flexbox, Text } from '@lobehub/ui';
import { memo, Suspense } from 'react';
import DebugNode from '@/components/DebugNode';
import PluginTag from '@/components/Plugins/PluginTag';
import { useToolStore } from '@/store/tool';
import { customPluginSelectors } from '@/store/tool/selectors';
import type { CheckboxItemProps } from '../components/CheckboxWithLoading';
import CheckboxItem from '../components/CheckboxWithLoading';
const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
const isCustom = useToolStore((s) => customPluginSelectors.isCustomPlugin(id)(s));
return (
<Suspense fallback={<DebugNode trace="ActionBar/Tools/ToolItem" />}>
<CheckboxItem
@ -28,7 +23,6 @@ const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
>
{label || id}
</Text>
{isCustom && <PluginTag showText={false} type={'customPlugin'} />}
</Flexbox>
}
onUpdate={onUpdate}

View file

@ -0,0 +1,80 @@
import { Flexbox, Tag, Text } from '@lobehub/ui';
import { createStaticStyles, cssVar } from 'antd-style';
import { memo, type ReactNode } from 'react';
const styles = createStaticStyles(({ css }) => ({
container: css`
width: 320px;
padding: 12px;
`,
description: css`
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 6;
font-size: 12px;
line-height: 1.5;
color: ${cssVar.colorTextSecondary};
`,
identifier: css`
overflow: hidden;
font-family: ${cssVar.fontFamilyCode};
font-size: 11px;
color: ${cssVar.colorTextTertiary};
text-overflow: ellipsis;
white-space: nowrap;
`,
title: css`
font-size: 14px;
font-weight: 600;
color: ${cssVar.colorText};
`,
}));
interface ToolItemDetailPopoverProps {
description?: ReactNode;
icon?: ReactNode;
identifier?: string;
meta?: ReactNode;
sourceLabel?: string;
title: ReactNode;
}
/**
* Hover popover content for items rendered in the skill panel.
* Mirrors the structure of ModelDetailPanel but kept lightweight: a header
* row (icon + title + source tag), a description block, and an optional
* identifier line for technical reference.
*/
const ToolItemDetailPopover = memo<ToolItemDetailPopoverProps>(
({ icon, title, description, sourceLabel, identifier, meta }) => {
return (
<Flexbox className={styles.container} gap={10}>
<Flexbox horizontal align={'center'} gap={10}>
{icon}
<Flexbox flex={1} gap={2} style={{ minWidth: 0 }}>
<Flexbox horizontal align={'center'} gap={6}>
<Text ellipsis className={styles.title}>
{title}
</Text>
{sourceLabel && (
<Tag size={'small'} style={{ flexShrink: 0 }}>
{sourceLabel}
</Tag>
)}
</Flexbox>
{identifier && <span className={styles.identifier}>{identifier}</span>}
</Flexbox>
</Flexbox>
{description && <div className={styles.description}>{description}</div>}
{meta}
</Flexbox>
);
},
);
ToolItemDetailPopover.displayName = 'ToolItemDetailPopover';
export default ToolItemDetailPopover;

View file

@ -1,9 +1,11 @@
import type { ItemType } from '@lobehub/ui';
import { Flexbox, Icon, Text } from '@lobehub/ui';
import { Flexbox, Icon, Popover, Text } from '@lobehub/ui';
import { Divider } from 'antd';
import { createStaticStyles, cssVar } from 'antd-style';
import type { ReactNode } from 'react';
import { Fragment, isValidElement, memo } from 'react';
import { Fragment, isValidElement, memo, useCallback, useState } from 'react';
import { useScrollSignal } from './ScrollSignalContext';
export const toolsListStyles = createStaticStyles(({ css }) => ({
groupLabel: css`
@ -49,6 +51,12 @@ interface ToolItemData {
key?: string;
label?: ReactNode;
onClick?: () => void;
/**
* Optional rich content shown in a hover popover for this row.
* When set, the row is wrapped with a Popover triggered on hover, similar
* to the model selector's detail popover.
*/
popoverContent?: ReactNode;
type?: 'group' | 'divider';
}
@ -61,6 +69,16 @@ const DividerItem = memo<{ index: number }>(({ index }) => (
));
const RegularItem = memo<{ index: number; item: ToolItemData }>(({ item, index }) => {
const [open, setOpen] = useState(false);
// Close hover popover whenever the surrounding list scrolls — avoids the
// detail panel hovering in mid-air after its anchor row has moved away.
useScrollSignal(
useCallback(() => {
setOpen(false);
}, []),
);
const iconNode = item.icon ? (
isValidElement(item.icon) ? (
item.icon
@ -69,7 +87,7 @@ const RegularItem = memo<{ index: number; item: ToolItemData }>(({ item, index }
)
) : null;
return (
const row = (
<div
className={toolsListStyles.item}
key={item.key || `item-${index}`}
@ -82,6 +100,23 @@ const RegularItem = memo<{ index: number; item: ToolItemData }>(({ item, index }
{item.extra}
</div>
);
if (!item.popoverContent) return row;
return (
<Popover
arrow={false}
content={item.popoverContent}
mouseEnterDelay={0.3}
open={open}
placement={'rightTop'}
positionerProps={{ sideOffset: 8 }}
styles={{ content: { padding: 0 } }}
onOpenChange={setOpen}
>
{row}
</Popover>
);
});
const GroupItem = memo<{ index: number; item: ToolItemData }>(({ item, index }) => (

View file

@ -30,7 +30,10 @@ import KlavisServerItem from './KlavisServerItem';
import KlavisSkillIcon from './KlavisSkillIcon';
import LobehubSkillIcon from './LobehubSkillIcon';
import LobehubSkillServerItem from './LobehubSkillServerItem';
import MarketAgentSkillPopoverContent from './MarketAgentSkillPopoverContent';
import MarketSkillIcon from './MarketSkillIcon';
import ToolItem from './ToolItem';
import ToolItemDetailPopover from './ToolItemDetailPopover';
const SKILL_ICON_SIZE = 20;
@ -172,9 +175,20 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
serverName={type.serverName}
/>
),
popoverContent: (
<ToolItemDetailPopover
icon={<KlavisSkillIcon icon={type.icon} label={type.label} size={36} />}
identifier={type.identifier}
sourceLabel={type.author}
title={type.label}
description={t(`tools.klavis.servers.${type.identifier}.description` as any, {
defaultValue: type.description,
})}
/>
),
}))
: [],
[isKlavisEnabledInEnv, allKlavisServers, installedKlavisIds, recommendedKlavisIds, agentId],
[isKlavisEnabledInEnv, allKlavisServers, installedKlavisIds, recommendedKlavisIds, agentId, t],
);
// LobeHub Skill Provider list items - only show installed or recommended
@ -200,6 +214,17 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
provider={provider.id}
/>
),
popoverContent: (
<ToolItemDetailPopover
icon={<LobehubSkillIcon icon={provider.icon} label={provider.label} size={36} />}
identifier={provider.id}
sourceLabel={provider.author}
title={provider.label}
description={t(`tools.lobehubSkill.providers.${provider.id}.description` as any, {
defaultValue: provider.description,
})}
/>
),
}))
: [],
[
@ -208,6 +233,7 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
installedLobehubIds,
recommendedLobehubIds,
agentId,
t,
],
);
@ -236,8 +262,28 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${item.identifier}.description` as any, {
defaultValue: item.meta?.description || '',
})}
icon={
<Avatar
avatar={item.meta.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
}
title={t(`tools.builtins.${item.identifier}.title` as any, {
defaultValue: item.meta?.title || item.identifier,
})}
/>
),
})),
[filteredBuiltinList, checked, togglePlugin, setUpdating],
[filteredBuiltinList, checked, togglePlugin, setUpdating, t],
);
// Builtin Agent Skills list items (grouped under LobeHub)
@ -262,15 +308,41 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${skill.identifier}.description` as any, {
defaultValue: skill.description,
})}
icon={
skill.avatar ? (
<Avatar
avatar={skill.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
) : (
<Icon icon={SkillsIcon} size={36} />
)
}
title={t(`tools.builtins.${skill.identifier}.title` as any, {
defaultValue: skill.name,
})}
/>
),
})),
[installedBuiltinSkills, checked, togglePlugin, setUpdating],
[installedBuiltinSkills, checked, togglePlugin, setUpdating, t],
);
// Market Agent Skills list items (grouped under Community)
const marketAgentSkillItems = useMemo(
() =>
marketAgentSkills.map((skill) => ({
icon: <Icon icon={SkillsIcon} size={SKILL_ICON_SIZE} />,
icon: (
<MarketSkillIcon identifier={skill.identifier} name={skill.name} size={SKILL_ICON_SIZE} />
),
key: skill.identifier,
label: (
<ToolItem
@ -284,8 +356,16 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<MarketAgentSkillPopoverContent
description={skill.description}
identifier={skill.identifier}
name={skill.name}
sourceLabel={t('skillStore.tabs.community')}
/>
),
})),
[marketAgentSkills, checked, togglePlugin, setUpdating],
[marketAgentSkills, checked, togglePlugin, setUpdating, t],
);
// User Agent Skills list items (grouped under Custom)
@ -306,8 +386,17 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={skill.description}
icon={<Icon icon={SkillsIcon} size={36} />}
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={skill.name}
/>
),
})),
[userAgentSkills, checked, togglePlugin, setUpdating],
[userAgentSkills, checked, togglePlugin, setUpdating, t],
);
// Skills list items (including LobeHub Skill and Klavis)
@ -350,27 +439,50 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
const customPlugins = list.filter((item) => item.type === 'customPlugin');
// Function to map plugins to list items
const mapPluginToItem = (item: (typeof list)[0]) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
const mapPluginToItem = (item: (typeof list)[0]) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
const isCustom = item.type === 'customPlugin';
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={checked.includes(item.identifier)}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
});
key: item.identifier,
label: (
<ToolItem
checked={checked.includes(item.identifier)}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={isCustom ? t('skillStore.tabs.custom') : t('skillStore.tabs.community')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
};
// Build LobeHub group children (including Builtin Agent Skills, builtin tools, and LobeHub Skill/Klavis)
const lobehubGroupChildren: ItemType[] = [
@ -460,6 +572,26 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${item.identifier}.description` as any, {
defaultValue: item.meta?.description || '',
})}
icon={
<Avatar
avatar={item.meta.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
}
title={t(`tools.builtins.${item.identifier}.title` as any, {
defaultValue: item.meta?.title || item.identifier,
})}
/>
),
}));
// Connected Klavis servers
@ -497,6 +629,30 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${skill.identifier}.description` as any, {
defaultValue: skill.description,
})}
icon={
skill.avatar ? (
<Avatar
avatar={skill.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
) : (
<Icon icon={SkillsIcon} size={36} />
)
}
title={t(`tools.builtins.${skill.identifier}.title` as any, {
defaultValue: skill.name,
})}
/>
),
}));
// Build builtin tools group children (including Builtin Agent Skills, builtin tools, and LobeHub Skill/Klavis)
@ -525,58 +681,104 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
// Enabled community plugins
const enabledCommunityPlugins = communityPlugins
.filter((item) => checked.includes(item.identifier))
.map((item) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
.map((item) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
}));
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.community')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
});
// Enabled custom plugins
const enabledCustomPlugins = customPlugins
.filter((item) => checked.includes(item.identifier))
.map((item) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
.map((item) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
}));
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
});
// Enabled Market Agent Skills
const enabledMarketAgentSkillItems = marketAgentSkills
.filter((skill) => checked.includes(skill.identifier))
.map((skill) => ({
icon: <Icon icon={SkillsIcon} size={SKILL_ICON_SIZE} />,
icon: (
<MarketSkillIcon identifier={skill.identifier} name={skill.name} size={SKILL_ICON_SIZE} />
),
key: skill.identifier,
label: (
<ToolItem
@ -590,6 +792,14 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<MarketAgentSkillPopoverContent
description={skill.description}
identifier={skill.identifier}
name={skill.name}
sourceLabel={t('skillStore.tabs.community')}
/>
),
}));
// Community group (Market Agent Skills + community plugins)
@ -621,6 +831,15 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={skill.description}
icon={<Icon icon={SkillsIcon} size={36} />}
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={skill.name}
/>
),
}));
// Custom group (User Agent Skills + custom plugins)

View file

@ -17,7 +17,10 @@ import KlavisSkillIcon, {
} from '@/features/ChatInput/ActionBar/Tools/KlavisSkillIcon';
import LobehubSkillIcon from '@/features/ChatInput/ActionBar/Tools/LobehubSkillIcon';
import LobehubSkillServerItem from '@/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem';
import MarketAgentSkillPopoverContent from '@/features/ChatInput/ActionBar/Tools/MarketAgentSkillPopoverContent';
import MarketSkillIcon from '@/features/ChatInput/ActionBar/Tools/MarketSkillIcon';
import ToolItem from '@/features/ChatInput/ActionBar/Tools/ToolItem';
import ToolItemDetailPopover from '@/features/ChatInput/ActionBar/Tools/ToolItemDetailPopover';
import { createSkillStoreModal } from '@/features/SkillStore';
import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
@ -266,9 +269,20 @@ const AgentTool = memo<AgentToolProps>(
serverName={type.serverName}
/>
),
popoverContent: (
<ToolItemDetailPopover
icon={<KlavisSkillIcon icon={type.icon} label={type.label} size={36} />}
identifier={type.identifier}
sourceLabel={type.author}
title={type.label}
description={t(`tools.klavis.servers.${type.identifier}.description` as any, {
defaultValue: type.description,
})}
/>
),
}))
: [],
[isKlavisEnabledInEnv, allKlavisServers, effectiveAgentId],
[isKlavisEnabledInEnv, allKlavisServers, effectiveAgentId, t],
);
// LobeHub Skill Provider list items
@ -291,9 +305,20 @@ const AgentTool = memo<AgentToolProps>(
provider={provider.id}
/>
),
popoverContent: (
<ToolItemDetailPopover
icon={<LobehubSkillIcon icon={provider.icon} label={provider.label} size={36} />}
identifier={provider.id}
sourceLabel={provider.author}
title={provider.label}
description={t(`tools.lobehubSkill.providers.${provider.id}.description` as any, {
defaultValue: provider.description,
})}
/>
),
}))
: [],
[isLobehubSkillEnabled, allLobehubSkillServers, effectiveAgentId],
[isLobehubSkillEnabled, allLobehubSkillServers, effectiveAgentId, t],
);
// Handle plugin remove via Tag close - use byId actions
@ -335,15 +360,44 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${skill.identifier}.description` as any, {
defaultValue: skill.description,
})}
icon={
skill.avatar ? (
<Avatar
avatar={skill.avatar}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
) : (
<Icon icon={SkillsIcon} size={36} />
)
}
title={t(`tools.builtins.${skill.identifier}.title` as any, {
defaultValue: skill.name,
})}
/>
),
})),
[installedBuiltinSkills, isToolEnabled, handleToggleTool],
[installedBuiltinSkills, isToolEnabled, handleToggleTool, t],
);
// Market Agent Skills list items (grouped under Community)
const marketAgentSkillItems = useMemo(
() =>
marketAgentSkills.map((skill) => ({
icon: <Icon icon={SkillsIcon} size={SKILL_ICON_SIZE} />,
icon: (
<MarketSkillIcon
identifier={skill.identifier}
name={skill.name}
size={SKILL_ICON_SIZE}
/>
),
key: skill.identifier,
label: (
<ToolItem
@ -357,8 +411,16 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<MarketAgentSkillPopoverContent
description={skill.description}
identifier={skill.identifier}
name={skill.name}
sourceLabel={t('skillStore.tabs.community')}
/>
),
})),
[marketAgentSkills, isToolEnabled, handleToggleTool],
[marketAgentSkills, isToolEnabled, handleToggleTool, t],
);
// User Agent Skills list items (grouped under Custom)
@ -379,8 +441,17 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={skill.description}
icon={<Icon icon={SkillsIcon} size={36} />}
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={skill.name}
/>
),
})),
[userAgentSkills, isToolEnabled, handleToggleTool],
[userAgentSkills, isToolEnabled, handleToggleTool, t],
);
// Merge Builtin Agent Skills, builtin tools, LobeHub Skill Providers, and Klavis servers
@ -410,6 +481,25 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${item.identifier}.description` as any, {
defaultValue: item.meta?.description || '',
})}
icon={
<Avatar
avatar={item.meta.avatar}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
}
title={t(`tools.builtins.${item.identifier}.title` as any, {
defaultValue: item.meta?.title || item.identifier,
})}
/>
),
})),
// 3. LobeHub Skill Providers
...lobehubSkillItems,
@ -423,6 +513,7 @@ const AgentTool = memo<AgentToolProps>(
lobehubSkillItems,
isToolEnabled,
handleToggleTool,
t,
],
);
@ -432,28 +523,51 @@ const AgentTool = memo<AgentToolProps>(
// Function to generate plugin list items
const mapPluginToItem = useCallback(
(item: (typeof installedPluginList)[0]) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
(item: (typeof installedPluginList)[0]) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
const isCustom = item.type === 'customPlugin';
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={plugins.includes(item.identifier)}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
}),
[plugins, togglePlugin],
key: item.identifier,
label: (
<ToolItem
checked={plugins.includes(item.identifier)}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={isCustom ? t('skillStore.tabs.custom') : t('skillStore.tabs.community')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
},
[plugins, togglePlugin, t],
);
// Community plugin list items
@ -548,6 +662,25 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
description={t(`tools.builtins.${item.identifier}.description` as any, {
defaultValue: item.meta?.description || '',
})}
icon={
<Avatar
avatar={item.meta.avatar}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
}
title={t(`tools.builtins.${item.identifier}.title` as any, {
defaultValue: item.meta?.title || item.identifier,
})}
/>
),
}));
// Connected and enabled Klavis servers
@ -582,6 +715,25 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={skill.description}
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.lobehub')}
title={skill.name}
icon={
skill.avatar ? (
<Avatar
avatar={skill.avatar}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
) : (
<Icon icon={SkillsIcon} size={36} />
)
}
/>
),
}));
// LobeHub group (Builtin Agent Skills + builtin + LobeHub Skill + Klavis)
@ -604,33 +756,61 @@ const AgentTool = memo<AgentToolProps>(
// Enabled community plugins
const enabledCommunityPlugins = communityPlugins
.filter((item) => plugins.includes(item.identifier))
.map((item) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
.map((item) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
}));
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.community')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
});
// Enabled Market Agent Skills
const enabledMarketAgentSkillItems = marketAgentSkills
.filter((skill) => isToolEnabled(skill.identifier))
.map((skill) => ({
icon: <Icon icon={SkillsIcon} size={SKILL_ICON_SIZE} />,
icon: (
<MarketSkillIcon
identifier={skill.identifier}
name={skill.name}
size={SKILL_ICON_SIZE}
/>
),
key: skill.identifier,
label: (
<ToolItem
@ -644,6 +824,14 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<MarketAgentSkillPopoverContent
description={skill.description}
identifier={skill.identifier}
name={skill.name}
sourceLabel={t('skillStore.tabs.community')}
/>
),
}));
// Community group (Market Agent Skills + community plugins)
@ -660,27 +848,49 @@ const AgentTool = memo<AgentToolProps>(
// Enabled custom plugins
const enabledCustomPlugins = customPlugins
.filter((item) => plugins.includes(item.identifier))
.map((item) => ({
icon:
item?.avatar === 'MCP_AVATAR' || !item?.avatar ? (
.map((item) => {
const isMcp = item?.avatar === 'MCP_AVATAR' || !item?.avatar;
return {
icon: isMcp ? (
<Icon icon={McpIcon} size={SKILL_ICON_SIZE} />
) : (
<Avatar avatar={item.avatar} shape={'square'} size={SKILL_ICON_SIZE} />
),
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
}));
key: item.identifier,
label: (
<ToolItem
checked={true}
id={item.identifier}
label={item.title}
onUpdate={async () => {
setUpdating(true);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={item.description}
identifier={item.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={item.title}
icon={
isMcp ? (
<Icon icon={McpIcon} size={36} />
) : (
<Avatar
avatar={item.avatar}
shape={'square'}
size={36}
style={{ flex: 'none', marginInlineEnd: 0 }}
/>
)
}
/>
),
};
});
// Enabled User Agent Skills
const enabledUserAgentSkillItems = userAgentSkills
@ -700,6 +910,15 @@ const AgentTool = memo<AgentToolProps>(
}}
/>
),
popoverContent: (
<ToolItemDetailPopover
description={skill.description}
icon={<Icon icon={SkillsIcon} size={36} />}
identifier={skill.identifier}
sourceLabel={t('skillStore.tabs.custom')}
title={skill.name}
/>
),
}));
// Custom group (User Agent Skills + custom plugins)

View file

@ -6,6 +6,7 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ScrollSignalProvider } from '@/features/ChatInput/ActionBar/Tools/ScrollSignalContext';
import ToolsList, { toolsListStyles } from '@/features/ChatInput/ActionBar/Tools/ToolsList';
import Empty from './Empty';
@ -69,13 +70,13 @@ const PopoverContent = memo<PopoverContentProps>(
onChange={(v) => onTabChange(v as TabType)}
/>
</div>
<div className={styles.scroller} style={{ flex: 1 }}>
<ScrollSignalProvider className={styles.scroller} style={{ flex: 1 }}>
{activeTab === 'installed' && installedTabItems.length === 0 ? (
<Empty />
) : (
<ToolsList items={currentItems} />
)}
</div>
</ScrollSignalProvider>
<div className={styles.footer}>
<div className={toolsListStyles.item} role="button" tabIndex={0} onClick={onOpenStore}>
<div className={toolsListStyles.itemIcon}>

View file

@ -1001,6 +1001,85 @@ When I am ___, I need ___
'tools.builtins.lobe-user-memory.readme':
'Build a personalized knowledge base about you. Remember preferences, track activities and experiences, store identity information, and recall relevant context in future conversations.',
'tools.builtins.lobe-user-memory.title': 'Memory',
// ===== Additional Builtin Tools =====
'tools.builtins.lobe-activator.description': 'Discover and activate tools and skills',
'tools.builtins.lobe-activator.title': 'Tools & Skills Activator',
'tools.builtins.lobe-agent-builder.description':
'Configure agent metadata, model settings, plugins, and the system prompt',
'tools.builtins.lobe-agent-builder.title': 'Agent Builder',
'tools.builtins.lobe-agent-documents.description':
'Manage agent-scoped documents (list, create, read, edit, remove, rename) and load rules',
'tools.builtins.lobe-agent-documents.title': 'Documents',
'tools.builtins.lobe-agent-management.description': 'Create, manage, and orchestrate AI agents',
'tools.builtins.lobe-agent-management.title': 'Agent Management',
'tools.builtins.lobe-brief.description':
'Report progress, deliver results, and request user decisions',
'tools.builtins.lobe-brief.title': 'Brief Tools',
'tools.builtins.lobe-creds.description':
'Manage user credentials for authentication, environment variable injection, and API verification — handle API keys, OAuth tokens, and secrets for third-party integrations.',
'tools.builtins.lobe-creds.title': 'Credentials',
'tools.builtins.lobe-cron.description':
'Manage scheduled tasks that run automatically at specified times. Create, update, enable/disable, and monitor recurring tasks for your agents.',
'tools.builtins.lobe-cron.title': 'Scheduled Tasks',
'tools.builtins.lobe-group-agent-builder.description':
'Configure group metadata, members, and shared content for multi-agent groups',
'tools.builtins.lobe-group-agent-builder.title': 'Group Agent Builder',
'tools.builtins.lobe-group-management.description':
'Orchestrate and manage multi-agent group conversations',
'tools.builtins.lobe-group-management.title': 'Group Management',
'tools.builtins.lobe-knowledge-base.description':
'Search uploaded documents and domain knowledge via semantic vector search — for persistent, reusable reference',
'tools.builtins.lobe-knowledge-base.title': 'Knowledge Base',
'tools.builtins.lobe-message.description':
'Send, read, edit, and manage messages across multiple messaging platforms with a unified interface',
'tools.builtins.lobe-message.readme':
'Cross-platform messaging tool supporting Discord, Telegram, Slack, Google Chat, and IRC. Provides unified APIs for message operations, reactions, pins, threads, channel management, and platform-specific features like polls.',
'tools.builtins.lobe-message.title': 'Message',
'tools.builtins.lobe-page-agent.description':
'Create, read, update, and delete nodes in XML-structured documents',
'tools.builtins.lobe-page-agent.readme':
'Create and edit structured documents with precise node-level control. Initialize from Markdown, perform batch insert/modify/remove operations, and find-and-replace text across documents.',
'tools.builtins.lobe-page-agent.title': 'Document',
'tools.builtins.lobe-remote-device.description':
'Discover and manage remote desktop device connections',
'tools.builtins.lobe-remote-device.readme':
'Manage connections to your desktop devices. List online devices, activate a device for remote operations, and check connection status.',
'tools.builtins.lobe-remote-device.title': 'Remote Device',
'tools.builtins.lobe-skill-store.description':
'Browse and install agent skills from the LobeHub marketplace. Use this when you need extended capabilities or want to install a specific skill.',
'tools.builtins.lobe-skill-store.title': 'Skill Store',
'tools.builtins.lobe-skills.description': 'Activate and use reusable skill packages',
'tools.builtins.lobe-skills.title': 'Skills',
'tools.builtins.lobe-task.description':
'Create, list, edit, and delete tasks with dependencies and review configuration',
'tools.builtins.lobe-task.title': 'Task Tools',
'tools.builtins.lobe-topic-reference.description':
'Retrieve context from referenced topic conversations',
'tools.builtins.lobe-topic-reference.title': 'Topic Reference',
'tools.builtins.lobe-user-interaction.description':
'Ask users questions through UI interactions and observe their lifecycle outcomes',
'tools.builtins.lobe-user-interaction.title': 'User Interaction',
'tools.builtins.lobe-web-browsing.description':
'Search the web for current information and crawl web pages to extract content. Supports multiple search engines, categories, and time ranges.',
'tools.builtins.lobe-web-browsing.readme':
'Search the web for current information and crawl web pages to extract content. Supports multiple search engines, categories, and time ranges for comprehensive research.',
'tools.builtins.lobe-web-browsing.title': 'Web Browsing',
'tools.builtins.lobe-web-onboarding.description':
'Drive the web onboarding flow with a controlled agent runtime',
'tools.builtins.lobe-web-onboarding.title': 'Web Onboarding',
// ===== Builtin Agent Skills =====
'tools.builtins.find-skills.description':
'Helps users discover and install agent skills when they ask "how do I do X", "find a skill for X", or want to extend capabilities',
'tools.builtins.find-skills.title': 'Find Skills',
'tools.builtins.lobe-agent-browser.description':
'Browser automation CLI for AI agents. Use when tasks involve website or Electron interaction such as navigation, form filling, clicking, screenshot capture, scraping data, login flows, and end-to-end app testing.',
'tools.builtins.lobe-agent-browser.title': 'Agent Browser',
'tools.builtins.lobehub.description':
'Manage the LobeHub platform via CLI — knowledge bases, memory, agents, files, search, generation, and more.',
'tools.builtins.lobehub.title': 'LobeHub',
'tools.builtins.task.description':
'Task management and execution — create, track, review, and complete tasks via CLI.',
'tools.builtins.task.title': 'Task',
'tools.builtins.notInstalled': 'Not Installed',
'tools.builtins.uninstall': 'Uninstall',
'tools.builtins.uninstallConfirm.desc':
@ -1184,6 +1263,10 @@ When I am ___, I need ___
'tools.lobehubSkill.error': 'Error',
// LobeHub Skill Providers i18n
'tools.lobehubSkill.providers.github.description':
'GitHub is a platform for version control and collaboration, enabling developers to host, review, and manage code repositories.',
'tools.lobehubSkill.providers.github.readme':
'Connect to GitHub to access your repositories, create and manage issues, review pull requests, and collaborate on code—all through natural conversation with your AI assistant.',
'tools.lobehubSkill.providers.linear.description':
'Linear is a modern issue tracking and project management tool designed for high-performance teams to build better software faster',
'tools.lobehubSkill.providers.linear.readme':
@ -1196,6 +1279,10 @@ When I am ___, I need ___
'X (Twitter) is a social media platform for sharing real-time updates, news, and engaging with your audience through posts, replies, and direct messages.',
'tools.lobehubSkill.providers.twitter.readme':
'Connect to X (Twitter) to post tweets, manage your timeline, and engage with your audience. Create content, schedule posts, monitor mentions, and build your social media presence through conversational AI.',
'tools.lobehubSkill.providers.vercel.description':
'Vercel is a cloud platform for frontend developers, providing hosting and serverless functions to deploy web applications with ease.',
'tools.lobehubSkill.providers.vercel.readme':
'Connect to Vercel to manage your deployments, monitor project status, and control your infrastructure. Deploy applications, check build logs, manage environment variables, and scale your projects through conversational AI.',
'tools.notInstalled': 'Not Installed',
'tools.notInstalledWarning':