- {content}
+ {hasSkillTags
+ ? contentNodes.map((node, i) =>
+ node.type === 'text' ? (
+ {node.value}
+ ) : (
+
+ )
+ )
+ : content}
{attaches && attaches.length > 0 && (
diff --git a/src/components/ChatBox/index.tsx b/src/components/ChatBox/index.tsx
index 495b2fcd9..5734d3727 100644
--- a/src/components/ChatBox/index.tsx
+++ b/src/components/ChatBox/index.tsx
@@ -156,8 +156,9 @@ export default function ChatBox(): JSX.Element {
// .catch((err) => console.error("Failed to fetch settings:", err));
// }
// }, [privacyDialogOpen]);
- const [searchParams] = useSearchParams();
+ const [searchParams, setSearchParams] = useSearchParams();
const share_token = searchParams.get('share_token');
+ const skill_prompt = searchParams.get('skill_prompt');
const [loading, setLoading] = useState(false);
const [isReplayLoading, setIsReplayLoading] = useState(false);
@@ -353,6 +354,17 @@ export default function ChatBox(): JSX.Element {
}
}, [share_token, isConfigLoaded, isPrivacyLoaded, handleSendShare]);
+ // Handle skill_prompt from URL - pre-fill message when navigating from Skills page
+ useEffect(() => {
+ if (skill_prompt) {
+ setMessage(skill_prompt);
+ // Clear the skill_prompt param from URL after setting the message
+ const newSearchParams = new URLSearchParams(searchParams);
+ newSearchParams.delete('skill_prompt');
+ setSearchParams(newSearchParams, { replace: true });
+ }
+ }, [skill_prompt, searchParams, setSearchParams]);
+
useEffect(() => {
if (!chatStore) return;
console.log('ChatStore Data: ', chatStore);
diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx
index e016821b9..ff3f0e157 100644
--- a/src/components/Navigation/index.tsx
+++ b/src/components/Navigation/index.tsx
@@ -58,11 +58,11 @@ export function VerticalNavigation({
value={value}
defaultValue={initial}
onValueChange={onValueChange}
- className={cn('flex w-full gap-4', className)}
+ className={cn('flex-1 w-full', className)}
>
diff --git a/src/components/SearchInput/index.tsx b/src/components/SearchInput/index.tsx
index 2a1c5cf1d..b74f60758 100644
--- a/src/components/SearchInput/index.tsx
+++ b/src/components/SearchInput/index.tsx
@@ -12,24 +12,161 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
-import { Search } from 'lucide-react';
+import { TooltipSimple } from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
+import { AnimatePresence, motion } from 'framer-motion';
+import { Search, X } from 'lucide-react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
+export type SearchInputVariant = 'default' | 'icon';
+
interface SearchInputProps {
value: string;
onChange: (e: React.ChangeEvent) => void;
+ placeholder?: string;
+ variant?: SearchInputVariant;
+ /** Optional: called when user presses Enter in the field (e.g. to submit search) */
+ onSearch?: () => void;
+ /** Tooltip for the search icon button (icon variant). Defaults to agents.search-tooltip */
+ searchTooltip?: string;
+ /** Tooltip for the clear (X) button (icon variant). Defaults to agents.clear-search-tooltip */
+ clearTooltip?: string;
}
-export default function SearchInput({ value, onChange }: SearchInputProps) {
+const COLLAPSED_WIDTH = 40;
+const EXPANDED_WIDTH = 240;
+
+export default function SearchInput({
+ value,
+ onChange,
+ placeholder,
+ variant = 'default',
+ onSearch,
+ searchTooltip,
+ clearTooltip,
+}: SearchInputProps) {
const { t } = useTranslation();
+ const inputRef = useRef(null);
+ const [userExpanded, setUserExpanded] = useState(false);
+ const isExpanded = userExpanded || value.length > 0;
+
+ const expand = useCallback(() => {
+ setUserExpanded(true);
+ }, []);
+
+ const collapse = useCallback(() => {
+ setUserExpanded(false);
+ onChange({ target: { value: '' } } as React.ChangeEvent);
+ }, [onChange]);
+
+ useEffect(() => {
+ if (userExpanded && inputRef.current) {
+ const id = requestAnimationFrame(() => {
+ inputRef.current?.focus();
+ });
+ return () => cancelAnimationFrame(id);
+ }
+ }, [userExpanded]);
+
+ const searchLabel = searchTooltip ?? t('agents.search-tooltip');
+ const clearLabel = clearTooltip ?? t('agents.clear-search-tooltip');
+ const place = placeholder ?? t('setting.search-mcp');
+
+ if (variant === 'icon') {
+ return (
+
+
+ {!isExpanded ? (
+
+
+
+
+
+ ) : (
+
+
+
+
+ {
+ if (value.length === 0) setUserExpanded(false);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ onSearch?.();
+ }
+ }}
+ className="h-6 min-w-0 flex-1 bg-transparent pl-2 text-label-sm text-text-heading outline-none placeholder:text-text-label"
+ />
+
+
+
+
+ )}
+
+
+ );
+ }
+
return (
}
/>
diff --git a/src/components/WorkFlow/agents.tsx b/src/components/WorkFlow/agents.tsx
new file mode 100644
index 000000000..0e62d337a
--- /dev/null
+++ b/src/components/WorkFlow/agents.tsx
@@ -0,0 +1,106 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import { Bird, CodeXml, FileText, Globe, Image } from 'lucide-react';
+import type { ReactNode } from 'react';
+
+export type WorkflowAgentType =
+ | 'developer_agent'
+ | 'browser_agent'
+ | 'document_agent'
+ | 'multi_modal_agent'
+ | 'social_media_agent';
+
+export interface AgentDisplayInfo {
+ name: string;
+ icon: ReactNode;
+ textColor: string;
+ bgColor: string;
+ shapeColor: string;
+ borderColor: string;
+ bgColorLight: string;
+}
+
+export const agentMap: Record = {
+ developer_agent: {
+ name: 'Developer Agent',
+ icon: ,
+ textColor: 'text-text-developer',
+ bgColor: 'bg-bg-fill-coding-active',
+ shapeColor: 'bg-bg-fill-coding-default',
+ borderColor: 'border-bg-fill-coding-active',
+ bgColorLight: 'bg-emerald-200',
+ },
+ browser_agent: {
+ name: 'Browser Agent',
+ icon: ,
+ textColor: 'text-blue-700',
+ bgColor: 'bg-bg-fill-browser-active',
+ shapeColor: 'bg-bg-fill-browser-default',
+ borderColor: 'border-bg-fill-browser-active',
+ bgColorLight: 'bg-blue-200',
+ },
+ document_agent: {
+ name: 'Document Agent',
+ icon: ,
+ textColor: 'text-yellow-700',
+ bgColor: 'bg-bg-fill-writing-active',
+ shapeColor: 'bg-bg-fill-writing-default',
+ borderColor: 'border-bg-fill-writing-active',
+ bgColorLight: 'bg-yellow-200',
+ },
+ multi_modal_agent: {
+ name: 'Multi Modal Agent',
+ icon: ,
+ textColor: 'text-fuchsia-700',
+ bgColor: 'bg-bg-fill-multimodal-active',
+ shapeColor: 'bg-bg-fill-multimodal-default',
+ borderColor: 'border-bg-fill-multimodal-active',
+ bgColorLight: 'bg-fuchsia-200',
+ },
+ social_media_agent: {
+ name: 'Social Media Agent',
+ icon: ,
+ textColor: 'text-purple-700',
+ bgColor: 'bg-violet-700',
+ shapeColor: 'bg-violet-300',
+ borderColor: 'border-violet-700',
+ bgColorLight: 'bg-purple-50',
+ },
+};
+
+/** Ordered list of workflow agents (name + icon) for use in skill scope and elsewhere. */
+export const WORKFLOW_AGENT_LIST: { name: string; icon: ReactNode }[] = [
+ { name: agentMap.developer_agent.name, icon: agentMap.developer_agent.icon },
+ { name: agentMap.browser_agent.name, icon: agentMap.browser_agent.icon },
+ { name: agentMap.document_agent.name, icon: agentMap.document_agent.icon },
+ {
+ name: agentMap.multi_modal_agent.name,
+ icon: agentMap.multi_modal_agent.icon,
+ },
+ {
+ name: agentMap.social_media_agent.name,
+ icon: agentMap.social_media_agent.icon,
+ },
+];
+
+/** Get display info (name + icon) by agent name; returns undefined if not a workflow agent. */
+export function getWorkflowAgentDisplay(
+ agentName: string
+): { name: string; icon: ReactNode } | undefined {
+ const entry = WORKFLOW_AGENT_LIST.find(
+ (a) => a.name.toLowerCase() === agentName.toLowerCase()
+ );
+ return entry;
+}
diff --git a/src/components/WorkFlow/node.tsx b/src/components/WorkFlow/node.tsx
index eafa42941..288cce69d 100644
--- a/src/components/WorkFlow/node.tsx
+++ b/src/components/WorkFlow/node.tsx
@@ -23,17 +23,12 @@ import {
} from '@/types/constants';
import { Handle, NodeResizer, Position, useReactFlow } from '@xyflow/react';
import {
- Bird,
Bot,
Circle,
CircleCheckBig,
CircleSlash,
CircleSlash2,
- CodeXml,
Ellipsis,
- FileText,
- Globe,
- Image,
LoaderCircle,
SquareChevronLeft,
SquareCode,
@@ -52,6 +47,7 @@ import {
} from '../ui/popover';
import ShinyText from '../ui/ShinyText/ShinyText';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
+import { agentMap } from './agents';
import { MarkDown } from './MarkDown';
interface NodeProps {
@@ -295,54 +291,6 @@ export function Node({ id, data }: NodeProps) {
data.onExpandChange(id, !isExpanded);
};
- const agentMap = {
- developer_agent: {
- name: 'Developer Agent',
- icon: ,
- textColor: 'text-text-developer',
- bgColor: 'bg-bg-fill-coding-active',
- shapeColor: 'bg-bg-fill-coding-default',
- borderColor: 'border-bg-fill-coding-active',
- bgColorLight: 'bg-emerald-200',
- },
- browser_agent: {
- name: 'Browser Agent',
- icon: ,
- textColor: 'text-blue-700',
- bgColor: 'bg-bg-fill-browser-active',
- shapeColor: 'bg-bg-fill-browser-default',
- borderColor: 'border-bg-fill-browser-active',
- bgColorLight: 'bg-blue-200',
- },
- document_agent: {
- name: 'Document Agent',
- icon: ,
- textColor: 'text-yellow-700',
- bgColor: 'bg-bg-fill-writing-active',
- shapeColor: 'bg-bg-fill-writing-default',
- borderColor: 'border-bg-fill-writing-active',
- bgColorLight: 'bg-yellow-200',
- },
- multi_modal_agent: {
- name: 'Multi Modal Agent',
- icon: ,
- textColor: 'text-fuchsia-700',
- bgColor: 'bg-bg-fill-multimodal-active',
- shapeColor: 'bg-bg-fill-multimodal-default',
- borderColor: 'border-bg-fill-multimodal-active',
- bgColorLight: 'bg-fuchsia-200',
- },
- social_media_agent: {
- name: 'Social Media Agent',
- icon: ,
- textColor: 'text-purple-700',
- bgColor: 'bg-violet-700',
- shapeColor: 'bg-violet-300',
- borderColor: 'border-violet-700',
- bgColorLight: 'bg-purple-50',
- },
- };
-
const agentToolkits = {
developer_agent: [
'# Terminal & Shell ',
diff --git a/src/components/ui/alertDialog.tsx b/src/components/ui/alertDialog.tsx
index f5bd4f900..354952ad7 100644
--- a/src/components/ui/alertDialog.tsx
+++ b/src/components/ui/alertDialog.tsx
@@ -55,7 +55,8 @@ export default function ConfirmModal({
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
- className="bg-white/5 z-100 alert-dialog fixed inset-0"
+ className="alert-dialog fixed inset-0 z-[99] bg-black/20"
+ style={{ backgroundColor: 'rgba(0, 0, 0, 0.2)' }}
onClick={onClose}
/>
@@ -64,9 +65,9 @@ export default function ConfirmModal({
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
- className="alert-dialog-wrapper fixed max-w-md rounded-xl shadow-perfect"
+ className="alert-dialog-wrapper fixed left-1/2 top-1/2 z-[100] max-w-md rounded-xl -translate-x-1/2 -translate-y-1/2"
>
-
+
{title}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index d45c6c749..240b074c4 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -46,6 +46,8 @@ const DialogOverlay = React.forwardRef<
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+export type DialogOverlayVariant = 'default' | 'dark';
+
// Size variants for dialog content
const dialogContentVariants = cva(
'fixed left-[50%] top-[50%] z-50 grid w-full translate-x-[-50%] translate-y-[-50%] gap-0 border border-solid border-popup-border bg-popup-bg shadow-perfect duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-xl',
@@ -72,6 +74,8 @@ interface DialogContentProps
closeButtonClassName?: string;
closeButtonIcon?: React.ReactNode;
onClose?: () => void;
+ /** Overlay behind the dialog: 'default' (transparent) or 'dark' (black overlay) */
+ overlayVariant?: DialogOverlayVariant;
}
const DialogContent = React.forwardRef<
@@ -87,15 +91,27 @@ const DialogContent = React.forwardRef<
closeButtonClassName,
closeButtonIcon,
onClose,
+ overlayVariant = 'default',
...props
},
ref
) => (
-
+
{children}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
index f081dfc23..77d93166e 100644
--- a/src/components/ui/tabs.tsx
+++ b/src/components/ui/tabs.tsx
@@ -13,56 +13,190 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import * as TabsPrimitive from '@radix-ui/react-tabs';
+import { AnimatePresence, motion } from 'framer-motion';
import * as React from 'react';
import { cn } from '@/lib/utils';
+// Context for variant
+const TabsContext = React.createContext<{ variant?: 'default' | 'outline' }>({
+ variant: 'default',
+});
+
const Tabs = TabsPrimitive.Root;
+type TabsListProps = React.ComponentPropsWithoutRef<
+ typeof TabsPrimitive.List
+> & {
+ variant?: 'default' | 'outline';
+};
+
const TabsList = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
+ TabsListProps
+>(({ className, variant = 'default', ...props }, ref) => {
+ const tabsListRef = React.useRef | null>(null) as React.MutableRefObject | null>;
+ const [sliderStyle, setSliderStyle] = React.useState({ left: 0, width: 0 });
+
+ // Update slider position when active tab changes
+ React.useLayoutEffect(() => {
+ if (variant !== 'outline' || !tabsListRef.current) return;
+
+ const updateSlider = () => {
+ // Use requestAnimationFrame to ensure DOM has updated
+ requestAnimationFrame(() => {
+ const activeTab = tabsListRef.current?.querySelector(
+ '[data-state="active"][data-variant="outline"]'
+ ) as HTMLElement;
+
+ if (activeTab && tabsListRef.current) {
+ const containerRect = tabsListRef.current.getBoundingClientRect();
+ const tabRect = activeTab.getBoundingClientRect();
+
+ setSliderStyle({
+ left: tabRect.left - containerRect.left,
+ width: tabRect.width,
+ });
+ }
+ });
+ };
+
+ // Initial update
+ updateSlider();
+
+ // Watch for changes
+ const observer = new MutationObserver(updateSlider);
+ if (tabsListRef.current) {
+ observer.observe(tabsListRef.current, {
+ attributes: true,
+ attributeFilter: ['data-state'],
+ subtree: true,
+ });
+ }
+
+ // Also listen for resize
+ window.addEventListener('resize', updateSlider);
+
+ return () => {
+ observer.disconnect();
+ window.removeEventListener('resize', updateSlider);
+ };
+ }, [variant]);
+
+ const combinedRef = React.useCallback(
+ (node: React.ElementRef | null) => {
+ if (typeof ref === 'function') {
+ ref(node);
+ } else if (ref && 'current' in ref) {
+ (
+ ref as React.MutableRefObject | null>
+ ).current = node;
+ }
+ tabsListRef.current = node;
+ },
+ [ref]
+ );
+
+ return (
+
+
+
+ {variant === 'outline' && sliderStyle.width > 0 && (
+
+ )}
+
+
+ );
+});
TabsList.displayName = TabsPrimitive.List.displayName;
+type TabsTriggerProps = React.ComponentPropsWithoutRef<
+ typeof TabsPrimitive.Trigger
+> & {
+ variant?: 'default' | 'outline';
+};
+
const TabsTrigger = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
+ TabsTriggerProps
+>(({ className, variant: propVariant, ...props }, ref) => {
+ const { variant: contextVariant } = React.useContext(TabsContext);
+ const variant = propVariant || contextVariant || 'default';
+
+ return (
+
+ );
+});
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
+>(({ className, children, ...props }, ref) => {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+});
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsContent, TabsList, TabsTrigger };
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
index fa4275ff1..a50e9b3fb 100644
--- a/src/components/ui/toggle-group.tsx
+++ b/src/components/ui/toggle-group.tsx
@@ -59,6 +59,7 @@ const ToggleGroupItem = React.forwardRef<
variant: context.variant || variant,
size: context.size || size,
}),
+ 'bg-surface-primary border-border-disabled data-[state=on]:bg-surface-tertiary data-[state=on]:border-border-secondary',
className
)}
{...props}
diff --git a/src/i18n/locales/ar/agents.json b/src/i18n/locales/ar/agents.json
new file mode 100644
index 000000000..521624d12
--- /dev/null
+++ b/src/i18n/locales/ar/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "المهارات",
+ "memory": "الذاكرة",
+ "preview": "معاينة",
+ "skills-description": "أضف مهارات مخصصة لتوسيع قدرات الوكيل الخاص بك.",
+ "memory-description": "إدارة ذاكرة الوكيل وقاعدة المعرفة الخاصة به.",
+ "memory-coming-soon-description": "ستتيح ميزات الذاكرة لوكلائك تذكر المعلومات المهمة عبر الجلسات.",
+ "learn-more": "معرفة المزيد",
+ "search-skills": "البحث عن المهارات...",
+ "search-tooltip": "بحث",
+ "clear-search-tooltip": "مسح البحث",
+ "add": "إضافة",
+ "your-skills": "مهاراتك",
+ "example-skills": "مهارات نموذجية",
+ "no-skills-found": "لم يتم العثور على مهارات مطابقة.",
+ "no-your-skills": "لم تقم بإضافة أي مهارات بعد.",
+ "no-example-skills": "لا توجد مهارات نموذجية متاحة.",
+ "add-your-first-skill": "أضف مهارتك الأولى",
+ "added": "مضاف",
+ "global": "عام",
+ "partial-available": "متاح جزئياً (اختيار متعدد)",
+ "select-agents": "اختر الوكلاء",
+ "select-scope": "اختر النطاق",
+ "skill-scope": "نطاق المهارة",
+ "selected": "محدد",
+ "agents": "الوكلاء",
+ "no-agents-available": "لا يوجد وكلاء متاحون",
+ "try-in-chat": "جرب في المحادثة",
+ "add-skill": "إضافة مهارة",
+ "drag-and-drop": "اسحب وأفلت الملف هنا",
+ "or-click-to-browse": "أو انقر للاستعراض",
+ "file-requirements": "متطلبات الملف:",
+ "file-requirements-detail-1": "يجب أن يتضمن ملف .zip أو حزمة المهارات المرفوعة ملف SKILL.md في الدليل الجذر.",
+ "file-requirements-detail-2": "يجب أن يحدد ملف SKILL.md اسم المهارة والوصف باستخدام تنسيق YAML.",
+ "supported-formats": "التنسيقات المدعومة",
+ "max-file-size": "الحد الأقصى لحجم الملف",
+ "upload": "رفع",
+ "invalid-file-type": "نوع ملف غير صالح. يرجى رفع ملف .skill أو .md أو .txt أو .json.",
+ "file-too-large": "الملف كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت.",
+ "file-read-error": "فشل في قراءة الملف. يرجى المحاولة مرة أخرى.",
+ "reupload-file": "إعادة رفع ملف",
+ "upload-error-invalid-format": "يجب أن يكون الملف .zip أو حزمة مهارة (.skill أو .md).",
+ "upload-error-invalid-yaml": "يجب أن يحدد SKILL.md الاسم والوصف بتنسيق YAML.",
+ "skill-added-success": "تمت إضافة المهارة بنجاح!",
+ "skill-add-error": "فشل في إضافة المهارة. يرجى المحاولة مرة أخرى.",
+ "custom-skill": "مهارة مخصصة",
+ "delete-skill": "حذف المهارة",
+ "delete-skill-confirmation": "هل أنت متأكد من أنك تريد حذف \"{{name}}\"؟ لا يمكن التراجع عن هذا الإجراء.",
+ "skill-deleted-success": "تم حذف المهارة بنجاح!"
+}
diff --git a/src/i18n/locales/ar/index.ts b/src/i18n/locales/ar/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/ar/index.ts
+++ b/src/i18n/locales/ar/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/ar/layout.json b/src/i18n/locales/ar/layout.json
index 75f758ab6..56ab973ce 100644
--- a/src/i18n/locales/ar/layout.json
+++ b/src/i18n/locales/ar/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "فشل التثبيت",
"projects": "المشاريع",
"mcp-tools": "MCP والأدوات",
+ "connectors": "الموصلات",
"browser": "المتصفح",
"settings": "الإعدادات",
+ "general": "عام",
"workers": "العمال",
"triggers": "المحفزات",
"new-project": "مشروع جديد",
@@ -166,5 +168,6 @@
"days-ago": "أيام مضت",
"delete-project": "حذف المشروع",
"delete-project-confirmation": "هل أنت متأكد من أنك تريد حذف هذا المشروع وجميع مهامه؟ لا يمكن التراجع عن هذا الإجراء.",
- "please-select-model": "يرجى اختيار نموذج في الإعدادات > النماذج للمتابعة."
+ "please-select-model": "يرجى اختيار نموذج في الإعدادات > النماذج للمتابعة.",
+ "capabilities": "القدرات"
}
diff --git a/src/i18n/locales/de/agents.json b/src/i18n/locales/de/agents.json
new file mode 100644
index 000000000..18bc2dd25
--- /dev/null
+++ b/src/i18n/locales/de/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Fähigkeiten",
+ "memory": "Speicher",
+ "preview": "Vorschau",
+ "skills-description": "Fügen Sie benutzerdefinierte Fähigkeiten hinzu, um die Funktionen Ihres Agenten zu erweitern.",
+ "memory-description": "Verwalten Sie den Speicher und die Wissensbasis Ihres Agenten.",
+ "memory-coming-soon-description": "Speicherfunktionen ermöglichen es Ihren Agenten, wichtige Informationen zwischen Sitzungen zu speichern.",
+ "learn-more": "Mehr erfahren",
+ "search-skills": "Fähigkeiten suchen...",
+ "search-tooltip": "Suchen",
+ "clear-search-tooltip": "Suche löschen",
+ "add": "Hinzufügen",
+ "your-skills": "Ihre Fähigkeiten",
+ "example-skills": "Beispiel-Fähigkeiten",
+ "no-skills-found": "Keine passenden Fähigkeiten gefunden.",
+ "no-your-skills": "Sie haben noch keine Fähigkeiten hinzugefügt.",
+ "no-example-skills": "Keine Beispiel-Fähigkeiten verfügbar.",
+ "add-your-first-skill": "Fügen Sie Ihre erste Fähigkeit hinzu",
+ "added": "Hinzugefügt",
+ "global": "Global",
+ "partial-available": "Teilweise verfügbar (Mehrfachauswahl)",
+ "select-agents": "Agenten auswählen",
+ "select-scope": "Bereich auswählen",
+ "skill-scope": "Fähigkeitsbereich",
+ "selected": "ausgewählt",
+ "agents": "Agenten",
+ "no-agents-available": "Keine Agenten verfügbar",
+ "try-in-chat": "Im Chat ausprobieren",
+ "add-skill": "Fähigkeit hinzufügen",
+ "drag-and-drop": "Datei hierher ziehen",
+ "or-click-to-browse": "oder klicken zum Durchsuchen",
+ "file-requirements": "Dateianforderungen:",
+ "file-requirements-detail-1": "Das hochgeladene .zip oder Skill-Paket muss eine SKILL.md Datei im Stammverzeichnis enthalten.",
+ "file-requirements-detail-2": "Die SKILL.md Datei muss den Skill-Namen und die Beschreibung im YAML-Format definieren.",
+ "supported-formats": "Unterstützte Formate",
+ "max-file-size": "Maximale Dateigröße",
+ "upload": "Hochladen",
+ "invalid-file-type": "Ungültiger Dateityp. Bitte laden Sie eine .skill, .md, .txt oder .json Datei hoch.",
+ "file-too-large": "Datei ist zu groß. Maximale Größe ist 1MB.",
+ "file-read-error": "Datei konnte nicht gelesen werden. Bitte versuchen Sie es erneut.",
+ "reupload-file": "Datei erneut hochladen",
+ "upload-error-invalid-format": "Datei muss ein .zip- oder Skill-Paket (.skill oder .md) sein.",
+ "upload-error-invalid-yaml": "SKILL.md muss name und description im YAML-Format definieren.",
+ "skill-added-success": "Fähigkeit erfolgreich hinzugefügt!",
+ "skill-add-error": "Fähigkeit konnte nicht hinzugefügt werden. Bitte versuchen Sie es erneut.",
+ "custom-skill": "Benutzerdefinierte Fähigkeit",
+ "delete-skill": "Fähigkeit löschen",
+ "delete-skill-confirmation": "Sind Sie sicher, dass Sie \"{{name}}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
+ "skill-deleted-success": "Fähigkeit erfolgreich gelöscht!"
+}
diff --git a/src/i18n/locales/de/index.ts b/src/i18n/locales/de/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/de/index.ts
+++ b/src/i18n/locales/de/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/de/layout.json b/src/i18n/locales/de/layout.json
index 4cc133adb..4e1b9f8b3 100644
--- a/src/i18n/locales/de/layout.json
+++ b/src/i18n/locales/de/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "Installation fehlgeschlagen",
"projects": "Projekte",
"mcp-tools": "MCP & Tools",
+ "connectors": "Konnektoren",
"browser": "Browser",
"settings": "Einstellungen",
+ "general": "Allgemein",
"workers": "Mitarbeiter",
"triggers": "Trigger",
"new-project": "Neues Projekt",
@@ -166,5 +168,6 @@
"days-ago": "Tage zuvor",
"delete-project": "Projekt löschen",
"delete-project-confirmation": "Sind Sie sicher, dass Sie dieses Projekt und alle seine Aufgaben löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
- "please-select-model": "Bitte wählen Sie ein Modell unter Einstellungen > Modelle aus, um fortzufahren."
+ "please-select-model": "Bitte wählen Sie ein Modell unter Einstellungen > Modelle aus, um fortzufahren.",
+ "capabilities": "Fähigkeiten"
}
diff --git a/src/i18n/locales/en-us/agents.json b/src/i18n/locales/en-us/agents.json
new file mode 100644
index 000000000..13f215331
--- /dev/null
+++ b/src/i18n/locales/en-us/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Skills",
+ "memory": "Memory",
+ "preview": "Preview",
+ "skills-description": "Add custom skills to extend your agent's capabilities.",
+ "memory-description": "Manage your agent's memory and knowledge base.",
+ "memory-coming-soon-description": "Memory features will allow your agents to remember important information across sessions.",
+ "learn-more": "Learn more",
+ "search-skills": "Search skills...",
+ "search-tooltip": "Search",
+ "clear-search-tooltip": "Clear search",
+ "add": "Add",
+ "your-skills": "Your skills",
+ "example-skills": "Example skills",
+ "no-skills-found": "No skills found matching your search.",
+ "no-your-skills": "You haven't added any skills yet.",
+ "no-example-skills": "No example skills available.",
+ "add-your-first-skill": "Add your first skill",
+ "added": "Added",
+ "global": "Global",
+ "partial-available": "Partial Available (Multi-select)",
+ "select-agents": "Select agents",
+ "select-scope": "Select scope",
+ "skill-scope": "Skill Scope",
+ "selected": "selected",
+ "agents": "Agents",
+ "no-agents-available": "No agents available",
+ "try-in-chat": "Try in chat",
+ "add-skill": "Add Skill",
+ "drag-and-drop": "Drag and drop your file here",
+ "or-click-to-browse": "or click to browse",
+ "file-requirements": "File requirements:",
+ "file-requirements-detail-1": "The uploaded .zip or skill package must include a SKILL.md file located in the root directory.",
+ "file-requirements-detail-2": "The SKILL.md file must define the skill name and description using YAML format.",
+ "supported-formats": "Supported formats",
+ "max-file-size": "Maximum file size",
+ "upload": "Upload",
+ "invalid-file-type": "Invalid file type. Please upload a .skill, .md, .txt, or .json file.",
+ "file-too-large": "File is too large. Maximum size is 1MB.",
+ "file-read-error": "Failed to read file. Please try again.",
+ "reupload-file": "Click to reupload a file",
+ "upload-error-invalid-format": "File must be a .zip or skill package (.skill or .md).",
+ "upload-error-invalid-yaml": "SKILL.md must define name and description using YAML format.",
+ "skill-added-success": "Skill added successfully!",
+ "skill-add-error": "Failed to add skill. Please try again.",
+ "custom-skill": "Custom skill",
+ "delete-skill": "Delete Skill",
+ "delete-skill-confirmation": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
+ "skill-deleted-success": "Skill deleted successfully!"
+}
diff --git a/src/i18n/locales/en-us/index.ts b/src/i18n/locales/en-us/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/en-us/index.ts
+++ b/src/i18n/locales/en-us/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/en-us/layout.json b/src/i18n/locales/en-us/layout.json
index 786103b67..3f6c609da 100644
--- a/src/i18n/locales/en-us/layout.json
+++ b/src/i18n/locales/en-us/layout.json
@@ -32,8 +32,10 @@
"backend-startup-failed": "Backend Startup Failed",
"projects": "Projects",
"mcp-tools": "MCP & Tools",
+ "connectors": "Connectors",
"browser": "Browser",
"settings": "Settings",
+ "general": "General",
"workers": "Workers",
"triggers": "Triggers",
"new-project": "New Project",
@@ -168,5 +170,6 @@
"days-ago": "days ago",
"delete-project": "Delete Project",
"delete-project-confirmation": "Are you sure you want to delete this project and all its tasks? This action cannot be undone.",
- "please-select-model": "Please select a model in Settings > Models to continue."
+ "please-select-model": "Please select a model in Settings > Models to continue.",
+ "capabilities": "Capabilities"
}
diff --git a/src/i18n/locales/en-us/setting.json b/src/i18n/locales/en-us/setting.json
index f04446e47..7b3a2c220 100644
--- a/src/i18n/locales/en-us/setting.json
+++ b/src/i18n/locales/en-us/setting.json
@@ -417,5 +417,6 @@
"preferred-ide": "Preferred IDE",
"preferred-ide-description": "Choose which application to use when opening agent project folders.",
- "system-file-manager": "System File Manager"
+ "system-file-manager": "System File Manager",
+ "agents": "Agents"
}
diff --git a/src/i18n/locales/es/agents.json b/src/i18n/locales/es/agents.json
new file mode 100644
index 000000000..c16ad29b1
--- /dev/null
+++ b/src/i18n/locales/es/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Habilidades",
+ "memory": "Memoria",
+ "preview": "Vista previa",
+ "skills-description": "Agregue habilidades personalizadas para ampliar las capacidades de su agente.",
+ "memory-description": "Administre la memoria y la base de conocimientos de su agente.",
+ "memory-coming-soon-description": "Las funciones de memoria permitirán que sus agentes recuerden información importante entre sesiones.",
+ "learn-more": "Más información",
+ "search-skills": "Buscar habilidades...",
+ "search-tooltip": "Buscar",
+ "clear-search-tooltip": "Borrar búsqueda",
+ "add": "Agregar",
+ "your-skills": "Sus habilidades",
+ "example-skills": "Habilidades de ejemplo",
+ "no-skills-found": "No se encontraron habilidades coincidentes.",
+ "no-your-skills": "Aún no ha agregado ninguna habilidad.",
+ "no-example-skills": "No hay habilidades de ejemplo disponibles.",
+ "add-your-first-skill": "Agregue su primera habilidad",
+ "added": "Agregado",
+ "global": "Global",
+ "partial-available": "Parcialmente disponible (selección múltiple)",
+ "select-agents": "Seleccionar agentes",
+ "select-scope": "Seleccionar alcance",
+ "skill-scope": "Alcance de habilidad",
+ "selected": "seleccionados",
+ "agents": "Agentes",
+ "no-agents-available": "No hay agentes disponibles",
+ "try-in-chat": "Probar en chat",
+ "add-skill": "Agregar habilidad",
+ "drag-and-drop": "Arrastre y suelte el archivo aquí",
+ "or-click-to-browse": "o haga clic para explorar",
+ "file-requirements": "Requisitos del archivo:",
+ "file-requirements-detail-1": "El .zip o paquete de habilidades cargado debe incluir un archivo SKILL.md en el directorio raíz.",
+ "file-requirements-detail-2": "El archivo SKILL.md debe definir el nombre y la descripción de la habilidad usando formato YAML.",
+ "supported-formats": "Formatos admitidos",
+ "max-file-size": "Tamaño máximo del archivo",
+ "upload": "Cargar",
+ "invalid-file-type": "Tipo de archivo no válido. Por favor cargue un archivo .skill, .md, .txt o .json.",
+ "file-too-large": "El archivo es demasiado grande. El tamaño máximo es 1MB.",
+ "file-read-error": "Error al leer el archivo. Por favor intente de nuevo.",
+ "reupload-file": "Volver a subir un archivo",
+ "upload-error-invalid-format": "El archivo debe ser un .zip o paquete de habilidad (.skill o .md).",
+ "upload-error-invalid-yaml": "SKILL.md debe definir name y description en formato YAML.",
+ "skill-added-success": "¡Habilidad agregada exitosamente!",
+ "skill-add-error": "Error al agregar la habilidad. Por favor intente de nuevo.",
+ "custom-skill": "Habilidad personalizada",
+ "delete-skill": "Eliminar habilidad",
+ "delete-skill-confirmation": "¿Está seguro de que desea eliminar \"{{name}}\"? Esta acción no se puede deshacer.",
+ "skill-deleted-success": "¡Habilidad eliminada exitosamente!"
+}
diff --git a/src/i18n/locales/es/index.ts b/src/i18n/locales/es/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/es/index.ts
+++ b/src/i18n/locales/es/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/es/layout.json b/src/i18n/locales/es/layout.json
index 3f700d514..db8414254 100644
--- a/src/i18n/locales/es/layout.json
+++ b/src/i18n/locales/es/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "Error al instalar",
"projects": "Proyectos",
"mcp-tools": "MCP & Herramientas",
+ "connectors": "Conectores",
"browser": "Navegador",
"settings": "Ajustes",
+ "general": "General",
"workers": "Trabajadores",
"triggers": "Disparadores",
"new-project": "Nuevo Proyecto",
@@ -166,5 +168,6 @@
"days-ago": "días atrás",
"delete-project": "Eliminar Proyecto",
"delete-project-confirmation": "¿Estás seguro de que quieres eliminar este proyecto y todas sus tareas? Esta acción no se puede deshacer.",
- "please-select-model": "Por favor, selecciona un modelo en Configuración > Modelos para continuar."
+ "please-select-model": "Por favor, selecciona un modelo en Configuración > Modelos para continuar.",
+ "capabilities": "Capacidades"
}
diff --git a/src/i18n/locales/fr/agents.json b/src/i18n/locales/fr/agents.json
new file mode 100644
index 000000000..45a457600
--- /dev/null
+++ b/src/i18n/locales/fr/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Compétences",
+ "memory": "Mémoire",
+ "preview": "Aperçu",
+ "skills-description": "Ajoutez des compétences personnalisées pour étendre les capacités de votre agent.",
+ "memory-description": "Gérez la mémoire et la base de connaissances de votre agent.",
+ "memory-coming-soon-description": "Les fonctionnalités de mémoire permettront à vos agents de mémoriser des informations importantes entre les sessions.",
+ "learn-more": "En savoir plus",
+ "search-skills": "Rechercher des compétences...",
+ "search-tooltip": "Rechercher",
+ "clear-search-tooltip": "Effacer la recherche",
+ "add": "Ajouter",
+ "your-skills": "Vos compétences",
+ "example-skills": "Exemples de compétences",
+ "no-skills-found": "Aucune compétence correspondante trouvée.",
+ "no-your-skills": "Vous n'avez pas encore ajouté de compétences.",
+ "no-example-skills": "Aucun exemple de compétence disponible.",
+ "add-your-first-skill": "Ajoutez votre première compétence",
+ "added": "Ajouté",
+ "global": "Global",
+ "partial-available": "Partiellement disponible (sélection multiple)",
+ "select-agents": "Sélectionner les agents",
+ "select-scope": "Sélectionner la portée",
+ "skill-scope": "Portée de la compétence",
+ "selected": "sélectionnés",
+ "agents": "Agents",
+ "no-agents-available": "Aucun agent disponible",
+ "try-in-chat": "Essayer dans le chat",
+ "add-skill": "Ajouter une compétence",
+ "drag-and-drop": "Glissez-déposez votre fichier ici",
+ "or-click-to-browse": "ou cliquez pour parcourir",
+ "file-requirements": "Exigences du fichier :",
+ "file-requirements-detail-1": "Le .zip ou le package de compétences téléchargé doit inclure un fichier SKILL.md situé dans le répertoire racine.",
+ "file-requirements-detail-2": "Le fichier SKILL.md doit définir le nom et la description de la compétence au format YAML.",
+ "supported-formats": "Formats pris en charge",
+ "max-file-size": "Taille maximale du fichier",
+ "upload": "Télécharger",
+ "invalid-file-type": "Type de fichier non valide. Veuillez télécharger un fichier .skill, .md, .txt ou .json.",
+ "file-too-large": "Le fichier est trop volumineux. La taille maximale est de 1 Mo.",
+ "file-read-error": "Échec de la lecture du fichier. Veuillez réessayer.",
+ "reupload-file": "Téléverser à nouveau un fichier",
+ "upload-error-invalid-format": "Le fichier doit être un .zip ou un package de compétence (.skill ou .md).",
+ "upload-error-invalid-yaml": "SKILL.md doit définir name et description au format YAML.",
+ "skill-added-success": "Compétence ajoutée avec succès !",
+ "skill-add-error": "Échec de l'ajout de la compétence. Veuillez réessayer.",
+ "custom-skill": "Compétence personnalisée",
+ "delete-skill": "Supprimer la compétence",
+ "delete-skill-confirmation": "Êtes-vous sûr de vouloir supprimer \"{{name}}\" ? Cette action est irréversible.",
+ "skill-deleted-success": "Compétence supprimée avec succès !"
+}
diff --git a/src/i18n/locales/fr/index.ts b/src/i18n/locales/fr/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/fr/index.ts
+++ b/src/i18n/locales/fr/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/fr/layout.json b/src/i18n/locales/fr/layout.json
index d2415b021..4ad84d348 100644
--- a/src/i18n/locales/fr/layout.json
+++ b/src/i18n/locales/fr/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "Échec de l'installation",
"projects": "Projets",
"mcp-tools": "MCP & Outils",
+ "connectors": "Connecteurs",
"browser": "Navigateur",
"settings": "Paramètres",
+ "general": "Général",
"workers": "Travailleurs",
"triggers": "Déclencheurs",
"new-project": "Nouveau Projet",
@@ -166,5 +168,6 @@
"days-ago": "jours auparavant",
"delete-project": "Supprimer le Projet",
"delete-project-confirmation": "Êtes-vous sûr de vouloir supprimer ce projet et toutes ses tâches ? Cette action ne peut pas être annulée.",
- "please-select-model": "Veuillez sélectionner un modèle dans Paramètres > Modèles pour continuer."
+ "please-select-model": "Veuillez sélectionner un modèle dans Paramètres > Modèles pour continuer.",
+ "capabilities": "Capacités"
}
diff --git a/src/i18n/locales/it/agents.json b/src/i18n/locales/it/agents.json
new file mode 100644
index 000000000..b9855f585
--- /dev/null
+++ b/src/i18n/locales/it/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Competenze",
+ "memory": "Memoria",
+ "preview": "Anteprima",
+ "skills-description": "Aggiungi competenze personalizzate per estendere le capacità del tuo agente.",
+ "memory-description": "Gestisci la memoria e la base di conoscenza del tuo agente.",
+ "memory-coming-soon-description": "Le funzionalità di memoria permetteranno ai tuoi agenti di ricordare informazioni importanti tra le sessioni.",
+ "learn-more": "Scopri di più",
+ "search-skills": "Cerca competenze...",
+ "search-tooltip": "Cerca",
+ "clear-search-tooltip": "Cancella ricerca",
+ "add": "Aggiungi",
+ "your-skills": "Le tue competenze",
+ "example-skills": "Competenze di esempio",
+ "no-skills-found": "Nessuna competenza corrispondente trovata.",
+ "no-your-skills": "Non hai ancora aggiunto competenze.",
+ "no-example-skills": "Nessuna competenza di esempio disponibile.",
+ "add-your-first-skill": "Aggiungi la tua prima competenza",
+ "added": "Aggiunto",
+ "global": "Globale",
+ "partial-available": "Parzialmente disponibile (selezione multipla)",
+ "select-agents": "Seleziona agenti",
+ "select-scope": "Seleziona ambito",
+ "skill-scope": "Ambito competenza",
+ "selected": "selezionati",
+ "agents": "Agenti",
+ "no-agents-available": "Nessun agente disponibile",
+ "try-in-chat": "Prova in chat",
+ "add-skill": "Aggiungi competenza",
+ "drag-and-drop": "Trascina e rilascia il file qui",
+ "or-click-to-browse": "o clicca per sfogliare",
+ "file-requirements": "Requisiti del file:",
+ "file-requirements-detail-1": "Il .zip o il pacchetto di competenze caricato deve includere un file SKILL.md nella directory principale.",
+ "file-requirements-detail-2": "Il file SKILL.md deve definire il nome e la descrizione della competenza usando il formato YAML.",
+ "supported-formats": "Formati supportati",
+ "max-file-size": "Dimensione massima del file",
+ "upload": "Carica",
+ "invalid-file-type": "Tipo di file non valido. Carica un file .skill, .md, .txt o .json.",
+ "file-too-large": "Il file è troppo grande. La dimensione massima è 1MB.",
+ "file-read-error": "Impossibile leggere il file. Riprova.",
+ "reupload-file": "Carica di nuovo un file",
+ "upload-error-invalid-format": "Il file deve essere un .zip o un pacchetto skill (.skill o .md).",
+ "upload-error-invalid-yaml": "SKILL.md deve definire name e description in formato YAML.",
+ "skill-added-success": "Competenza aggiunta con successo!",
+ "skill-add-error": "Impossibile aggiungere la competenza. Riprova.",
+ "custom-skill": "Competenza personalizzata",
+ "delete-skill": "Elimina competenza",
+ "delete-skill-confirmation": "Sei sicuro di voler eliminare \"{{name}}\"? Questa azione non può essere annullata.",
+ "skill-deleted-success": "Competenza eliminata con successo!"
+}
diff --git a/src/i18n/locales/it/index.ts b/src/i18n/locales/it/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/it/index.ts
+++ b/src/i18n/locales/it/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/it/layout.json b/src/i18n/locales/it/layout.json
index 1b5b365d0..75af7991e 100644
--- a/src/i18n/locales/it/layout.json
+++ b/src/i18n/locales/it/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "Installazione fallita",
"projects": "Progetti",
"mcp-tools": "MCP e Strumenti",
+ "connectors": "Connettori",
"browser": "Navigatore",
"settings": "Impostazioni",
+ "general": "Generale",
"workers": "Lavoratori",
"triggers": "Trigger",
"new-project": "Nuovo Progetto",
@@ -166,5 +168,6 @@
"days-ago": "giorni fa",
"delete-project": "Elimina Progetto",
"delete-project-confirmation": "Sei sicuro di voler eliminare questo progetto e tutte le sue attività? Questa azione non può essere annullata.",
- "please-select-model": "Seleziona un modello in Impostazioni > Modelli per continuare."
+ "please-select-model": "Seleziona un modello in Impostazioni > Modelli per continuare.",
+ "capabilities": "Capacità"
}
diff --git a/src/i18n/locales/ja/agents.json b/src/i18n/locales/ja/agents.json
new file mode 100644
index 000000000..eef61ac5a
--- /dev/null
+++ b/src/i18n/locales/ja/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "スキル",
+ "memory": "メモリ",
+ "preview": "プレビュー",
+ "skills-description": "カスタムスキルを追加してエージェントの能力を拡張します。",
+ "memory-description": "エージェントのメモリとナレッジベースを管理します。",
+ "memory-coming-soon-description": "メモリ機能により、エージェントがセッション間で重要な情報を記憶できるようになります。",
+ "learn-more": "詳細を見る",
+ "search-skills": "スキルを検索...",
+ "search-tooltip": "検索",
+ "clear-search-tooltip": "検索をクリア",
+ "add": "追加",
+ "your-skills": "あなたのスキル",
+ "example-skills": "サンプルスキル",
+ "no-skills-found": "一致するスキルが見つかりません。",
+ "no-your-skills": "まだスキルを追加していません。",
+ "no-example-skills": "利用可能なサンプルスキルがありません。",
+ "add-your-first-skill": "最初のスキルを追加",
+ "added": "追加日",
+ "global": "グローバル",
+ "partial-available": "部分的に利用可能(複数選択)",
+ "select-agents": "エージェントを選択",
+ "select-scope": "スコープを選択",
+ "skill-scope": "スキル範囲",
+ "selected": "選択済み",
+ "agents": "エージェント",
+ "no-agents-available": "利用可能なエージェントがありません",
+ "try-in-chat": "チャットで試す",
+ "add-skill": "スキルを追加",
+ "drag-and-drop": "ファイルをここにドラッグ&ドロップ",
+ "or-click-to-browse": "またはクリックして参照",
+ "file-requirements": "ファイル要件:",
+ "file-requirements-detail-1": "アップロードする .zip またはスキルパッケージには、ルートディレクトリに SKILL.md ファイルが含まれている必要があります。",
+ "file-requirements-detail-2": "SKILL.md ファイルは YAML 形式でスキル名と説明を定義する必要があります。",
+ "supported-formats": "サポート形式",
+ "max-file-size": "最大ファイルサイズ",
+ "upload": "アップロード",
+ "invalid-file-type": "無効なファイル形式です。.skill、.md、.txt、または .json ファイルをアップロードしてください。",
+ "file-too-large": "ファイルが大きすぎます。最大サイズは 1MB です。",
+ "file-read-error": "ファイルの読み込みに失敗しました。もう一度お試しください。",
+ "reupload-file": "ファイルを再アップロード",
+ "upload-error-invalid-format": "ファイルは .zip またはスキルパッケージ(.skill または .md)である必要があります。",
+ "upload-error-invalid-yaml": "SKILL.md では YAML 形式で name と description を定義する必要があります。",
+ "skill-added-success": "スキルが正常に追加されました!",
+ "skill-add-error": "スキルの追加に失敗しました。もう一度お試しください。",
+ "custom-skill": "カスタムスキル",
+ "delete-skill": "スキルを削除",
+ "delete-skill-confirmation": "\"{{name}}\" を削除してもよろしいですか?この操作は元に戻せません。",
+ "skill-deleted-success": "スキルが正常に削除されました!"
+}
diff --git a/src/i18n/locales/ja/index.ts b/src/i18n/locales/ja/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/ja/index.ts
+++ b/src/i18n/locales/ja/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/ja/layout.json b/src/i18n/locales/ja/layout.json
index 6c0e9e45c..b033a43b7 100644
--- a/src/i18n/locales/ja/layout.json
+++ b/src/i18n/locales/ja/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "インストールに失敗しました",
"projects": "プロジェクト",
"mcp-tools": "MCP & ツール",
+ "connectors": "コネクタ",
"browser": "ブラウザ",
"settings": "設定",
+ "general": "一般",
"workers": "ワーカー",
"triggers": "トリガー",
"new-project": "新規プロジェクト",
@@ -166,5 +168,6 @@
"days-ago": "日前",
"delete-project": "プロジェクトを削除",
"delete-project-confirmation": "このプロジェクトとそのすべてのタスクを削除してもよろしいですか?この操作は元に戻せません。",
- "please-select-model": "続行するには、設定 > モデルでモデルを選択してください。"
+ "please-select-model": "続行するには、設定 > モデルでモデルを選択してください。",
+ "capabilities": "機能"
}
diff --git a/src/i18n/locales/ko/agents.json b/src/i18n/locales/ko/agents.json
new file mode 100644
index 000000000..5649fb6d1
--- /dev/null
+++ b/src/i18n/locales/ko/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "스킬",
+ "memory": "메모리",
+ "preview": "미리보기",
+ "skills-description": "사용자 정의 스킬을 추가하여 에이전트 기능을 확장하세요.",
+ "memory-description": "에이전트의 메모리와 지식 베이스를 관리하세요.",
+ "memory-coming-soon-description": "메모리 기능을 통해 에이전트가 세션 간에 중요한 정보를 기억할 수 있습니다.",
+ "learn-more": "자세히 알아보기",
+ "search-skills": "스킬 검색...",
+ "search-tooltip": "검색",
+ "clear-search-tooltip": "검색 지우기",
+ "add": "추가",
+ "your-skills": "내 스킬",
+ "example-skills": "예제 스킬",
+ "no-skills-found": "일치하는 스킬을 찾을 수 없습니다.",
+ "no-your-skills": "아직 스킬을 추가하지 않았습니다.",
+ "no-example-skills": "사용 가능한 예제 스킬이 없습니다.",
+ "add-your-first-skill": "첫 번째 스킬 추가",
+ "added": "추가됨",
+ "global": "전역",
+ "partial-available": "부분 사용 가능 (다중 선택)",
+ "select-agents": "에이전트 선택",
+ "select-scope": "범위 선택",
+ "skill-scope": "스킬 범위",
+ "selected": "선택됨",
+ "agents": "에이전트",
+ "no-agents-available": "사용 가능한 에이전트가 없습니다",
+ "try-in-chat": "채팅에서 사용해보기",
+ "add-skill": "스킬 추가",
+ "drag-and-drop": "여기에 파일을 드래그 앤 드롭하세요",
+ "or-click-to-browse": "또는 클릭하여 찾아보기",
+ "file-requirements": "파일 요구 사항:",
+ "file-requirements-detail-1": "업로드된 .zip 또는 스킬 패키지에는 루트 디렉토리에 SKILL.md 파일이 포함되어야 합니다.",
+ "file-requirements-detail-2": "SKILL.md 파일은 YAML 형식으로 스킬 이름과 설명을 정의해야 합니다.",
+ "supported-formats": "지원 형식",
+ "max-file-size": "최대 파일 크기",
+ "upload": "업로드",
+ "invalid-file-type": "잘못된 파일 유형입니다. .skill, .md, .txt 또는 .json 파일을 업로드해 주세요.",
+ "file-too-large": "파일이 너무 큽니다. 최대 크기는 1MB입니다.",
+ "file-read-error": "파일 읽기에 실패했습니다. 다시 시도해 주세요.",
+ "reupload-file": "파일 다시 업로드",
+ "upload-error-invalid-format": "파일은 .zip 또는 스킬 패키지(.skill 또는 .md)여야 합니다.",
+ "upload-error-invalid-yaml": "SKILL.md에는 YAML 형식으로 name과 description이 정의되어 있어야 합니다.",
+ "skill-added-success": "스킬이 성공적으로 추가되었습니다!",
+ "skill-add-error": "스킬 추가에 실패했습니다. 다시 시도해 주세요.",
+ "custom-skill": "사용자 정의 스킬",
+ "delete-skill": "스킬 삭제",
+ "delete-skill-confirmation": "\"{{name}}\"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
+ "skill-deleted-success": "스킬이 성공적으로 삭제되었습니다!"
+}
diff --git a/src/i18n/locales/ko/index.ts b/src/i18n/locales/ko/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/ko/index.ts
+++ b/src/i18n/locales/ko/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/ko/layout.json b/src/i18n/locales/ko/layout.json
index d6678e942..40809222c 100644
--- a/src/i18n/locales/ko/layout.json
+++ b/src/i18n/locales/ko/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "설치 실패",
"projects": "프로젝트",
"mcp-tools": "MCP 및 도구",
+ "connectors": "커넥터",
"browser": "브라우저",
"settings": "설정",
+ "general": "일반",
"workers": "작업자",
"triggers": "트리거",
"new-project": "새 프로젝트",
@@ -166,5 +168,6 @@
"days-ago": "일 전",
"delete-project": "프로젝트 삭제",
"delete-project-confirmation": "이 프로젝트와 모든 작업을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
- "please-select-model": "계속하려면 설정 > 모델에서 모델을 선택하세요."
+ "please-select-model": "계속하려면 설정 > 모델에서 모델을 선택하세요.",
+ "capabilities": "기능"
}
diff --git a/src/i18n/locales/ru/agents.json b/src/i18n/locales/ru/agents.json
new file mode 100644
index 000000000..ef7c027f3
--- /dev/null
+++ b/src/i18n/locales/ru/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "Навыки",
+ "memory": "Память",
+ "preview": "Предпросмотр",
+ "skills-description": "Добавьте пользовательские навыки для расширения возможностей вашего агента.",
+ "memory-description": "Управляйте памятью и базой знаний вашего агента.",
+ "memory-coming-soon-description": "Функции памяти позволят вашим агентам запоминать важную информацию между сеансами.",
+ "learn-more": "Узнать больше",
+ "search-skills": "Поиск навыков...",
+ "search-tooltip": "Поиск",
+ "clear-search-tooltip": "Очистить поиск",
+ "add": "Добавить",
+ "your-skills": "Ваши навыки",
+ "example-skills": "Примеры навыков",
+ "no-skills-found": "Навыки не найдены.",
+ "no-your-skills": "Вы ещё не добавили навыки.",
+ "no-example-skills": "Нет доступных примеров навыков.",
+ "add-your-first-skill": "Добавьте свой первый навык",
+ "added": "Добавлено",
+ "global": "Глобальный",
+ "partial-available": "Частично доступно (множественный выбор)",
+ "select-agents": "Выберите агентов",
+ "select-scope": "Выберите область",
+ "skill-scope": "Область навыка",
+ "selected": "выбрано",
+ "agents": "Агенты",
+ "no-agents-available": "Нет доступных агентов",
+ "try-in-chat": "Попробовать в чате",
+ "add-skill": "Добавить навык",
+ "drag-and-drop": "Перетащите файл сюда",
+ "or-click-to-browse": "или нажмите для выбора",
+ "file-requirements": "Требования к файлу:",
+ "file-requirements-detail-1": "Загруженный .zip или пакет навыков должен содержать файл SKILL.md в корневом каталоге.",
+ "file-requirements-detail-2": "Файл SKILL.md должен определять название и описание навыка в формате YAML.",
+ "supported-formats": "Поддерживаемые форматы",
+ "max-file-size": "Максимальный размер файла",
+ "upload": "Загрузить",
+ "invalid-file-type": "Неверный тип файла. Пожалуйста, загрузите файл .skill, .md, .txt или .json.",
+ "file-too-large": "Файл слишком большой. Максимальный размер 1MB.",
+ "file-read-error": "Не удалось прочитать файл. Пожалуйста, попробуйте снова.",
+ "reupload-file": "Загрузить файл снова",
+ "upload-error-invalid-format": "Файл должен быть .zip или пакетом навыка (.skill или .md).",
+ "upload-error-invalid-yaml": "В SKILL.md должны быть указаны name и description в формате YAML.",
+ "skill-added-success": "Навык успешно добавлен!",
+ "skill-add-error": "Не удалось добавить навык. Пожалуйста, попробуйте снова.",
+ "custom-skill": "Пользовательский навык",
+ "delete-skill": "Удалить навык",
+ "delete-skill-confirmation": "Вы уверены, что хотите удалить \"{{name}}\"? Это действие нельзя отменить.",
+ "skill-deleted-success": "Навык успешно удалён!"
+}
diff --git a/src/i18n/locales/ru/index.ts b/src/i18n/locales/ru/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/ru/index.ts
+++ b/src/i18n/locales/ru/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/ru/layout.json b/src/i18n/locales/ru/layout.json
index bc93cfc18..60241f8b2 100644
--- a/src/i18n/locales/ru/layout.json
+++ b/src/i18n/locales/ru/layout.json
@@ -31,8 +31,10 @@
"installation-failed": "Установка не удалась",
"projects": "Проекты",
"mcp-tools": "MCP и Инструменты",
+ "connectors": "Коннекторы",
"browser": "Браузер",
"settings": "Настройки",
+ "general": "Общие",
"workers": "Работники",
"triggers": "Триггеры",
"new-project": "Новый проект",
@@ -166,5 +168,6 @@
"days-ago": "дней назад",
"delete-project": "Удалить проект",
"delete-project-confirmation": "Вы уверены, что хотите удалить этот проект и все его задачи? Это действие нельзя отменить.",
- "please-select-model": "Пожалуйста, выберите модель в Настройки > Модели, чтобы продолжить."
+ "please-select-model": "Пожалуйста, выберите модель в Настройки > Модели, чтобы продолжить.",
+ "capabilities": "Возможности"
}
diff --git a/src/i18n/locales/zh-Hans/agents.json b/src/i18n/locales/zh-Hans/agents.json
new file mode 100644
index 000000000..adedc1988
--- /dev/null
+++ b/src/i18n/locales/zh-Hans/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "技能",
+ "memory": "记忆",
+ "preview": "预览",
+ "skills-description": "添加自定义技能以扩展您的智能体能力。",
+ "memory-description": "管理您的智能体的记忆和知识库。",
+ "memory-coming-soon-description": "记忆功能将允许您的智能体在会话之间记住重要信息。",
+ "learn-more": "了解更多",
+ "search-skills": "搜索技能...",
+ "search-tooltip": "搜索",
+ "clear-search-tooltip": "清除搜索",
+ "add": "添加",
+ "your-skills": "您的技能",
+ "example-skills": "示例技能",
+ "no-skills-found": "未找到匹配的技能。",
+ "no-your-skills": "您还没有添加任何技能。",
+ "no-example-skills": "没有可用的示例技能。",
+ "add-your-first-skill": "添加您的第一个技能",
+ "added": "添加于",
+ "global": "全局",
+ "partial-available": "部分可用(多选)",
+ "select-agents": "选择智能体",
+ "select-scope": "选择范围",
+ "skill-scope": "技能范围",
+ "selected": "已选择",
+ "agents": "智能体",
+ "no-agents-available": "没有可用的智能体",
+ "try-in-chat": "在对话中尝试",
+ "add-skill": "添加技能",
+ "drag-and-drop": "拖放文件到此处",
+ "or-click-to-browse": "或点击浏览",
+ "file-requirements": "文件要求:",
+ "file-requirements-detail-1": "上传的 .zip 或技能包必须在根目录中包含一个 SKILL.md 文件。",
+ "file-requirements-detail-2": "SKILL.md 文件必须使用 YAML 格式定义技能名称和描述。",
+ "supported-formats": "支持的格式",
+ "max-file-size": "最大文件大小",
+ "upload": "上传",
+ "invalid-file-type": "无效的文件类型。请上传 .skill、.md、.txt 或 .json 文件。",
+ "file-too-large": "文件太大。最大大小为 1MB。",
+ "file-read-error": "读取文件失败。请重试。",
+ "reupload-file": "重新上传文件",
+ "upload-error-invalid-format": "文件必须为 .zip 或技能包(.skill 或 .md)。",
+ "upload-error-invalid-yaml": "SKILL.md 必须使用 YAML 格式定义 name 和 description。",
+ "skill-added-success": "技能添加成功!",
+ "skill-add-error": "添加技能失败。请重试。",
+ "custom-skill": "自定义技能",
+ "delete-skill": "删除技能",
+ "delete-skill-confirmation": "您确定要删除 \"{{name}}\" 吗?此操作无法撤销。",
+ "skill-deleted-success": "技能删除成功!"
+}
diff --git a/src/i18n/locales/zh-Hans/index.ts b/src/i18n/locales/zh-Hans/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/zh-Hans/index.ts
+++ b/src/i18n/locales/zh-Hans/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/zh-Hans/layout.json b/src/i18n/locales/zh-Hans/layout.json
index 4a28cc55e..933e99d7b 100644
--- a/src/i18n/locales/zh-Hans/layout.json
+++ b/src/i18n/locales/zh-Hans/layout.json
@@ -34,8 +34,10 @@
"backend-startup-failed": "后端启动失败",
"projects": "项目",
"mcp-tools": "MCP & 工具",
+ "connectors": "连接器",
"browser": "浏览器",
"settings": "设置",
+ "general": "通用",
"workers": "工作器",
"triggers": "触发器",
"new-project": "新项目",
@@ -168,5 +170,6 @@
"days-ago": "天前",
"delete-project": "删除项目",
"delete-project-confirmation": "您确定要删除此项目及其所有任务吗?此操作无法撤销。",
- "please-select-model": "请在设置 > 模型中选择一个模型以继续。"
+ "please-select-model": "请在设置 > 模型中选择一个模型以继续。",
+ "capabilities": "能力"
}
diff --git a/src/i18n/locales/zh-Hant/agents.json b/src/i18n/locales/zh-Hant/agents.json
new file mode 100644
index 000000000..2884243db
--- /dev/null
+++ b/src/i18n/locales/zh-Hant/agents.json
@@ -0,0 +1,50 @@
+{
+ "skills": "技能",
+ "memory": "記憶",
+ "preview": "預覽",
+ "skills-description": "新增自訂技能以擴展您的智能體能力。",
+ "memory-description": "管理您的智能體的記憶和知識庫。",
+ "memory-coming-soon-description": "記憶功能將允許您的智能體在會話之間記住重要資訊。",
+ "learn-more": "了解更多",
+ "search-skills": "搜尋技能...",
+ "search-tooltip": "搜尋",
+ "clear-search-tooltip": "清除搜尋",
+ "add": "新增",
+ "your-skills": "您的技能",
+ "example-skills": "範例技能",
+ "no-skills-found": "未找到匹配的技能。",
+ "no-your-skills": "您還沒有新增任何技能。",
+ "no-example-skills": "沒有可用的範例技能。",
+ "add-your-first-skill": "新增您的第一個技能",
+ "added": "新增於",
+ "global": "全域",
+ "partial-available": "部分可用(多選)",
+ "select-agents": "選擇智能體",
+ "select-scope": "選擇範圍",
+ "skill-scope": "技能範圍",
+ "selected": "已選擇",
+ "agents": "智能體",
+ "no-agents-available": "沒有可用的智能體",
+ "try-in-chat": "在對話中嘗試",
+ "add-skill": "新增技能",
+ "drag-and-drop": "拖放檔案到此處",
+ "or-click-to-browse": "或點擊瀏覽",
+ "file-requirements": "檔案要求:",
+ "file-requirements-detail-1": "上傳的 .zip 或技能包必須在根目錄中包含一個 SKILL.md 檔案。",
+ "file-requirements-detail-2": "SKILL.md 檔案必須使用 YAML 格式定義技能名稱和描述。",
+ "supported-formats": "支援的格式",
+ "max-file-size": "最大檔案大小",
+ "upload": "上傳",
+ "invalid-file-type": "無效的檔案類型。請上傳 .skill、.md、.txt 或 .json 檔案。",
+ "file-too-large": "檔案太大。最大大小為 1MB。",
+ "file-read-error": "讀取檔案失敗。請重試。",
+ "reupload-file": "重新上傳檔案",
+ "upload-error-invalid-format": "檔案必須為 .zip 或技能套件(.skill 或 .md)。",
+ "upload-error-invalid-yaml": "SKILL.md 必須使用 YAML 格式定義 name 與 description。",
+ "skill-added-success": "技能新增成功!",
+ "skill-add-error": "新增技能失敗。請重試。",
+ "custom-skill": "自訂技能",
+ "delete-skill": "刪除技能",
+ "delete-skill-confirmation": "您確定要刪除 \"{{name}}\" 嗎?此操作無法撤銷。",
+ "skill-deleted-success": "技能刪除成功!"
+}
diff --git a/src/i18n/locales/zh-Hant/index.ts b/src/i18n/locales/zh-Hant/index.ts
index 47ed589ff..dc2a149a6 100644
--- a/src/i18n/locales/zh-Hant/index.ts
+++ b/src/i18n/locales/zh-Hant/index.ts
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import agents from './agents.json';
import chat from './chat.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
@@ -19,6 +20,7 @@ import setting from './setting.json';
import update from './update.json';
import workforce from './workforce.json';
export default {
+ agents,
layout,
dashboard,
workforce,
diff --git a/src/i18n/locales/zh-Hant/layout.json b/src/i18n/locales/zh-Hant/layout.json
index 1a33236cd..1b7a4e966 100644
--- a/src/i18n/locales/zh-Hant/layout.json
+++ b/src/i18n/locales/zh-Hant/layout.json
@@ -33,8 +33,10 @@
"installation-failed": "安装失败",
"projects": "專案",
"mcp-tools": "MCP & 工具",
+ "connectors": "連接器",
"browser": "瀏覽器",
"settings": "設定",
+ "general": "一般",
"workers": "工作器",
"triggers": "觸發器",
"new-project": "新專案",
@@ -168,5 +170,6 @@
"days-ago": "天前",
"delete-project": "刪除專案",
"delete-project-confirmation": "您確定要刪除此專案及其所有任務嗎?此操作無法撤銷。",
- "please-select-model": "請在設定 > 模型中選擇一個模型以繼續。"
+ "please-select-model": "請在設定 > 模型中選擇一個模型以繼續。",
+ "capabilities": "能力"
}
diff --git a/src/lib/skillToolkit.ts b/src/lib/skillToolkit.ts
new file mode 100644
index 000000000..3a84bd945
--- /dev/null
+++ b/src/lib/skillToolkit.ts
@@ -0,0 +1,141 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+/**
+ * Skill toolkit utilities aligned with CAMEL's skill_toolkit:
+ * https://github.com/camel-ai/camel/blob/master/camel/toolkits/skill_toolkit.py
+ *
+ * Skills are stored as SKILL.md files with YAML frontmatter (name, description)
+ * and a markdown body. Discovery order: repo > user > system (CAMEL);
+ * in Eigent we use user scope at ~/.eigent/.camel/skills (one folder per skill).
+ */
+
+export interface SkillMeta {
+ name: string;
+ description: string;
+ body: string;
+}
+
+export interface ScannedSkill {
+ name: string;
+ description: string;
+ path: string;
+ scope: 'repo' | 'user' | 'system';
+ /** Folder name under skills dir (e.g. "my-skill") */
+ skillDirName: string;
+}
+
+const FRONTMATTER_DELIM = '---';
+
+/**
+ * Split YAML frontmatter from the body of a SKILL.md file.
+ * Expects content to start with "---", then YAML, then "---", then body.
+ */
+export function splitFrontmatter(contents: string): {
+ frontmatter: string | null;
+ body: string;
+} {
+ // Strip BOM and leading whitespace/newlines so the first `---` is detected
+ const cleaned = contents.replace(/^\uFEFF/, '').trimStart();
+ const lines = cleaned.split('\n');
+ if (!lines.length || lines[0].trim() !== FRONTMATTER_DELIM) {
+ return { frontmatter: null, body: cleaned };
+ }
+ for (let i = 1; i < lines.length; i++) {
+ if (lines[i].trim() === FRONTMATTER_DELIM) {
+ const frontmatter = lines.slice(1, i).join('\n');
+ const body = lines.slice(i + 1).join('\n');
+ return { frontmatter, body };
+ }
+ }
+ return { frontmatter: null, body: cleaned };
+}
+
+/** Simple YAML-like parse for "name:" and "description:" (first-level keys only). */
+function parseSimpleYaml(text: string): Record {
+ const out: Record = {};
+ const lines = text.split('\n');
+ for (const line of lines) {
+ const match = line.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
+ if (match) {
+ const value = match[2].trim();
+ // Lowercase key so `Name:` / `name:` / `NAME:` all work
+ out[match[1].toLowerCase()] = value
+ .replace(/^['"]|['"]$/g, '')
+ .replace(/\\"/g, '"')
+ .trim();
+ }
+ }
+ return out;
+}
+
+/**
+ * Parse a SKILL.md file content and extract name, description, and body.
+ * Compatible with CAMEL's _parse_skill format.
+ */
+export function parseSkillMd(contents: string): SkillMeta | null {
+ const { frontmatter, body } = splitFrontmatter(contents);
+ if (!frontmatter) return null;
+ const data = parseSimpleYaml(frontmatter);
+ const name = data.name;
+ const description = data.description;
+ if (typeof name !== 'string' || typeof description !== 'string') return null;
+ return {
+ name: name.trim(),
+ description: description.trim(),
+ body: body.trim(),
+ };
+}
+
+/**
+ * Build SKILL.md content from name, description, and body (CAMEL-compatible).
+ */
+export function buildSkillMd(
+ name: string,
+ description: string,
+ body: string
+): string {
+ const escapedDescription = description
+ .replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"');
+ const front = [
+ FRONTMATTER_DELIM,
+ `name: ${name}`,
+ `description: "${escapedDescription}"`,
+ FRONTMATTER_DELIM,
+ '',
+ body,
+ ].join('\n');
+ return front;
+}
+
+/**
+ * Sanitize a skill name into a safe folder name (no path separators, no dots at start).
+ */
+export function skillNameToDirName(name: string): string {
+ // Keep original casing so that folder name matches skill name as closely
+ // as possible, only stripping/normalizing unsafe characters.
+ const cleaned = name
+ .replace(/[\\/*?:"<>|\s]+/g, '-')
+ .replace(/-+/g, '-')
+ .replace(/^-|-$/g, '');
+ return cleaned || 'skill';
+}
+
+/** Check if running in Electron with skills API available */
+export function hasSkillsFsApi(): boolean {
+ return (
+ typeof window !== 'undefined' && !!(window as any).electronAPI?.skillsScan
+ );
+}
diff --git a/src/pages/Agents/Memory.tsx b/src/pages/Agents/Memory.tsx
new file mode 100644
index 000000000..2647c67c3
--- /dev/null
+++ b/src/pages/Agents/Memory.tsx
@@ -0,0 +1,46 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import { Brain } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+export default function Memory() {
+ const { t } = useTranslation();
+
+ return (
+
+ {/* Header Section */}
+
+
+ {t('agents.memory')}
+
+
+
+ {/* Content Section */}
+
+
+
+
+
+
+ {t('layout.coming-soon')}
+
+
+ {t('agents.memory-coming-soon-description')}
+
+
+
+
+ );
+}
diff --git a/src/pages/Setting/Models.tsx b/src/pages/Agents/Models.tsx
similarity index 100%
rename from src/pages/Setting/Models.tsx
rename to src/pages/Agents/Models.tsx
diff --git a/src/pages/Agents/Skills.tsx b/src/pages/Agents/Skills.tsx
new file mode 100644
index 000000000..1dd8b659d
--- /dev/null
+++ b/src/pages/Agents/Skills.tsx
@@ -0,0 +1,193 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import SearchInput from '@/components/SearchInput';
+import { Button } from '@/components/ui/button';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { useSkillsStore, type Skill } from '@/store/skillsStore';
+import { Plus } from 'lucide-react';
+import { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import SkillDeleteDialog from './components/SkillDeleteDialog';
+import SkillListItem from './components/SkillListItem';
+import SkillUploadDialog from './components/SkillUploadDialog';
+
+export default function Skills() {
+ const { t } = useTranslation();
+ const { skills, syncFromDisk } = useSkillsStore();
+ const [searchQuery, setSearchQuery] = useState('');
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [skillToDelete, setSkillToDelete] = useState(null);
+
+ // On first mount, sync skills from local SKILL.md files
+ useEffect(() => {
+ // No-op on web; in Electron this will scan ~/.eigent/skills
+ syncFromDisk();
+ }, [syncFromDisk]);
+
+ const yourSkills = useMemo(() => {
+ return skills
+ .filter((skill) => !skill.isExample)
+ .filter(
+ (skill) =>
+ skill.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ skill.description.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [skills, searchQuery]);
+
+ const exampleSkills = useMemo(() => {
+ return skills
+ .filter((skill) => skill.isExample)
+ .filter(
+ (skill) =>
+ skill.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ skill.description.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [skills, searchQuery]);
+
+ const handleDeleteClick = (skill: Skill) => {
+ setSkillToDelete(skill);
+ setDeleteDialogOpen(true);
+ };
+
+ const handleDeleteConfirm = () => {
+ setDeleteDialogOpen(false);
+ setSkillToDelete(null);
+ };
+
+ const handleDeleteCancel = () => {
+ setDeleteDialogOpen(false);
+ setSkillToDelete(null);
+ };
+
+ return (
+
+ {/* Header Section */}
+
+
+ {t('agents.skills')}
+
+
+
+ {/* Content Section */}
+
+
+
+
+
+
+ {t('agents.your-skills')}
+
+ {/* TODO: Add example skills back in */}
+ {/*
+ {t('agents.example-skills')}
+ */}
+
+
+
setSearchQuery(e.target.value)}
+ placeholder={t('agents.search-skills')}
+ />
+
+
+
+
+ {yourSkills.length === 0 ? (
+ setUploadDialogOpen(true) : undefined
+ }
+ />
+ ) : (
+
+ {yourSkills.map((skill) => (
+ handleDeleteClick(skill)}
+ />
+ ))}
+
+ )}
+
+
+ {exampleSkills.length === 0 ? (
+
+ ) : (
+
+ {exampleSkills.map((skill) => (
+
+ ))}
+
+ )}
+
+
+
+
+
+ {/* Upload Dialog */}
+
setUploadDialogOpen(false)}
+ />
+
+ {/* Delete Dialog */}
+
+
+ );
+}
diff --git a/src/pages/Agents/components/SkillDeleteDialog.tsx b/src/pages/Agents/components/SkillDeleteDialog.tsx
new file mode 100644
index 000000000..1aec0f88d
--- /dev/null
+++ b/src/pages/Agents/components/SkillDeleteDialog.tsx
@@ -0,0 +1,58 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import ConfirmModal from '@/components/ui/alertDialog';
+import { useSkillsStore, type Skill } from '@/store/skillsStore';
+import { useTranslation } from 'react-i18next';
+import { toast } from 'sonner';
+
+interface SkillDeleteDialogProps {
+ open: boolean;
+ skill: Skill | null;
+ onConfirm: () => void;
+ onCancel: () => void;
+}
+
+export default function SkillDeleteDialog({
+ open,
+ skill,
+ onConfirm,
+ onCancel,
+}: SkillDeleteDialogProps) {
+ const { t } = useTranslation();
+ const { deleteSkill } = useSkillsStore();
+
+ const handleDelete = () => {
+ if (skill) {
+ deleteSkill(skill.id);
+ toast.success(t('agents.skill-deleted-success'));
+ }
+ onConfirm();
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/Agents/components/SkillListItem.tsx b/src/pages/Agents/components/SkillListItem.tsx
new file mode 100644
index 000000000..1b275e550
--- /dev/null
+++ b/src/pages/Agents/components/SkillListItem.tsx
@@ -0,0 +1,297 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { TooltipSimple } from '@/components/ui/tooltip';
+import {
+ getWorkflowAgentDisplay,
+ WORKFLOW_AGENT_LIST,
+} from '@/components/WorkFlow/agents';
+import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
+import { useWorkerList } from '@/store/authStore';
+import { useSkillsStore, type Skill } from '@/store/skillsStore';
+import {
+ Bot,
+ Check,
+ ChevronRight,
+ Ellipsis,
+ MessageSquare,
+ Plus,
+ Trash2,
+ Users,
+} from 'lucide-react';
+import { useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+
+interface SkillListItemDefaultProps {
+ variant?: 'default';
+ skill: Skill;
+ onDelete?: () => void;
+ message?: never;
+ addButtonText?: never;
+ onAddClick?: never;
+}
+
+interface SkillListItemPlaceholderProps {
+ variant: 'placeholder';
+ skill?: never;
+ onDelete?: never;
+ message: string;
+ addButtonText?: string;
+ onAddClick?: () => void;
+}
+
+type SkillListItemProps =
+ | SkillListItemDefaultProps
+ | SkillListItemPlaceholderProps;
+
+export default function SkillListItem(props: SkillListItemProps) {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const { updateSkill } = useSkillsStore();
+ const { projectStore } = useChatStoreAdapter();
+ const workerList = useWorkerList();
+ const [scopeOpen, setScopeOpen] = useState(false);
+
+ const allAgents = useMemo(() => {
+ const workflowNames = WORKFLOW_AGENT_LIST.map((a) => a.name);
+ const workerNames = workerList.map((w) => w.name);
+ const combined = [...workflowNames];
+ workerNames.forEach((name) => {
+ if (!combined.includes(name)) {
+ combined.push(name);
+ }
+ });
+ return combined;
+ }, [workerList]);
+
+ if (props.variant === 'placeholder') {
+ const isClickable = props.onAddClick != null;
+ return (
+ {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ props.onAddClick?.();
+ }
+ }
+ : undefined
+ }
+ aria-label={isClickable ? props.addButtonText : undefined}
+ >
+
{props.message}
+ {isClickable &&
}
+
+ );
+ }
+
+ const { skill, onDelete } = props;
+
+ const formatDate = (timestamp: number) => {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const diffDays = Math.floor(
+ (now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24)
+ );
+
+ if (diffDays === 0) {
+ return t('layout.today');
+ } else if (diffDays === 1) {
+ return t('layout.yesterday');
+ } else if (diffDays < 30) {
+ return `${diffDays} ${t('layout.days-ago')}`;
+ } else {
+ return date.toLocaleDateString();
+ }
+ };
+
+ const handleScopeChange = (scope: {
+ isGlobal: boolean;
+ selectedAgents: string[];
+ }) => {
+ updateSkill(skill.id, { scope });
+ };
+
+ const isAllAgentsSelected = skill.scope.isGlobal;
+
+ const handleToggleAllAgents = () => {
+ if (isAllAgentsSelected) {
+ // When user unselects "All agents", clear all individual selections
+ handleScopeChange({
+ isGlobal: false,
+ selectedAgents: [],
+ });
+ } else {
+ // When user selects "All agents", use empty array to indicate ALL agents (including future ones)
+ handleScopeChange({
+ isGlobal: true,
+ selectedAgents: [], // Empty array = all agents, including future agents
+ });
+ }
+ };
+
+ const handleToggleAgent = (agentName: string) => {
+ if (isAllAgentsSelected) {
+ const newSelectedAgents = allAgents.filter((a) => a !== agentName);
+ handleScopeChange({
+ isGlobal: false,
+ selectedAgents: newSelectedAgents,
+ });
+ return;
+ }
+
+ const isSelected = skill.scope.selectedAgents.includes(agentName);
+ const newSelectedAgents = isSelected
+ ? skill.scope.selectedAgents.filter((a) => a !== agentName)
+ : [...skill.scope.selectedAgents, agentName];
+ handleScopeChange({
+ isGlobal: false,
+ selectedAgents: newSelectedAgents,
+ });
+ };
+
+ const handleTryInChat = () => {
+ projectStore?.createProject('new project');
+ const prompt = `I just added the {{${skill.name}}} skill for Eigent, can you make something amazing with this skill?`;
+ navigate(`/?skill_prompt=${encodeURIComponent(prompt)}`);
+ };
+
+ return (
+
+ {/* Row 1: Name / Actions */}
+
+
+
+ {skill.name}
+
+
+
+
+
+ {t('agents.added')} {formatDate(skill.addedAt)}
+
+
+
+
+
+
+
+
+ {t('agents.try-in-chat')}
+
+ {!skill.isExample && onDelete && (
+
+
+ {t('layout.delete')}
+
+ )}
+
+
+
+
+
+ {/* Row 2: Description - 5 lines max, hover shows full */}
+
+
+
+ {skill.description}
+
+
+
+
+ {/* Row 3: Added time / Skill scope */}
+
+
+
+ {scopeOpen && (
+
+ {/* All agents as first tab; then each agent toggle */}
+
+
+ {allAgents.map((agentName) => {
+ const isSelected =
+ isAllAgentsSelected ||
+ skill.scope.selectedAgents.includes(agentName);
+ const display = getWorkflowAgentDisplay(agentName);
+ const icon = display?.icon ?? (
+
+ );
+ return (
+
+ );
+ })}
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/Agents/components/SkillUploadDialog.tsx b/src/pages/Agents/components/SkillUploadDialog.tsx
new file mode 100644
index 000000000..3a50e0a15
--- /dev/null
+++ b/src/pages/Agents/components/SkillUploadDialog.tsx
@@ -0,0 +1,371 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogContentSection,
+ DialogHeader,
+} from '@/components/ui/dialog';
+import { parseSkillMd } from '@/lib/skillToolkit';
+import { useSkillsStore } from '@/store/skillsStore';
+import { AlertCircle, File, Upload, X } from 'lucide-react';
+import { useCallback, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { toast } from 'sonner';
+
+interface SkillUploadDialogProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export default function SkillUploadDialog({
+ open,
+ onClose,
+}: SkillUploadDialogProps) {
+ const { t } = useTranslation();
+ const { addSkill, syncFromDisk } = useSkillsStore();
+ const [isDragging, setIsDragging] = useState(false);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [fileContent, setFileContent] = useState('');
+ const [_isUploading, setIsUploading] = useState(false);
+ const fileInputRef = useRef(null);
+ const [isZip, setIsZip] = useState(false);
+ const [uploadError, setUploadError] = useState<
+ 'invalid_format' | 'invalid_yaml' | null
+ >(null);
+
+ const handleClose = useCallback(() => {
+ setSelectedFile(null);
+ setFileContent('');
+ setIsDragging(false);
+ setIsZip(false);
+ setUploadError(null);
+ onClose();
+ }, [onClose]);
+
+ const handleUpload = useCallback(
+ async (
+ fileArg?: File,
+ options?: { isZipOverride?: boolean; contentOverride?: string }
+ ) => {
+ const fileToUse = fileArg ?? selectedFile;
+ if (!fileToUse) return;
+
+ const isZipToUse = options?.isZipOverride ?? isZip;
+ const fileContentToUse = options?.contentOverride ?? fileContent;
+
+ setIsUploading(true);
+ try {
+ // Zip import: read file in renderer and send buffer to main (no path in sandbox)
+ if (isZipToUse) {
+ if (!(window as any).electronAPI?.skillImportZip) {
+ toast.error(t('agents.skill-add-error'));
+ return;
+ }
+ let buffer: ArrayBuffer;
+ try {
+ buffer = await fileToUse.arrayBuffer();
+ } catch {
+ toast.error(t('agents.file-read-error'));
+ return;
+ }
+ const result = await (window as any).electronAPI.skillImportZip(
+ buffer
+ );
+ if (!result?.success) {
+ toast.error(result?.error || t('agents.skill-add-error'));
+ return;
+ }
+ await syncFromDisk();
+ toast.success(t('agents.skill-added-success'));
+ handleClose();
+ return;
+ }
+
+ if (!fileContentToUse) return;
+
+ const fileName = fileToUse.name.replace(/\.[^/.]+$/, '');
+
+ // Prefer SKILL.md frontmatter (name + description) at upload time
+ const meta = parseSkillMd(fileContentToUse);
+ let name = meta?.name ?? fileName;
+ let description = meta?.description ?? '';
+
+ // Fallback: no frontmatter — use first heading and first paragraph
+ if (!meta && fileContentToUse.startsWith('#')) {
+ const lines = fileContentToUse.split('\n');
+ const headingMatch = lines[0].match(/^#\s+(.+)/);
+ if (headingMatch) name = headingMatch[1];
+ for (let i = 1; i < lines.length; i++) {
+ const line = lines[i].trim();
+ if (line && !line.startsWith('#')) {
+ description = line;
+ break;
+ }
+ }
+ }
+
+ addSkill({
+ name,
+ description: description || t('agents.custom-skill'),
+ filePath: fileToUse.name,
+ fileContent: fileContentToUse,
+ scope: { isGlobal: true, selectedAgents: [] },
+ enabled: true,
+ });
+
+ toast.success(t('agents.skill-added-success'));
+ handleClose();
+ } catch (_error) {
+ toast.error(t('agents.skill-add-error'));
+ } finally {
+ setIsUploading(false);
+ }
+ },
+ [addSkill, fileContent, handleClose, isZip, selectedFile, syncFromDisk, t]
+ );
+
+ const handleDragOver = useCallback((e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(true);
+ }, []);
+
+ const handleDragLeave = useCallback((e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(false);
+ }, []);
+
+ const processFile = useCallback(
+ async (file: File) => {
+ // Only .zip or skill package (.skill, .md) are valid
+ const skillPackageExtensions = ['.zip', '.skill', '.md'];
+ const extension = file.name
+ .substring(file.name.lastIndexOf('.'))
+ .toLowerCase();
+
+ if (!skillPackageExtensions.includes(extension)) {
+ setSelectedFile(file);
+ setUploadError('invalid_format');
+ return;
+ }
+
+ // Validate file size (max 5MB to allow small zip bundles)
+ if (file.size > 5 * 1024 * 1024) {
+ toast.error(t('agents.file-too-large'));
+ return;
+ }
+
+ try {
+ setUploadError(null);
+ setSelectedFile(file);
+ if (extension === '.zip') {
+ setIsZip(true);
+ setFileContent('');
+ await handleUpload(file, {
+ isZipOverride: true,
+ contentOverride: '',
+ });
+ } else {
+ const content = await file.text();
+ setIsZip(false);
+ setFileContent(content);
+ // .skill / .md must have YAML frontmatter (name + description)
+ const meta = parseSkillMd(content);
+ if (!meta) {
+ setUploadError('invalid_yaml');
+ return;
+ }
+ await handleUpload(file, {
+ isZipOverride: false,
+ contentOverride: content,
+ });
+ }
+ } catch (_error) {
+ toast.error(t('agents.file-read-error'));
+ }
+ },
+ [handleUpload, t]
+ );
+
+ const handleDrop = useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(false);
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ processFile(files[0]);
+ }
+ },
+ [processFile]
+ );
+
+ const handleFileSelect = (e: React.ChangeEvent) => {
+ const files = e.target.files;
+ if (files && files.length > 0) {
+ processFile(files[0]);
+ }
+ };
+
+ const handleRemoveFile = () => {
+ setSelectedFile(null);
+ setFileContent('');
+ setUploadError(null);
+ if (fileInputRef.current) {
+ fileInputRef.current.value = '';
+ }
+ };
+
+ const errorMessage =
+ uploadError === 'invalid_format'
+ ? t('agents.upload-error-invalid-format')
+ : uploadError === 'invalid_yaml'
+ ? t('agents.upload-error-invalid-yaml')
+ : null;
+
+ return (
+
+ );
+}
diff --git a/src/pages/Agents/index.tsx b/src/pages/Agents/index.tsx
new file mode 100644
index 000000000..d8543f9df
--- /dev/null
+++ b/src/pages/Agents/index.tsx
@@ -0,0 +1,78 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import VerticalNavigation, {
+ type VerticalNavItem,
+} from '@/components/Navigation';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import Memory from './Memory';
+import Models from './Models';
+import Skills from './Skills';
+
+export default function Capabilities() {
+ const { t } = useTranslation();
+ const [activeTab, setActiveTab] = useState('skills');
+
+ const menuItems = [
+ {
+ id: 'models',
+ name: t('setting.models'),
+ },
+ {
+ id: 'skills',
+ name: t('agents.skills'),
+ },
+ {
+ id: 'memory',
+ name: t('agents.memory'),
+ },
+ ];
+
+ const handleTabChange = (tabId: string) => {
+ setActiveTab(tabId);
+ };
+
+ return (
+
+
+
+ ({
+ value: menu.id,
+ label: (
+ {menu.name}
+ ),
+ })) as VerticalNavItem[]
+ }
+ value={activeTab}
+ onValueChange={handleTabChange}
+ className="h-full min-h-0 w-full flex-1 gap-0"
+ listClassName="w-full h-full overflow-y-auto"
+ contentClassName="hidden"
+ />
+
+
+
+
+ {activeTab === 'models' && }
+ {activeTab === 'skills' && }
+ {activeTab === 'memory' && }
+
+
+
+
+ );
+}
diff --git a/src/pages/History.tsx b/src/pages/History.tsx
index 3551d840e..45b8b73a4 100644
--- a/src/pages/History.tsx
+++ b/src/pages/History.tsx
@@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+import { Bot } from '@/components/animate-ui/icons/bot';
import { Compass } from '@/components/animate-ui/icons/compass';
import { Hammer } from '@/components/animate-ui/icons/hammer';
import { Settings } from '@/components/animate-ui/icons/settings';
@@ -31,6 +32,7 @@ import { Plus } from 'lucide-react';
import { useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useSearchParams } from 'react-router-dom';
+import Agents from './Agents';
import Browser from './Dashboard/Browser';
import MCP from './Setting/MCP';
@@ -41,6 +43,7 @@ const VALID_TABS = [
'settings',
'mcp_tools',
'browser',
+ 'agents',
] as const;
type TabType = (typeof VALID_TABS)[number];
@@ -152,13 +155,21 @@ export default function Home() {
>
{t('layout.projects')}
+ }
+ >
+ {t('setting.agents')}
+
}
>
- {t('layout.mcp-tools')}
+ {t('layout.connectors')}
}
>
- {t('layout.settings')}
+ {t('layout.general')}
@@ -188,6 +199,7 @@ export default function Home() {
{activeTab === 'mcp_tools' &&
}
{activeTab === 'browser' &&
}
{activeTab === 'settings' &&
}
+ {activeTab === 'agents' &&
}
);
}
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index c20f83036..7f070829c 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -144,7 +144,7 @@ export default function Home() {
// });
// chatStore.setSnapshots(chatStore.activeTaskId as string, list);
})
- .catch((error) => {
+ .catch((error: unknown) => {
console.error('capture webview error:', error);
});
});
diff --git a/src/pages/Setting.tsx b/src/pages/Setting.tsx
index 586058c6f..a2d113e64 100644
--- a/src/pages/Setting.tsx
+++ b/src/pages/Setting.tsx
@@ -19,10 +19,9 @@ import VerticalNavigation, {
} from '@/components/Navigation';
import useAppVersion from '@/hooks/use-app-version';
import General from '@/pages/Setting/General';
-import Models from '@/pages/Setting/Models';
import Privacy from '@/pages/Setting/Privacy';
import { useAuthStore } from '@/store/authStore';
-import { Fingerprint, Settings, TagIcon, TextSelect } from 'lucide-react';
+import { Fingerprint, Settings, TagIcon } from 'lucide-react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
@@ -48,12 +47,6 @@ export default function Setting() {
icon: Fingerprint,
path: '/setting/privacy',
},
- {
- id: 'models',
- name: t('setting.models'),
- icon: TextSelect,
- path: '/setting/models',
- },
];
// Initialize tab from URL once, then manage locally without routing
const getCurrentTab = () => {
@@ -130,7 +123,6 @@ export default function Setting() {
{activeTab === 'general' &&
}
{activeTab === 'privacy' &&
}
- {activeTab === 'models' &&
}
diff --git a/src/store/skillsStore.ts b/src/store/skillsStore.ts
new file mode 100644
index 000000000..4485c89e4
--- /dev/null
+++ b/src/store/skillsStore.ts
@@ -0,0 +1,369 @@
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
+
+import {
+ buildSkillMd,
+ hasSkillsFsApi,
+ parseSkillMd,
+ skillNameToDirName,
+} from '@/lib/skillToolkit';
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+import { useAuthStore } from './authStore';
+
+// Helper function to normalize email to user_id format
+// Matches the logic in backend's file_save_path
+function emailToUserId(email: string | null): string | null {
+ if (!email) return null;
+ return email
+ .split('@')[0]
+ .replace(/[\\/*?:"<>|\s]/g, '_')
+ .replace(/^\.+|\.+$/g, '');
+}
+
+// Skill scope interface
+export interface SkillScope {
+ isGlobal: boolean;
+ selectedAgents: string[];
+}
+
+// Skill interface
+export interface Skill {
+ id: string;
+ name: string;
+ description: string;
+ filePath: string;
+ fileContent: string;
+ // Optional: folder name under ~/.eigent/skills
+ skillDirName?: string;
+ addedAt: number;
+ scope: SkillScope;
+ enabled: boolean;
+ isExample: boolean;
+}
+
+export const EXAMPLE_SKILL_DIR_NAMES = [
+ 'code-reviewer',
+ 'report-writer',
+ 'data-analyzer',
+] as const;
+
+// Skills state interface
+interface SkillsState {
+ skills: Skill[];
+ addSkill: (
+ skill: Omit