Polish ux and ui flow for new open connector to replace original build in connector

This commit is contained in:
Douglas 2026-07-19 17:17:33 +01:00 committed by 4pmtong
parent d8d0a9f087
commit c8aaa6232f
37 changed files with 4850 additions and 3040 deletions

View file

@ -121,18 +121,49 @@ export interface FetchConnectorProvidersOptions {
query?: string;
}
export async function fetchConnectorProviders(
export function providerLabel(provider: ConnectorProvider): string {
return provider.displayName || provider.service;
}
export function isConnectedProvider(
provider: ConnectorProvider | null | undefined
): boolean {
const connection = provider?.connection;
return connection?.configured === true && connection.virtual !== true;
}
export interface FetchConnectorProvidersRequestOptions {
/** Skip the short-lived list cache and force a network fetch. */
bypassCache?: boolean;
}
const PROVIDERS_LIST_CACHE_TTL_MS = 60_000;
type ProvidersListCacheEntry = {
expiresAt: number;
data: ConnectorProvidersResponse;
};
const providersListCache = new Map<string, ProvidersListCacheEntry>();
const providersListInflight = new Map<
string,
Promise<ConnectorProvidersResponse>
>();
function providersListCacheKey(
options: FetchConnectorProvidersOptions = {}
): Promise<ConnectorProvidersResponse> {
const params: Record<string, string | number> = {
page: options.page || 1,
page_size: options.pageSize || 24,
};
const query = options.query?.trim();
if (query) {
params.q = query;
}
const response = await proxyFetchGet('/api/v1/connectors/providers', params);
): string {
return [
options.page || 1,
options.pageSize || 24,
options.query?.trim() || '',
].join('::');
}
function normalizeProvidersResponse(
response: any,
options: FetchConnectorProvidersOptions = {}
): ConnectorProvidersResponse {
const providers = Array.isArray(response?.providers)
? response.providers
: [];
@ -168,6 +199,88 @@ export async function fetchConnectorProviders(
};
}
/** Synchronous cache read for instant UI hydration. */
export function getCachedConnectorProviders(
options: FetchConnectorProvidersOptions = {}
): ConnectorProvidersResponse | null {
const entry = providersListCache.get(providersListCacheKey(options));
if (!entry || entry.expiresAt <= Date.now()) return null;
return entry.data;
}
export function invalidateConnectorProvidersCache(): void {
providersListCache.clear();
providersListInflight.clear();
}
/** Warm the list cache without waiting for dialog open. */
export function prefetchConnectorProviders(
options: FetchConnectorProvidersOptions = {}
): Promise<ConnectorProvidersResponse> {
return fetchConnectorProviders(options);
}
export async function fetchConnectorProviders(
options: FetchConnectorProvidersOptions = {},
requestOptions: FetchConnectorProvidersRequestOptions = {}
): Promise<ConnectorProvidersResponse> {
const cacheKey = providersListCacheKey(options);
if (!requestOptions.bypassCache) {
const cached = getCachedConnectorProviders(options);
if (cached) return cached;
}
// Always coalesce concurrent identical requests, even when bypassing cache.
const inflight = providersListInflight.get(cacheKey);
if (inflight) return inflight;
const request = (async () => {
const params: Record<string, string | number> = {
page: options.page || 1,
page_size: options.pageSize || 24,
};
const query = options.query?.trim();
if (query) {
params.q = query;
}
const response = await proxyFetchGet(
'/api/v1/connectors/providers',
params
);
const normalized = normalizeProvidersResponse(response, options);
providersListCache.set(cacheKey, {
expiresAt: Date.now() + PROVIDERS_LIST_CACHE_TTL_MS,
data: normalized,
});
return normalized;
})();
providersListInflight.set(cacheKey, request);
try {
return await request;
} finally {
if (providersListInflight.get(cacheKey) === request) {
providersListInflight.delete(cacheKey);
}
}
}
/** Fetch every provider page and return only connected providers. */
export async function fetchConnectedProviders(): Promise<ConnectorProvider[]> {
const first = await fetchConnectorProviders({ page: 1, pageSize: 100 });
let providers = first.providers;
for (let page = 2; page <= first.total_pages; page += 1) {
const response = await fetchConnectorProviders({
page,
pageSize: first.page_size,
});
providers = providers.concat(response.providers);
}
const unique = new Map(
providers.map((provider) => [provider.service, provider])
);
return Array.from(unique.values()).filter(isConnectedProvider);
}
export async function fetchConnectorProvider(
service: string
): Promise<ConnectorProviderResponse> {

View file

@ -22,6 +22,7 @@ import {
} from '@/api/http';
import IntegrationList from '@/components/Dashboard/IntegrationList';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
useIntegrationManagement,
type IntegrationItem,
@ -42,6 +43,7 @@ import {
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Checkbox } from '../ui/checkbox';
import { Textarea } from '../ui/textarea';
import { TooltipSimple } from '../ui/tooltip';
@ -258,6 +260,7 @@ const ToolSelect = forwardRef<
const host = useHost();
const electronAPI = host?.electronAPI;
const { t } = useTranslation();
const navigate = useNavigate();
// state management - remove internal selected state, use parent passed initialSelectedTools
const [keyword, setKeyword] = useState<string>('');
const { email } = useAuthStore();
@ -783,36 +786,22 @@ const ToolSelect = forwardRef<
});
}, [integrations, webInstalled, keyword]);
const webNotConnectedItems = useMemo(() => {
const kw = keyword.trim().toLowerCase();
return integrations
.filter((i: IntegrationItem) => !webInstalled[i.key])
.filter((i: IntegrationItem) => {
if (!kw) return true;
const descStr = typeof i.desc === 'string' ? i.desc.toLowerCase() : '';
return (
(i.key || '').toLowerCase().includes(kw) ||
(i.name || '').toLowerCase().includes(kw) ||
descStr.includes(kw)
);
});
}, [integrations, webInstalled, keyword]);
// Align with the Connectors page: only connected built-ins and enabled
// custom MCPs are selectable as agent tools.
const ownPicks = useMemo(() => {
const kw = keyword.trim().toLowerCase();
return userMcpList.filter((opt) => {
if (!kw) return true;
const name = String(opt.mcp_name || '').toLowerCase();
const desc = String(opt.mcp_desc || '').toLowerCase();
const key = String(opt.mcp_key || '').toLowerCase();
return name.includes(kw) || desc.includes(kw) || key.includes(kw);
});
return userMcpList
.filter((opt) => Number(opt.status) === 1)
.filter((opt) => {
if (!kw) return true;
const name = String(opt.mcp_name || '').toLowerCase();
const desc = String(opt.mcp_desc || '').toLowerCase();
const key = String(opt.mcp_key || '').toLowerCase();
return name.includes(kw) || desc.includes(kw) || key.includes(kw);
});
}, [userMcpList, keyword]);
const listHasItems =
webConnectedItems.length > 0 ||
webNotConnectedItems.length > 0 ||
ownPicks.length > 0;
const listHasItems = webConnectedItems.length > 0 || ownPicks.length > 0;
const showSearchPlaceholder =
keyword.length === 0 && (initialSelectedTools?.length ?? 0) === 0;
@ -944,29 +933,21 @@ const ToolSelect = forwardRef<
</div>
</div>
)}
{webNotConnectedItems.length > 0 && (
<div>
<div className="px-2 py-1 text-body-sm font-medium text-ds-text-neutral-subtle-default">
{t('setting.mcp-sidebar-not-connected')}
</div>
<IntegrationList
className="!space-y-1.5"
variant="select"
onShowEnvConfig={onShowEnvConfig}
addOption={addOption}
items={webNotConnectedItems}
translationNamespace="layout"
selectWithCheckbox
isIntegrationSelected={isIntegrationInAgentSelection}
onToggleIntegration={handleToggleIntegrationForAgent}
/>
</div>
)}
</div>
) : (
<p className="break-words px-2 py-2 text-center text-body-md text-ds-text-neutral-muted-default">
{t('dashboard.no-results')}
</p>
<div className="flex flex-col items-center gap-1 px-2 py-2">
<p className="break-words text-center text-body-md text-ds-text-neutral-muted-default">
{t('dashboard.no-results')}
</p>
<Button
variant="ghost"
size="xs"
buttonContent="text"
onClick={() => navigate('/history?tab=connectors')}
>
{t('chat.input-attach-manage-connectors')}
</Button>
</div>
)}
</div>
</div>

View file

@ -12,9 +12,14 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { fetchConnectedProviders, providerLabel } from '@/api/connectors';
import { proxyFetchGet } from '@/api/http';
import ellipseIcon from '@/assets/mcp/Ellipse-25.svg';
import { Button } from '@/components/ui/button';
import {
useIntegrationManagement,
type IntegrationItem,
} from '@/hooks/useIntegrationManagement';
import { integrationLeadingIconUrl } from '@/lib/connectorIcons';
import {
RICH_CONNECTOR_STYLE_CLASSES,
@ -24,6 +29,7 @@ import {
} from '@/lib/richText';
import { skillNameToDirName } from '@/lib/skillToolkit';
import { cn } from '@/lib/utils';
import { useServerCapabilityStore } from '@/store/serverCapabilityStore';
import { useSkillsStore } from '@/store/skillsStore';
import { Check, Plus, Wrench } from 'lucide-react';
import { Fragment, useEffect, useMemo, useState, type ReactNode } from 'react';
@ -38,6 +44,8 @@ export interface PickerItem {
id: string;
name: string;
token: string;
/** Provider icon URL for Open Connector items. */
iconUrl?: string;
}
/** A labelled section within a picker (e.g. built-in vs. your own connectors). */
@ -205,9 +213,10 @@ interface WiredPickerPanelProps {
const EXCLUDED_BUILTIN_CONNECTORS = ['Search', 'RAG'];
/**
* Full connector list matching the Connectors settings page: built-in
* integrations (`/api/v1/config/info`) plus the user's own MCPs
* (`/api/v1/mcp/users`), shown as two labelled sections.
* Connected connectors only, matching the Connectors page sidebar: connected
* Open Connectors (when the Connector Gateway is enabled), connected built-in
* integrations (`/api/v1/config/info` + configs), and the user's enabled MCPs
* (`/api/v1/mcp/users`), shown as labelled sections.
*/
export function ConnectorPickerPanel({
inputValue,
@ -215,10 +224,27 @@ export function ConnectorPickerPanel({
}: WiredPickerPanelProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const [builtIn, setBuiltIn] = useState<PickerItem[]>([]);
const [builtInItems, setBuiltInItems] = useState<IntegrationItem[]>([]);
const [openItems, setOpenItems] = useState<PickerItem[]>([]);
const [yourMcps, setYourMcps] = useState<PickerItem[]>([]);
const [loading, setLoading] = useState(true);
const capabilities = useServerCapabilityStore((state) => state.capabilities);
const capabilityStatus = useServerCapabilityStore((state) => state.status);
const fetchCapabilities = useServerCapabilityStore(
(state) => state.fetchCapabilities
);
const gatewayEnabled =
capabilities.features.connector_gateway.enabled === true;
// Reuse the Connectors page's connected-state rules (OAuth tokens, config
// groups, Google Search defaults) instead of duplicating them here.
const { installed } = useIntegrationManagement(builtInItems);
useEffect(() => {
void fetchCapabilities();
}, [fetchCapabilities]);
useEffect(() => {
let cancelled = false;
Promise.allSettled([
@ -232,13 +258,15 @@ export function ConnectorPickerPanel({
infoRes.value &&
typeof infoRes.value === 'object'
) {
setBuiltIn(
setBuiltInItems(
Object.keys(infoRes.value)
.filter((key) => !EXCLUDED_BUILTIN_CONNECTORS.includes(key))
.map((key) => ({
id: `builtin-${key}`,
key,
name: key,
token: connectorNameToToken(key),
desc: '',
env_vars: [],
onInstall: () => undefined,
}))
);
}
@ -247,11 +275,13 @@ export function ConnectorPickerPanel({
? usersRes.value
: (usersRes.value?.items ?? []);
setYourMcps(
list.map((item: { id: number; mcp_name: string }) => ({
id: `user-${item.id}`,
name: item.mcp_name,
token: connectorNameToToken(item.mcp_name),
}))
list
.filter((item: { status?: number }) => Number(item.status) === 1)
.map((item: { id: number; mcp_name: string }) => ({
id: `user-${item.id}`,
name: item.mcp_name,
token: connectorNameToToken(item.mcp_name),
}))
);
}
})
@ -263,7 +293,46 @@ export function ConnectorPickerPanel({
};
}, []);
useEffect(() => {
if (capabilityStatus !== 'ready' || !gatewayEnabled) {
setOpenItems([]);
return;
}
let cancelled = false;
fetchConnectedProviders()
.then((providers) => {
if (cancelled) return;
setOpenItems(
providers.map((provider) => ({
id: `open-${provider.service}`,
name: providerLabel(provider),
token: connectorNameToToken(providerLabel(provider)),
iconUrl: provider.iconUrl || undefined,
}))
);
})
.catch(() => {
if (!cancelled) setOpenItems([]);
});
return () => {
cancelled = true;
};
}, [capabilityStatus, gatewayEnabled]);
const builtIn = useMemo(
() =>
builtInItems
.filter((item) => installed[item.key])
.map((item) => ({
id: `builtin-${item.key}`,
name: item.name,
token: connectorNameToToken(item.key),
})),
[builtInItems, installed]
);
const groups: PickerGroup[] = [
{ id: 'open', label: t('connectors.open-connectors'), items: openItems },
{
id: 'builtin',
label: t('setting.mcp-sidebar-built-in'),
@ -289,6 +358,13 @@ export function ConnectorPickerPanel({
</span>
)}
renderLogo={(item) => {
if (item.id.startsWith('open-')) {
return item.iconUrl ? (
<img src={item.iconUrl} alt="" className="h-4 w-4 object-contain" />
) : (
<img src={ellipseIcon} alt="" className="h-3 w-3" />
);
}
if (!item.id.startsWith('builtin-')) {
return (
<Wrench size={16} className="text-ds-icon-neutral-muted-default" />

View file

@ -0,0 +1,132 @@
{
"title": "الموصلات",
"search-placeholder": "البحث في موصلاتك",
"browse": "تصفح الموصلات",
"add-custom": "إضافة موصل مخصص",
"retry": "إعادة المحاولة",
"your-connectors": "موصلاتك",
"no-matching": "لا توجد موصلات مطابقة.",
"count-one": "موصل",
"count-other": "موصلات",
"recommended": "موصلات موصى بها لك.",
"no-recommended": "لا توجد موصلات موصى بها متاحة.",
"gateway-unavailable": "Open Connectors غير متاح.",
"gateway-unavailable-desc": "لم يتم تمكين Connector Gateway في هذا النشر.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "مدمج",
"source-local": "محلي",
"source-remote": "بعيد",
"load-built-in-failed": "تعذر تحميل الموصلات المدمجة",
"load-custom-failed": "تعذر تحميل الموصلات المخصصة",
"load-open-failed": "تعذر تحميل Open Connectors",
"disconnected": "تم قطع اتصال {{name}}",
"disconnect-failed": "تعذر قطع اتصال الموصل",
"update-failed": "تعذر تحديث الموصل",
"status-save-failed-enabled": "تم تمكين الموصل، ولكن تعذر حفظ حالته. حدّث الصفحة وحاول مرة أخرى.",
"status-save-failed-disabled": "تم تعطيل الموصل، ولكن تعذر حفظ حالته. حدّث الصفحة وحاول مرة أخرى.",
"updated": "تم تحديث الموصل",
"save-failed": "تعذر حفظ الموصل",
"deleted": "تم حذف الموصل",
"delete-failed": "تعذر حذف الموصل",
"enable-connector": "تمكين الموصل",
"disable-connector": "تعطيل الموصل",
"more-actions": "المزيد من الإجراءات",
"open": "فتح",
"edit": "تعديل",
"delete": "حذف",
"connected-account": "الحساب المتصل",
"supported-actions": "الإجراءات المدعومة",
"supported-actions-count": "{{num}} من الإجراءات المدعومة",
"show-more": "عرض المزيد",
"show-less": "عرض أقل",
"provider-website": "موقع المزود",
"built-in-title": "موصل توافق محلي",
"built-in-desc": "يستخدم هذا الموصل بيئة التكامل المحلية في Eigent. ويُعد Open Connectors السوق الأساسي للتكاملات المستضافة الجديدة.",
"configuration": "الإعداد: {{vars}}",
"status": "الحالة",
"active": "نشط",
"disabled": "معطّل",
"server-url": "عنوان URL للخادم",
"not-configured": "غير معدّ",
"command": "الأمر",
"arguments": "الوسائط",
"requires": "يتطلب {{vars}}",
"notion-desc": "اتصل بمساحة عمل Notion.",
"google-calendar-desc": "إدارة أحداث Google Calendar وجداوله.",
"generic-desc": "ربط {{name}} بـ Eigent.",
"google-search": "بحث Google",
"google-search-desc": "ربط Google Custom Search بمهام التصفح والبحث.",
"notion-install-failed": "تعذر تثبيت Notion",
"google-calendar-install-failed": "تعذر تثبيت Google Calendar",
"search-connectors": "البحث عن موصلات",
"auth-api-key": "مفتاح API",
"auth-credential": "بيانات الاعتماد",
"auth-oauth": "OAuth",
"auth-none": "دون مصادقة",
"unnamed-action": "إجراء بلا اسم",
"try-again": "حاول مرة أخرى",
"no-open-found": "لم يتم العثور على موصلات Open Connectors.",
"no-built-in-found": "لم يتم العثور على موصلات مدمجة.",
"loading": "جارٍ التحميل…",
"updating": "جارٍ التحديث…",
"loading-more": "جارٍ تحميل المزيد…",
"new": "جديد",
"installed": "مثبّت",
"local-integration": "تكامل محلي",
"authentication": "المصادقة",
"oauth-title": "الاتصال باستخدام OAuth",
"oauth-desc": "سيفتح Eigent صفحة تفويض المزود. ستُكمل نافذة الحوار هذه التثبيت عند اكتمال التفويض.",
"oauth-scopes": "الأذونات المطلوبة: {{scopes}}",
"no-auth-desc": "لا يتطلب هذا الموصل بيانات اعتماد.",
"waiting-authorization": "في انتظار اكتمال التفويض…",
"authorization-started": "بدأ التفويض",
"installed-toast": "تم تثبيت {{name}}",
"no-authorization-url": "لم يُرجع الموصل عنوان URL للتفويض",
"authorization-pending": "لا يزال التفويض معلقًا. أكمله في نافذة المزود، ثم حاول مرة أخرى.",
"install-failed": "تعذر تثبيت الموصل",
"detail-load-failed": "تعذر تحميل تفاصيل الموصل",
"authorization-incomplete": "لم يكتمل التفويض بعد. أكمله في نافذة المزود ثم حدّث الحالة مرة أخرى.",
"refresh-status-failed": "تعذر تحديث حالة الموصل",
"field-required": "الحقل {{field}} مطلوب",
"complete-authorization": "أكمل التفويض في نافذة المزود.",
"refresh-status": "تحديث الحالة",
"built-in-auth-desc": "يفتح التثبيت مسار تفويض المزود في نافذة منفصلة.",
"built-in-generic-desc": "تكامل Eigent محلي.",
"cancel": "إلغاء",
"install": "تثبيت",
"installing": "جارٍ التثبيت…",
"save": "حفظ",
"enter-value": "أدخل {{field}}",
"custom-title": "إضافة موصل مخصص",
"custom-subtitle": "ثبّت خادم MCP محليًا أو بعيدًا تثق به.",
"custom-warning": "يمكن لخوادم MCP المخصصة تنفيذ الأوامر أو الوصول إلى خدمات بعيدة. ثبّت الإعدادات التي تثق بها فقط.",
"local-json-desc": "أضف خادم MCP محليًا من خلال تقديم إعداد JSON صالح.",
"learn-more": "معرفة المزيد",
"connector-name": "اسم الموصل",
"remote-name-placeholder": "خادم MCP البعيد الخاص بي",
"remote-url": "عنوان URL لخادم MCP البعيد",
"remote-url-note": "استخدم HTTPS للخوادم الموجودة خارج شبكة محلية موثوقة.",
"invalid-json": "JSON غير صالح: {{message}}",
"json-format-error": "خطأ في تنسيق JSON: {{message}}",
"parse-failed": "فشل التحليل",
"missing-mcp-servers": "يجب أن يحتوي الإعداد على كائن mcpServers",
"add-at-least-one": "أضف خادم MCP واحدًا على الأقل",
"already-exists": "{{name}} موجود بالفعل",
"name-required": "اسم الموصل مطلوب",
"invalid-remote-url": "أدخل عنوان URL صالحًا لخادم MCP بعيد",
"remote-url-protocol": "يجب أن تستخدم عناوين URL لخوادم MCP البعيدة بروتوكول HTTP أو HTTPS",
"remote-missing-id": "تم إنشاء الموصل البعيد دون معرّف",
"custom-installed": "الموصلات المخصصة المثبّتة: {{num}}",
"install-custom-failed": "تعذر تثبيت الموصل المخصص",
"env-key-value": "متغيرات البيئة (مفتاح-قيمة)",
"value-placeholder": "القيمة",
"configuration-title": "الإعداد",
"google-api-key": "مفتاح Google API",
"google-api-key-placeholder": "أدخل مفتاح Google API من Google Cloud Console",
"google-api-key-note": "تعرّف على كيفية الحصول على مفتاح Google API ← https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "معرّف محرك البحث",
"search-engine-id-placeholder": "أدخل معرّف محرك البحث المخصص المرتبط بمفتاح API",
"google-search-custom-desc": "اتصل بـ Google Custom Search. يتطلب مفتاح Google API ومعرّف محرك بحث مخصص (CSE).",
"google-search-default-desc": "بحث Google مفعّل افتراضيًا. لا يلزم مفتاح API."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Konnektoren",
"search-placeholder": "Deine Konnektoren durchsuchen",
"browse": "Konnektoren durchsuchen",
"add-custom": "Benutzerdefinierten hinzufügen",
"retry": "Erneut versuchen",
"your-connectors": "Deine Konnektoren",
"no-matching": "Keine passenden Konnektoren.",
"count-one": "Konnektor",
"count-other": "Konnektoren",
"recommended": "Für dich empfohlene Konnektoren.",
"no-recommended": "Keine empfohlenen Konnektoren verfügbar.",
"gateway-unavailable": "Open Connectors ist nicht verfügbar.",
"gateway-unavailable-desc": "Für diese Bereitstellung ist das Connector Gateway nicht aktiviert.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Integriert",
"source-local": "Lokal",
"source-remote": "Remote",
"load-built-in-failed": "Integrierte Konnektoren konnten nicht geladen werden",
"load-custom-failed": "Benutzerdefinierte Konnektoren konnten nicht geladen werden",
"load-open-failed": "Open Connectors konnte nicht geladen werden",
"disconnected": "Verbindung zu {{name}} getrennt",
"disconnect-failed": "Konnektor konnte nicht getrennt werden",
"update-failed": "Konnektor konnte nicht aktualisiert werden",
"status-save-failed-enabled": "Der Konnektor wurde aktiviert, sein Status konnte jedoch nicht gespeichert werden. Aktualisiere die Seite und versuche es erneut.",
"status-save-failed-disabled": "Der Konnektor wurde deaktiviert, sein Status konnte jedoch nicht gespeichert werden. Aktualisiere die Seite und versuche es erneut.",
"updated": "Konnektor aktualisiert",
"save-failed": "Konnektor konnte nicht gespeichert werden",
"deleted": "Konnektor gelöscht",
"delete-failed": "Konnektor konnte nicht gelöscht werden",
"enable-connector": "Konnektor aktivieren",
"disable-connector": "Konnektor deaktivieren",
"more-actions": "Weitere Aktionen",
"open": "Öffnen",
"edit": "Bearbeiten",
"delete": "Löschen",
"connected-account": "Verbundenes Konto",
"supported-actions": "Unterstützte Aktionen",
"supported-actions-count": "{{num}} unterstützte Aktionen",
"show-more": "Mehr anzeigen",
"show-less": "Weniger anzeigen",
"provider-website": "Website des Anbieters",
"built-in-title": "Lokaler Kompatibilitätskonnektor",
"built-in-desc": "Dieser Konnektor verwendet die lokale Integrationslaufzeit von Eigent. Open Connectors ist der primäre Marktplatz für neue gehostete Integrationen.",
"configuration": "Konfiguration: {{vars}}",
"status": "Status",
"active": "Aktiv",
"disabled": "Deaktiviert",
"server-url": "Server-URL",
"not-configured": "Nicht konfiguriert",
"command": "Befehl",
"arguments": "Argumente",
"requires": "Erfordert {{vars}}",
"notion-desc": "Einen Notion-Arbeitsbereich verbinden.",
"google-calendar-desc": "Google-Kalendertermine und -zeitpläne verwalten.",
"generic-desc": "{{name}} mit Eigent verbinden.",
"google-search": "Google-Suche",
"google-search-desc": "Google Custom Search für Browser- und Rechercheaufgaben verbinden.",
"notion-install-failed": "Notion konnte nicht installiert werden",
"google-calendar-install-failed": "Google Kalender konnte nicht installiert werden",
"search-connectors": "Konnektoren suchen",
"auth-api-key": "API-Schlüssel",
"auth-credential": "Anmeldedaten",
"auth-oauth": "OAuth",
"auth-none": "Keine Authentifizierung",
"unnamed-action": "Unbenannte Aktion",
"try-again": "Erneut versuchen",
"no-open-found": "Keine Open Connectors gefunden.",
"no-built-in-found": "Keine integrierten Konnektoren gefunden.",
"loading": "Wird geladen…",
"updating": "Wird aktualisiert…",
"loading-more": "Weitere werden geladen…",
"new": "Neu",
"installed": "Installiert",
"local-integration": "Lokale Integration",
"authentication": "Authentifizierung",
"oauth-title": "Mit OAuth verbinden",
"oauth-desc": "Eigent öffnet die Autorisierungsseite des Anbieters. Dieses Dialogfeld schließt die Installation ab, sobald die Autorisierung abgeschlossen ist.",
"oauth-scopes": "Angeforderte Berechtigungen: {{scopes}}",
"no-auth-desc": "Dieser Konnektor benötigt keine Anmeldedaten.",
"waiting-authorization": "Warten auf Abschluss der Autorisierung…",
"authorization-started": "Autorisierung gestartet",
"installed-toast": "{{name}} installiert",
"no-authorization-url": "Der Konnektor hat keine Autorisierungs-URL zurückgegeben",
"authorization-pending": "Die Autorisierung steht noch aus. Schließe sie im Anbieterfenster ab und versuche es erneut.",
"install-failed": "Konnektor konnte nicht installiert werden",
"detail-load-failed": "Konnektordetails konnten nicht geladen werden",
"authorization-incomplete": "Die Autorisierung ist noch nicht abgeschlossen. Schließe sie im Anbieterfenster ab und aktualisiere den Status erneut.",
"refresh-status-failed": "Konnektorstatus konnte nicht aktualisiert werden",
"field-required": "{{field}} ist erforderlich",
"complete-authorization": "Schließe die Autorisierung im Anbieterfenster ab.",
"refresh-status": "Status aktualisieren",
"built-in-auth-desc": "Bei der Installation wird der Autorisierungsvorgang des Anbieters in einem separaten Fenster geöffnet.",
"built-in-generic-desc": "Eine lokale Eigent-Integration.",
"cancel": "Abbrechen",
"install": "Installieren",
"installing": "Wird installiert…",
"save": "Speichern",
"enter-value": "{{field}} eingeben",
"custom-title": "Benutzerdefinierten Konnektor hinzufügen",
"custom-subtitle": "Installiere einen vertrauenswürdigen lokalen oder Remote-MCP-Server.",
"custom-warning": "Benutzerdefinierte MCP-Server können Befehle ausführen oder auf Remote-Dienste zugreifen. Installiere nur Konfigurationen, denen du vertraust.",
"local-json-desc": "Füge einen lokalen MCP-Server mit einer gültigen JSON-Konfiguration hinzu.",
"learn-more": "Mehr erfahren",
"connector-name": "Konnektorname",
"remote-name-placeholder": "Mein Remote-MCP",
"remote-url": "Remote-MCP-URL",
"remote-url-note": "Verwende HTTPS für Server außerhalb eines vertrauenswürdigen lokalen Netzwerks.",
"invalid-json": "Ungültiges JSON: {{message}}",
"json-format-error": "JSON-Formatfehler: {{message}}",
"parse-failed": "Analyse fehlgeschlagen",
"missing-mcp-servers": "Die Konfiguration muss ein mcpServers-Objekt enthalten",
"add-at-least-one": "Füge mindestens einen MCP-Server hinzu",
"already-exists": "{{name}} ist bereits vorhanden",
"name-required": "Der Konnektorname ist erforderlich",
"invalid-remote-url": "Gib eine gültige Remote-MCP-URL ein",
"remote-url-protocol": "Remote-MCP-URLs müssen HTTP oder HTTPS verwenden",
"remote-missing-id": "Der Remote-Konnektor wurde ohne ID erstellt",
"custom-installed": "Installierte benutzerdefinierte Konnektoren: {{num}}",
"install-custom-failed": "Benutzerdefinierter Konnektor konnte nicht installiert werden",
"env-key-value": "Umgebungsvariablen (Schlüssel-Wert)",
"value-placeholder": "Wert",
"configuration-title": "Konfiguration",
"google-api-key": "Google-API-Schlüssel",
"google-api-key-placeholder": "Google-API-Schlüssel aus der Google Cloud Console eingeben",
"google-api-key-note": "So erhältst du deinen Google-API-Schlüssel → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "Suchmaschinen-ID",
"search-engine-id-placeholder": "Die mit deinem API-Schlüssel verknüpfte ID der benutzerdefinierten Suchmaschine eingeben",
"google-search-custom-desc": "Mit Google Custom Search verbinden. Erfordert einen Google-API-Schlüssel und eine ID der benutzerdefinierten Suchmaschine (CSE).",
"google-search-default-desc": "Die Google-Suche ist standardmäßig aktiviert. Kein API-Schlüssel erforderlich."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Connectors",
"search-placeholder": "Search your connectors",
"browse": "Browse connectors",
"add-custom": "Add custom",
"retry": "Retry",
"your-connectors": "Your connectors",
"no-matching": "No matching connectors.",
"count-one": "connector",
"count-other": "connectors",
"recommended": "Recommended connectors for you.",
"no-recommended": "No recommended connectors available.",
"gateway-unavailable": "Open Connectors is unavailable.",
"gateway-unavailable-desc": "This deployment does not have the Connector Gateway enabled.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Built-in",
"source-local": "Local",
"source-remote": "Remote",
"load-built-in-failed": "Failed to load Built-in connectors",
"load-custom-failed": "Failed to load Custom connectors",
"load-open-failed": "Failed to load Open Connectors",
"disconnected": "{{name}} disconnected",
"disconnect-failed": "Failed to disconnect connector",
"update-failed": "Failed to update connector",
"status-save-failed-enabled": "Connector enabled, but saving its state failed. Refresh and try again.",
"status-save-failed-disabled": "Connector disabled, but saving its state failed. Refresh and try again.",
"updated": "Connector updated",
"save-failed": "Failed to save connector",
"deleted": "Connector deleted",
"delete-failed": "Failed to delete connector",
"enable-connector": "Enable connector",
"disable-connector": "Disable connector",
"more-actions": "More actions",
"open": "Open",
"edit": "Edit",
"delete": "Delete",
"connected-account": "Connected account",
"supported-actions": "Supported actions",
"supported-actions-count": "{{num}} supported actions",
"show-more": "Show more",
"show-less": "Show less",
"provider-website": "Provider website",
"built-in-title": "Local compatibility connector",
"built-in-desc": "This connector uses Eigent's local integration runtime. Open Connectors is the primary marketplace for new hosted integrations.",
"configuration": "Configuration: {{vars}}",
"status": "Status",
"active": "Active",
"disabled": "Disabled",
"server-url": "Server URL",
"not-configured": "Not configured",
"command": "Command",
"arguments": "Arguments",
"requires": "Requires {{vars}}",
"notion-desc": "Connect a Notion workspace.",
"google-calendar-desc": "Manage Google Calendar events and schedules.",
"generic-desc": "Connect {{name}} to Eigent.",
"google-search": "Google Search",
"google-search-desc": "Connect Google Custom Search for browser and research tasks.",
"notion-install-failed": "Failed to install Notion",
"google-calendar-install-failed": "Failed to install Google Calendar",
"search-connectors": "Search connectors",
"auth-api-key": "API key",
"auth-credential": "Credential",
"auth-oauth": "OAuth",
"auth-none": "No authentication",
"unnamed-action": "Unnamed action",
"try-again": "Try again",
"no-open-found": "No Open Connectors found.",
"no-built-in-found": "No Built-in connectors found.",
"loading": "Loading…",
"updating": "Updating…",
"loading-more": "Loading more…",
"new": "New",
"installed": "Installed",
"local-integration": "Local integration",
"authentication": "Authentication",
"oauth-title": "Connect with OAuth",
"oauth-desc": "Eigent will open the provider authorization page. This dialog will finish installation when authorization is complete.",
"oauth-scopes": "Requested scopes: {{scopes}}",
"no-auth-desc": "This connector does not require credentials.",
"waiting-authorization": "Waiting for authorization to complete…",
"authorization-started": "Authorization started",
"installed-toast": "{{name}} installed",
"no-authorization-url": "The connector did not return an authorization URL",
"authorization-pending": "Authorization is still pending. Complete it in the provider window, then try again.",
"install-failed": "Failed to install connector",
"detail-load-failed": "Failed to load connector details",
"authorization-incomplete": "Authorization has not completed yet. Finish it in the provider window and refresh again.",
"refresh-status-failed": "Failed to refresh connector status",
"field-required": "{{field}} is required",
"complete-authorization": "Complete authorization in the provider window.",
"refresh-status": "Refresh status",
"built-in-auth-desc": "Installation opens the provider authorization flow in a separate window.",
"built-in-generic-desc": "A local Eigent integration.",
"cancel": "Cancel",
"install": "Install",
"installing": "Installing…",
"save": "Save",
"enter-value": "Enter {{field}}",
"custom-title": "Add custom connector",
"custom-subtitle": "Install a local or remote MCP server you trust.",
"custom-warning": "Custom MCP servers can execute commands or access remote services. Only install configurations you trust.",
"local-json-desc": "Add a local MCP server by providing a valid JSON configuration.",
"learn-more": "Learn more",
"connector-name": "Connector name",
"remote-name-placeholder": "My remote MCP",
"remote-url": "Remote MCP URL",
"remote-url-note": "Use HTTPS for servers outside a trusted local network.",
"invalid-json": "Invalid JSON: {{message}}",
"json-format-error": "JSON format error: {{message}}",
"parse-failed": "Parsing failed",
"missing-mcp-servers": "Configuration must contain an mcpServers object",
"add-at-least-one": "Add at least one MCP server",
"already-exists": "{{name}} already exists",
"name-required": "Connector name is required",
"invalid-remote-url": "Enter a valid remote MCP URL",
"remote-url-protocol": "Remote MCP URLs must use HTTP or HTTPS",
"remote-missing-id": "Remote connector was created without an ID",
"custom-installed": "Custom connectors installed: {{num}}",
"install-custom-failed": "Failed to install custom connector",
"env-key-value": "Env (key-value)",
"value-placeholder": "Value",
"configuration-title": "Configuration",
"google-api-key": "Google API Key",
"google-api-key-placeholder": "Enter your Google API key from Google Cloud Console",
"google-api-key-note": "Learn how to get your Google API key → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "Search Engine ID",
"search-engine-id-placeholder": "Enter the Custom Search Engine ID associated with your API key",
"google-search-custom-desc": "Connect to Google Custom Search. Requires a Google API key and a Custom Search Engine (CSE) ID.",
"google-search-default-desc": "Google Search is enabled by default. No API key required."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Conectores",
"search-placeholder": "Busca tus conectores",
"browse": "Explorar conectores",
"add-custom": "Añadir personalizado",
"retry": "Reintentar",
"your-connectors": "Tus conectores",
"no-matching": "No hay conectores coincidentes.",
"count-one": "conector",
"count-other": "conectores",
"recommended": "Conectores recomendados para ti.",
"no-recommended": "No hay conectores recomendados disponibles.",
"gateway-unavailable": "Open Connectors no está disponible.",
"gateway-unavailable-desc": "Esta implementación no tiene habilitado el Connector Gateway.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Integrado",
"source-local": "Local",
"source-remote": "Remoto",
"load-built-in-failed": "Error al cargar los conectores integrados",
"load-custom-failed": "Error al cargar los conectores personalizados",
"load-open-failed": "Error al cargar Open Connectors",
"disconnected": "{{name}} desconectado",
"disconnect-failed": "Error al desconectar el conector",
"update-failed": "Error al actualizar el conector",
"status-save-failed-enabled": "Conector habilitado, pero no se pudo guardar su estado. Actualiza e inténtalo de nuevo.",
"status-save-failed-disabled": "Conector deshabilitado, pero no se pudo guardar su estado. Actualiza e inténtalo de nuevo.",
"updated": "Conector actualizado",
"save-failed": "Error al guardar el conector",
"deleted": "Conector eliminado",
"delete-failed": "Error al eliminar el conector",
"enable-connector": "Habilitar conector",
"disable-connector": "Deshabilitar conector",
"more-actions": "Más acciones",
"open": "Abrir",
"edit": "Editar",
"delete": "Eliminar",
"connected-account": "Cuenta conectada",
"supported-actions": "Acciones compatibles",
"supported-actions-count": "{{num}} acciones compatibles",
"show-more": "Mostrar más",
"show-less": "Mostrar menos",
"provider-website": "Sitio web del proveedor",
"built-in-title": "Conector de compatibilidad local",
"built-in-desc": "Este conector usa el entorno de integración local de Eigent. Open Connectors es el mercado principal para nuevas integraciones alojadas.",
"configuration": "Configuración: {{vars}}",
"status": "Estado",
"active": "Activo",
"disabled": "Deshabilitado",
"server-url": "URL del servidor",
"not-configured": "Sin configurar",
"command": "Comando",
"arguments": "Argumentos",
"requires": "Requiere {{vars}}",
"notion-desc": "Conecta un espacio de trabajo de Notion.",
"google-calendar-desc": "Gestiona eventos y horarios de Google Calendar.",
"generic-desc": "Conecta {{name}} a Eigent.",
"google-search": "Búsqueda de Google",
"google-search-desc": "Conecta Google Custom Search para tareas de navegación e investigación.",
"notion-install-failed": "Error al instalar Notion",
"google-calendar-install-failed": "Error al instalar Google Calendar",
"search-connectors": "Buscar conectores",
"auth-api-key": "Clave API",
"auth-credential": "Credencial",
"auth-oauth": "OAuth",
"auth-none": "Sin autenticación",
"unnamed-action": "Acción sin nombre",
"try-again": "Intentar de nuevo",
"no-open-found": "No se encontraron Open Connectors.",
"no-built-in-found": "No se encontraron conectores integrados.",
"loading": "Cargando…",
"updating": "Actualizando…",
"loading-more": "Cargando más…",
"new": "Nuevo",
"installed": "Instalado",
"local-integration": "Integración local",
"authentication": "Autenticación",
"oauth-title": "Conectar con OAuth",
"oauth-desc": "Eigent abrirá la página de autorización del proveedor. Este cuadro de diálogo finalizará la instalación cuando se complete la autorización.",
"oauth-scopes": "Permisos solicitados: {{scopes}}",
"no-auth-desc": "Este conector no requiere credenciales.",
"waiting-authorization": "Esperando a que se complete la autorización…",
"authorization-started": "Autorización iniciada",
"installed-toast": "{{name}} instalado",
"no-authorization-url": "El conector no devolvió una URL de autorización",
"authorization-pending": "La autorización sigue pendiente. Complétala en la ventana del proveedor e inténtalo de nuevo.",
"install-failed": "Error al instalar el conector",
"detail-load-failed": "Error al cargar los detalles del conector",
"authorization-incomplete": "La autorización aún no se ha completado. Termínala en la ventana del proveedor y actualiza de nuevo.",
"refresh-status-failed": "Error al actualizar el estado del conector",
"field-required": "{{field}} es obligatorio",
"complete-authorization": "Completa la autorización en la ventana del proveedor.",
"refresh-status": "Actualizar estado",
"built-in-auth-desc": "La instalación abre el flujo de autorización del proveedor en una ventana aparte.",
"built-in-generic-desc": "Una integración local de Eigent.",
"cancel": "Cancelar",
"install": "Instalar",
"installing": "Instalando…",
"save": "Guardar",
"enter-value": "Introduce {{field}}",
"custom-title": "Añadir conector personalizado",
"custom-subtitle": "Instala un servidor MCP local o remoto de confianza.",
"custom-warning": "Los servidores MCP personalizados pueden ejecutar comandos o acceder a servicios remotos. Instala solo configuraciones de confianza.",
"local-json-desc": "Añade un servidor MCP local proporcionando una configuración JSON válida.",
"learn-more": "Más información",
"connector-name": "Nombre del conector",
"remote-name-placeholder": "Mi MCP remoto",
"remote-url": "URL del MCP remoto",
"remote-url-note": "Usa HTTPS para servidores fuera de una red local de confianza.",
"invalid-json": "JSON no válido: {{message}}",
"json-format-error": "Error de formato JSON: {{message}}",
"parse-failed": "Error al analizar",
"missing-mcp-servers": "La configuración debe contener un objeto mcpServers",
"add-at-least-one": "Añade al menos un servidor MCP",
"already-exists": "{{name}} ya existe",
"name-required": "El nombre del conector es obligatorio",
"invalid-remote-url": "Introduce una URL de MCP remoto válida",
"remote-url-protocol": "Las URL de MCP remoto deben usar HTTP o HTTPS",
"remote-missing-id": "El conector remoto se creó sin un ID",
"custom-installed": "Conectores personalizados instalados: {{num}}",
"install-custom-failed": "Error al instalar el conector personalizado",
"env-key-value": "Variables de entorno (clave-valor)",
"value-placeholder": "Valor",
"configuration-title": "Configuración",
"google-api-key": "Clave de API de Google",
"google-api-key-placeholder": "Introduce tu clave de API de Google Cloud Console",
"google-api-key-note": "Cómo obtener tu clave de API de Google → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "ID del motor de búsqueda",
"search-engine-id-placeholder": "Introduce el ID del motor de búsqueda personalizado asociado a tu clave de API",
"google-search-custom-desc": "Conecta Google Custom Search. Requiere una clave de API de Google y un ID de motor de búsqueda personalizado (CSE).",
"google-search-default-desc": "Google Search está habilitado de forma predeterminada. No se requiere una clave de API."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Connecteurs",
"search-placeholder": "Rechercher dans vos connecteurs",
"browse": "Parcourir les connecteurs",
"add-custom": "Ajouter un connecteur personnalisé",
"retry": "Réessayer",
"your-connectors": "Vos connecteurs",
"no-matching": "Aucun connecteur correspondant.",
"count-one": "connecteur",
"count-other": "connecteurs",
"recommended": "Connecteurs recommandés pour vous.",
"no-recommended": "Aucun connecteur recommandé disponible.",
"gateway-unavailable": "Open Connectors nest pas disponible.",
"gateway-unavailable-desc": "Connector Gateway nest pas activé pour ce déploiement.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Intégré",
"source-local": "Local",
"source-remote": "Distant",
"load-built-in-failed": "Échec du chargement des connecteurs intégrés",
"load-custom-failed": "Échec du chargement des connecteurs personnalisés",
"load-open-failed": "Échec du chargement dOpen Connectors",
"disconnected": "{{name}} déconnecté",
"disconnect-failed": "Échec de la déconnexion du connecteur",
"update-failed": "Échec de la mise à jour du connecteur",
"status-save-failed-enabled": "Le connecteur a été activé, mais son état na pas pu être enregistré. Actualisez la page et réessayez.",
"status-save-failed-disabled": "Le connecteur a été désactivé, mais son état na pas pu être enregistré. Actualisez la page et réessayez.",
"updated": "Connecteur mis à jour",
"save-failed": "Échec de lenregistrement du connecteur",
"deleted": "Connecteur supprimé",
"delete-failed": "Échec de la suppression du connecteur",
"enable-connector": "Activer le connecteur",
"disable-connector": "Désactiver le connecteur",
"more-actions": "Plus dactions",
"open": "Ouvrir",
"edit": "Modifier",
"delete": "Supprimer",
"connected-account": "Compte connecté",
"supported-actions": "Actions prises en charge",
"supported-actions-count": "{{num}} actions prises en charge",
"show-more": "Afficher plus",
"show-less": "Afficher moins",
"provider-website": "Site web du fournisseur",
"built-in-title": "Connecteur de compatibilité locale",
"built-in-desc": "Ce connecteur utilise lenvironnement dintégration local dEigent. Open Connectors est la place de marché principale pour les nouvelles intégrations hébergées.",
"configuration": "Configuration : {{vars}}",
"status": "État",
"active": "Actif",
"disabled": "Désactivé",
"server-url": "URL du serveur",
"not-configured": "Non configuré",
"command": "Commande",
"arguments": "Arguments",
"requires": "Nécessite {{vars}}",
"notion-desc": "Connecter un espace de travail Notion.",
"google-calendar-desc": "Gérer les événements et les calendriers Google Agenda.",
"generic-desc": "Connecter {{name}} à Eigent.",
"google-search": "Recherche Google",
"google-search-desc": "Connecter Google Custom Search pour les tâches de navigation et de recherche.",
"notion-install-failed": "Échec de linstallation de Notion",
"google-calendar-install-failed": "Échec de linstallation de Google Agenda",
"search-connectors": "Rechercher des connecteurs",
"auth-api-key": "Clé API",
"auth-credential": "Identifiants",
"auth-oauth": "OAuth",
"auth-none": "Aucune authentification",
"unnamed-action": "Action sans nom",
"try-again": "Réessayer",
"no-open-found": "Aucun Open Connector trouvé.",
"no-built-in-found": "Aucun connecteur intégré trouvé.",
"loading": "Chargement…",
"updating": "Mise à jour…",
"loading-more": "Chargement dautres éléments…",
"new": "Nouveau",
"installed": "Installé",
"local-integration": "Intégration locale",
"authentication": "Authentification",
"oauth-title": "Se connecter avec OAuth",
"oauth-desc": "Eigent ouvrira la page dautorisation du fournisseur. Cette boîte de dialogue terminera linstallation une fois lautorisation accordée.",
"oauth-scopes": "Autorisations demandées : {{scopes}}",
"no-auth-desc": "Ce connecteur ne nécessite aucun identifiant.",
"waiting-authorization": "En attente de lautorisation…",
"authorization-started": "Autorisation démarrée",
"installed-toast": "{{name}} installé",
"no-authorization-url": "Le connecteur na pas renvoyé dURL dautorisation",
"authorization-pending": "Lautorisation est toujours en attente. Terminez-la dans la fenêtre du fournisseur, puis réessayez.",
"install-failed": "Échec de linstallation du connecteur",
"detail-load-failed": "Échec du chargement des détails du connecteur",
"authorization-incomplete": "Lautorisation nest pas encore terminée. Terminez-la dans la fenêtre du fournisseur et actualisez à nouveau létat.",
"refresh-status-failed": "Échec de lactualisation de létat du connecteur",
"field-required": "{{field}} est requis",
"complete-authorization": "Terminez lautorisation dans la fenêtre du fournisseur.",
"refresh-status": "Actualiser létat",
"built-in-auth-desc": "Linstallation ouvre le processus dautorisation du fournisseur dans une fenêtre séparée.",
"built-in-generic-desc": "Une intégration Eigent locale.",
"cancel": "Annuler",
"install": "Installer",
"installing": "Installation…",
"save": "Enregistrer",
"enter-value": "Saisir {{field}}",
"custom-title": "Ajouter un connecteur personnalisé",
"custom-subtitle": "Installez un serveur MCP local ou distant auquel vous faites confiance.",
"custom-warning": "Les serveurs MCP personnalisés peuvent exécuter des commandes ou accéder à des services distants. Installez uniquement des configurations auxquelles vous faites confiance.",
"local-json-desc": "Ajoutez un serveur MCP local en fournissant une configuration JSON valide.",
"learn-more": "En savoir plus",
"connector-name": "Nom du connecteur",
"remote-name-placeholder": "Mon MCP distant",
"remote-url": "URL du MCP distant",
"remote-url-note": "Utilisez HTTPS pour les serveurs situés hors dun réseau local de confiance.",
"invalid-json": "JSON non valide : {{message}}",
"json-format-error": "Erreur de format JSON : {{message}}",
"parse-failed": "Échec de lanalyse",
"missing-mcp-servers": "La configuration doit contenir un objet mcpServers",
"add-at-least-one": "Ajoutez au moins un serveur MCP",
"already-exists": "{{name}} existe déjà",
"name-required": "Le nom du connecteur est requis",
"invalid-remote-url": "Saisissez une URL de MCP distant valide",
"remote-url-protocol": "Les URL de MCP distant doivent utiliser HTTP ou HTTPS",
"remote-missing-id": "Le connecteur distant a été créé sans identifiant",
"custom-installed": "Connecteurs personnalisés installés : {{num}}",
"install-custom-failed": "Échec de linstallation du connecteur personnalisé",
"env-key-value": "Variables denvironnement (clé-valeur)",
"value-placeholder": "Valeur",
"configuration-title": "Configuration",
"google-api-key": "Clé API Google",
"google-api-key-placeholder": "Saisissez votre clé API Google depuis Google Cloud Console",
"google-api-key-note": "Comment obtenir votre clé API Google → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "Identifiant du moteur de recherche",
"search-engine-id-placeholder": "Saisissez lidentifiant du moteur de recherche personnalisé associé à votre clé API",
"google-search-custom-desc": "Connectez Google Custom Search. Une clé API Google et un identifiant de moteur de recherche personnalisé (CSE) sont requis.",
"google-search-default-desc": "La recherche Google est activée par défaut. Aucune clé API nest requise."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Connettori",
"search-placeholder": "Cerca nei tuoi connettori",
"browse": "Sfoglia i connettori",
"add-custom": "Aggiungi personalizzato",
"retry": "Riprova",
"your-connectors": "I tuoi connettori",
"no-matching": "Nessun connettore corrispondente.",
"count-one": "connettore",
"count-other": "connettori",
"recommended": "Connettori consigliati per te.",
"no-recommended": "Nessun connettore consigliato disponibile.",
"gateway-unavailable": "Open Connectors non è disponibile.",
"gateway-unavailable-desc": "Connector Gateway non è abilitato per questa distribuzione.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Integrato",
"source-local": "Locale",
"source-remote": "Remoto",
"load-built-in-failed": "Impossibile caricare i connettori integrati",
"load-custom-failed": "Impossibile caricare i connettori personalizzati",
"load-open-failed": "Impossibile caricare Open Connectors",
"disconnected": "{{name}} disconnesso",
"disconnect-failed": "Impossibile disconnettere il connettore",
"update-failed": "Impossibile aggiornare il connettore",
"status-save-failed-enabled": "Il connettore è stato abilitato, ma non è stato possibile salvarne lo stato. Aggiorna la pagina e riprova.",
"status-save-failed-disabled": "Il connettore è stato disabilitato, ma non è stato possibile salvarne lo stato. Aggiorna la pagina e riprova.",
"updated": "Connettore aggiornato",
"save-failed": "Impossibile salvare il connettore",
"deleted": "Connettore eliminato",
"delete-failed": "Impossibile eliminare il connettore",
"enable-connector": "Abilita connettore",
"disable-connector": "Disabilita connettore",
"more-actions": "Altre azioni",
"open": "Apri",
"edit": "Modifica",
"delete": "Elimina",
"connected-account": "Account connesso",
"supported-actions": "Azioni supportate",
"supported-actions-count": "{{num}} azioni supportate",
"show-more": "Mostra altro",
"show-less": "Mostra meno",
"provider-website": "Sito web del fornitore",
"built-in-title": "Connettore di compatibilità locale",
"built-in-desc": "Questo connettore utilizza lambiente di integrazione locale di Eigent. Open Connectors è il marketplace principale per le nuove integrazioni ospitate.",
"configuration": "Configurazione: {{vars}}",
"status": "Stato",
"active": "Attivo",
"disabled": "Disabilitato",
"server-url": "URL del server",
"not-configured": "Non configurato",
"command": "Comando",
"arguments": "Argomenti",
"requires": "Richiede {{vars}}",
"notion-desc": "Connetti uno spazio di lavoro Notion.",
"google-calendar-desc": "Gestisci eventi e pianificazioni di Google Calendar.",
"generic-desc": "Connetti {{name}} a Eigent.",
"google-search": "Ricerca Google",
"google-search-desc": "Connetti Google Custom Search per attività di navigazione e ricerca.",
"notion-install-failed": "Impossibile installare Notion",
"google-calendar-install-failed": "Impossibile installare Google Calendar",
"search-connectors": "Cerca connettori",
"auth-api-key": "Chiave API",
"auth-credential": "Credenziale",
"auth-oauth": "OAuth",
"auth-none": "Nessuna autenticazione",
"unnamed-action": "Azione senza nome",
"try-again": "Riprova",
"no-open-found": "Nessun Open Connector trovato.",
"no-built-in-found": "Nessun connettore integrato trovato.",
"loading": "Caricamento…",
"updating": "Aggiornamento…",
"loading-more": "Caricamento di altri elementi…",
"new": "Nuovo",
"installed": "Installato",
"local-integration": "Integrazione locale",
"authentication": "Autenticazione",
"oauth-title": "Connetti con OAuth",
"oauth-desc": "Eigent aprirà la pagina di autorizzazione del fornitore. Questa finestra completerà linstallazione al termine dellautorizzazione.",
"oauth-scopes": "Autorizzazioni richieste: {{scopes}}",
"no-auth-desc": "Questo connettore non richiede credenziali.",
"waiting-authorization": "In attesa del completamento dellautorizzazione…",
"authorization-started": "Autorizzazione avviata",
"installed-toast": "{{name}} installato",
"no-authorization-url": "Il connettore non ha restituito un URL di autorizzazione",
"authorization-pending": "Lautorizzazione è ancora in sospeso. Completala nella finestra del fornitore, quindi riprova.",
"install-failed": "Impossibile installare il connettore",
"detail-load-failed": "Impossibile caricare i dettagli del connettore",
"authorization-incomplete": "Lautorizzazione non è ancora completa. Completala nella finestra del fornitore e aggiorna nuovamente lo stato.",
"refresh-status-failed": "Impossibile aggiornare lo stato del connettore",
"field-required": "{{field}} è obbligatorio",
"complete-authorization": "Completa lautorizzazione nella finestra del fornitore.",
"refresh-status": "Aggiorna stato",
"built-in-auth-desc": "Linstallazione apre il flusso di autorizzazione del fornitore in una finestra separata.",
"built-in-generic-desc": "Unintegrazione locale di Eigent.",
"cancel": "Annulla",
"install": "Installa",
"installing": "Installazione…",
"save": "Salva",
"enter-value": "Inserisci {{field}}",
"custom-title": "Aggiungi connettore personalizzato",
"custom-subtitle": "Installa un server MCP locale o remoto attendibile.",
"custom-warning": "I server MCP personalizzati possono eseguire comandi o accedere a servizi remoti. Installa solo configurazioni attendibili.",
"local-json-desc": "Aggiungi un server MCP locale fornendo una configurazione JSON valida.",
"learn-more": "Scopri di più",
"connector-name": "Nome del connettore",
"remote-name-placeholder": "Il mio MCP remoto",
"remote-url": "URL MCP remoto",
"remote-url-note": "Usa HTTPS per i server esterni a una rete locale attendibile.",
"invalid-json": "JSON non valido: {{message}}",
"json-format-error": "Errore di formato JSON: {{message}}",
"parse-failed": "Analisi non riuscita",
"missing-mcp-servers": "La configurazione deve contenere un oggetto mcpServers",
"add-at-least-one": "Aggiungi almeno un server MCP",
"already-exists": "{{name}} esiste già",
"name-required": "Il nome del connettore è obbligatorio",
"invalid-remote-url": "Inserisci un URL MCP remoto valido",
"remote-url-protocol": "Gli URL MCP remoti devono utilizzare HTTP o HTTPS",
"remote-missing-id": "Il connettore remoto è stato creato senza ID",
"custom-installed": "Connettori personalizzati installati: {{num}}",
"install-custom-failed": "Impossibile installare il connettore personalizzato",
"env-key-value": "Variabili dambiente (chiave-valore)",
"value-placeholder": "Valore",
"configuration-title": "Configurazione",
"google-api-key": "Chiave API Google",
"google-api-key-placeholder": "Inserisci la chiave API Google da Google Cloud Console",
"google-api-key-note": "Come ottenere la chiave API Google → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "ID del motore di ricerca",
"search-engine-id-placeholder": "Inserisci lID del motore di ricerca personalizzato associato alla chiave API",
"google-search-custom-desc": "Connetti Google Custom Search. Sono richiesti una chiave API Google e un ID del motore di ricerca personalizzato (CSE).",
"google-search-default-desc": "La Ricerca Google è abilitata per impostazione predefinita. Non è richiesta alcuna chiave API."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "コネクタ",
"search-placeholder": "コネクタを検索",
"browse": "コネクタを探す",
"add-custom": "カスタムを追加",
"retry": "再試行",
"your-connectors": "あなたのコネクタ",
"no-matching": "一致するコネクタがありません。",
"count-one": "件のコネクタ",
"count-other": "件のコネクタ",
"recommended": "あなたへのおすすめコネクタ。",
"no-recommended": "おすすめのコネクタはありません。",
"gateway-unavailable": "Open Connectors は利用できません。",
"gateway-unavailable-desc": "このデプロイでは Connector Gateway が有効になっていません。",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "組み込み",
"source-local": "ローカル",
"source-remote": "リモート",
"load-built-in-failed": "組み込みコネクタの読み込みに失敗しました",
"load-custom-failed": "カスタムコネクタの読み込みに失敗しました",
"load-open-failed": "Open Connectors の読み込みに失敗しました",
"disconnected": "{{name}} の接続を解除しました",
"disconnect-failed": "コネクタの接続解除に失敗しました",
"update-failed": "コネクタの更新に失敗しました",
"status-save-failed-enabled": "コネクタは有効になりましたが、状態の保存に失敗しました。更新して再試行してください。",
"status-save-failed-disabled": "コネクタは無効になりましたが、状態の保存に失敗しました。更新して再試行してください。",
"updated": "コネクタを更新しました",
"save-failed": "コネクタの保存に失敗しました",
"deleted": "コネクタを削除しました",
"delete-failed": "コネクタの削除に失敗しました",
"enable-connector": "コネクタを有効化",
"disable-connector": "コネクタを無効化",
"more-actions": "その他の操作",
"open": "開く",
"edit": "編集",
"delete": "削除",
"connected-account": "接続中のアカウント",
"supported-actions": "対応アクション",
"supported-actions-count": "{{num}} 件の対応アクション",
"show-more": "もっと見る",
"show-less": "折りたたむ",
"provider-website": "プロバイダーのウェブサイト",
"built-in-title": "ローカル互換コネクタ",
"built-in-desc": "このコネクタは Eigent のローカル統合ランタイムを使用します。新しいホスト型統合は Open Connectors が主要なマーケットプレイスです。",
"configuration": "設定項目:{{vars}}",
"status": "ステータス",
"active": "有効",
"disabled": "無効",
"server-url": "サーバー URL",
"not-configured": "未設定",
"command": "コマンド",
"arguments": "引数",
"requires": "{{vars}} が必要です",
"notion-desc": "Notion ワークスペースを接続します。",
"google-calendar-desc": "Google カレンダーの予定とスケジュールを管理します。",
"generic-desc": "{{name}} を Eigent に接続します。",
"google-search": "Google 検索",
"google-search-desc": "ブラウザやリサーチタスク用に Google カスタム検索を接続します。",
"notion-install-failed": "Notion のインストールに失敗しました",
"google-calendar-install-failed": "Google カレンダーのインストールに失敗しました",
"search-connectors": "コネクタを検索",
"auth-api-key": "API キー",
"auth-credential": "認証情報",
"auth-oauth": "OAuth",
"auth-none": "認証不要",
"unnamed-action": "名称未設定のアクション",
"try-again": "再試行",
"no-open-found": "Open Connectors が見つかりません。",
"no-built-in-found": "組み込みコネクタが見つかりません。",
"loading": "読み込み中…",
"updating": "更新中…",
"loading-more": "さらに読み込み中…",
"new": "新着",
"installed": "インストール済み",
"local-integration": "ローカル統合",
"authentication": "認証",
"oauth-title": "OAuth で接続",
"oauth-desc": "Eigent がプロバイダーの認可ページを開きます。認可が完了すると、このダイアログでインストールが完了します。",
"oauth-scopes": "要求されるスコープ:{{scopes}}",
"no-auth-desc": "このコネクタに認証情報は不要です。",
"waiting-authorization": "認可の完了を待っています…",
"authorization-started": "認可を開始しました",
"installed-toast": "{{name}} をインストールしました",
"no-authorization-url": "コネクタが認可 URL を返しませんでした",
"authorization-pending": "認可はまだ完了していません。プロバイダーのウィンドウで完了してから再試行してください。",
"install-failed": "コネクタのインストールに失敗しました",
"detail-load-failed": "コネクタの詳細の読み込みに失敗しました",
"authorization-incomplete": "認可がまだ完了していません。プロバイダーのウィンドウで完了してから再度更新してください。",
"refresh-status-failed": "コネクタの状態の更新に失敗しました",
"field-required": "{{field}} は必須です",
"complete-authorization": "プロバイダーのウィンドウで認可を完了してください。",
"refresh-status": "状態を更新",
"built-in-auth-desc": "インストール時に別ウィンドウでプロバイダーの認可フローが開きます。",
"built-in-generic-desc": "Eigent のローカル統合です。",
"cancel": "キャンセル",
"install": "インストール",
"installing": "インストール中…",
"save": "保存",
"enter-value": "{{field}} を入力",
"custom-title": "カスタムコネクタを追加",
"custom-subtitle": "信頼できるローカルまたはリモートの MCP サーバーをインストールします。",
"custom-warning": "カスタム MCP サーバーはコマンドの実行やリモートサービスへのアクセスが可能です。信頼できる設定のみをインストールしてください。",
"local-json-desc": "有効な JSON 設定を指定してローカル MCP サーバーを追加します。",
"learn-more": "詳細",
"connector-name": "コネクタ名",
"remote-name-placeholder": "マイリモート MCP",
"remote-url": "リモート MCP URL",
"remote-url-note": "信頼できるローカルネットワーク外のサーバーには HTTPS を使用してください。",
"invalid-json": "無効な JSON{{message}}",
"json-format-error": "JSON 形式エラー:{{message}}",
"parse-failed": "解析に失敗しました",
"missing-mcp-servers": "設定には mcpServers オブジェクトが必要です",
"add-at-least-one": "MCP サーバーを少なくとも 1 つ追加してください",
"already-exists": "{{name}} は既に存在します",
"name-required": "コネクタ名は必須です",
"invalid-remote-url": "有効なリモート MCP URL を入力してください",
"remote-url-protocol": "リモート MCP URL は HTTP または HTTPS を使用する必要があります",
"remote-missing-id": "リモートコネクタが ID なしで作成されました",
"custom-installed": "{{num}} 件のカスタムコネクタをインストールしました",
"install-custom-failed": "カスタムコネクタのインストールに失敗しました",
"env-key-value": "環境変数(キーと値)",
"value-placeholder": "値",
"configuration-title": "設定",
"google-api-key": "Google API キー",
"google-api-key-placeholder": "Google Cloud Console の API キーを入力してください",
"google-api-key-note": "Google API キーの取得方法 → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "検索エンジン ID",
"search-engine-id-placeholder": "API キーに関連付けられたカスタム検索エンジン ID を入力してください",
"google-search-custom-desc": "Google カスタム検索に接続します。Google API キーとカスタム検索エンジンCSEID が必要です。",
"google-search-default-desc": "Google 検索はデフォルトで有効です。API キーは必要ありません。"
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "커넥터",
"search-placeholder": "내 커넥터 검색",
"browse": "커넥터 찾아보기",
"add-custom": "사용자 지정 추가",
"retry": "다시 시도",
"your-connectors": "내 커넥터",
"no-matching": "일치하는 커넥터가 없습니다.",
"count-one": "개 커넥터",
"count-other": "개 커넥터",
"recommended": "추천 커넥터입니다.",
"no-recommended": "사용 가능한 추천 커넥터가 없습니다.",
"gateway-unavailable": "Open Connectors를 사용할 수 없습니다.",
"gateway-unavailable-desc": "이 배포에서는 Connector Gateway가 활성화되어 있지 않습니다.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "기본 제공",
"source-local": "로컬",
"source-remote": "원격",
"load-built-in-failed": "기본 제공 커넥터를 불러오지 못했습니다",
"load-custom-failed": "사용자 지정 커넥터를 불러오지 못했습니다",
"load-open-failed": "Open Connectors를 불러오지 못했습니다",
"disconnected": "{{name}} 연결이 해제되었습니다",
"disconnect-failed": "커넥터 연결을 해제하지 못했습니다",
"update-failed": "커넥터를 업데이트하지 못했습니다",
"status-save-failed-enabled": "커넥터가 활성화되었지만 상태를 저장하지 못했습니다. 새로 고친 후 다시 시도하세요.",
"status-save-failed-disabled": "커넥터가 비활성화되었지만 상태를 저장하지 못했습니다. 새로 고친 후 다시 시도하세요.",
"updated": "커넥터가 업데이트되었습니다",
"save-failed": "커넥터를 저장하지 못했습니다",
"deleted": "커넥터가 삭제되었습니다",
"delete-failed": "커넥터를 삭제하지 못했습니다",
"enable-connector": "커넥터 활성화",
"disable-connector": "커넥터 비활성화",
"more-actions": "추가 작업",
"open": "열기",
"edit": "편집",
"delete": "삭제",
"connected-account": "연결된 계정",
"supported-actions": "지원되는 작업",
"supported-actions-count": "지원되는 작업 {{num}}개",
"show-more": "더 보기",
"show-less": "간략히 보기",
"provider-website": "제공업체 웹사이트",
"built-in-title": "로컬 호환 커넥터",
"built-in-desc": "이 커넥터는 Eigent의 로컬 통합 런타임을 사용합니다. Open Connectors는 새로운 호스팅 통합을 위한 기본 마켓플레이스입니다.",
"configuration": "구성: {{vars}}",
"status": "상태",
"active": "활성",
"disabled": "비활성",
"server-url": "서버 URL",
"not-configured": "구성되지 않음",
"command": "명령어",
"arguments": "인수",
"requires": "{{vars}} 필요",
"notion-desc": "Notion 워크스페이스를 연결합니다.",
"google-calendar-desc": "Google Calendar 일정과 스케줄을 관리합니다.",
"generic-desc": "{{name}}을(를) Eigent에 연결합니다.",
"google-search": "Google 검색",
"google-search-desc": "브라우저 및 리서치 작업에 Google Custom Search를 연결합니다.",
"notion-install-failed": "Notion을 설치하지 못했습니다",
"google-calendar-install-failed": "Google Calendar를 설치하지 못했습니다",
"search-connectors": "커넥터 검색",
"auth-api-key": "API 키",
"auth-credential": "자격 증명",
"auth-oauth": "OAuth",
"auth-none": "인증 없음",
"unnamed-action": "이름 없는 작업",
"try-again": "다시 시도",
"no-open-found": "Open Connectors를 찾을 수 없습니다.",
"no-built-in-found": "기본 제공 커넥터를 찾을 수 없습니다.",
"loading": "불러오는 중…",
"updating": "업데이트 중…",
"loading-more": "더 불러오는 중…",
"new": "신규",
"installed": "설치됨",
"local-integration": "로컬 통합",
"authentication": "인증",
"oauth-title": "OAuth로 연결",
"oauth-desc": "Eigent가 제공업체의 인증 페이지를 엽니다. 인증이 완료되면 이 대화 상자에서 설치가 완료됩니다.",
"oauth-scopes": "요청된 권한: {{scopes}}",
"no-auth-desc": "이 커넥터에는 자격 증명이 필요하지 않습니다.",
"waiting-authorization": "인증 완료 대기 중…",
"authorization-started": "인증이 시작되었습니다",
"installed-toast": "{{name}}이(가) 설치되었습니다",
"no-authorization-url": "커넥터가 인증 URL을 반환하지 않았습니다",
"authorization-pending": "인증이 아직 진행 중입니다. 제공업체 창에서 완료한 후 다시 시도하세요.",
"install-failed": "커넥터를 설치하지 못했습니다",
"detail-load-failed": "커넥터 세부 정보를 불러오지 못했습니다",
"authorization-incomplete": "인증이 아직 완료되지 않았습니다. 제공업체 창에서 완료한 후 상태를 다시 새로 고치세요.",
"refresh-status-failed": "커넥터 상태를 새로 고치지 못했습니다",
"field-required": "{{field}}은(는) 필수입니다",
"complete-authorization": "제공업체 창에서 인증을 완료하세요.",
"refresh-status": "상태 새로 고침",
"built-in-auth-desc": "설치하면 별도 창에서 제공업체 인증 절차가 열립니다.",
"built-in-generic-desc": "Eigent 로컬 통합입니다.",
"cancel": "취소",
"install": "설치",
"installing": "설치 중…",
"save": "저장",
"enter-value": "{{field}} 입력",
"custom-title": "사용자 지정 커넥터 추가",
"custom-subtitle": "신뢰할 수 있는 로컬 또는 원격 MCP 서버를 설치합니다.",
"custom-warning": "사용자 지정 MCP 서버는 명령을 실행하거나 원격 서비스에 접근할 수 있습니다. 신뢰할 수 있는 구성만 설치하세요.",
"local-json-desc": "유효한 JSON 구성을 입력하여 로컬 MCP 서버를 추가합니다.",
"learn-more": "자세히 알아보기",
"connector-name": "커넥터 이름",
"remote-name-placeholder": "내 원격 MCP",
"remote-url": "원격 MCP URL",
"remote-url-note": "신뢰할 수 있는 로컬 네트워크 외부의 서버에는 HTTPS를 사용하세요.",
"invalid-json": "잘못된 JSON: {{message}}",
"json-format-error": "JSON 형식 오류: {{message}}",
"parse-failed": "구문 분석 실패",
"missing-mcp-servers": "구성에 mcpServers 객체가 있어야 합니다",
"add-at-least-one": "MCP 서버를 하나 이상 추가하세요",
"already-exists": "{{name}}이(가) 이미 있습니다",
"name-required": "커넥터 이름은 필수입니다",
"invalid-remote-url": "유효한 원격 MCP URL을 입력하세요",
"remote-url-protocol": "원격 MCP URL은 HTTP 또는 HTTPS를 사용해야 합니다",
"remote-missing-id": "원격 커넥터가 ID 없이 생성되었습니다",
"custom-installed": "사용자 지정 커넥터 {{num}}개가 설치되었습니다",
"install-custom-failed": "사용자 지정 커넥터를 설치하지 못했습니다",
"env-key-value": "환경 변수(키-값)",
"value-placeholder": "값",
"configuration-title": "구성",
"google-api-key": "Google API 키",
"google-api-key-placeholder": "Google Cloud Console의 Google API 키를 입력하세요",
"google-api-key-note": "Google API 키를 발급받는 방법 → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "검색 엔진 ID",
"search-engine-id-placeholder": "API 키와 연결된 Custom Search Engine ID를 입력하세요",
"google-search-custom-desc": "Google Custom Search를 연결합니다. Google API 키와 Custom Search Engine(CSE) ID가 필요합니다.",
"google-search-default-desc": "Google 검색은 기본적으로 활성화되어 있습니다. API 키가 필요하지 않습니다."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "Коннекторы",
"search-placeholder": "Поиск по вашим коннекторам",
"browse": "Обзор коннекторов",
"add-custom": "Добавить свой",
"retry": "Повторить",
"your-connectors": "Ваши коннекторы",
"no-matching": "Подходящие коннекторы не найдены.",
"count-one": "коннектор",
"count-other": "коннекторов",
"recommended": "Рекомендованные для вас коннекторы.",
"no-recommended": "Нет доступных рекомендованных коннекторов.",
"gateway-unavailable": "Open Connectors недоступен.",
"gateway-unavailable-desc": "Connector Gateway не включён для этого развёртывания.",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "Встроенный",
"source-local": "Локальный",
"source-remote": "Удалённый",
"load-built-in-failed": "Не удалось загрузить встроенные коннекторы",
"load-custom-failed": "Не удалось загрузить пользовательские коннекторы",
"load-open-failed": "Не удалось загрузить Open Connectors",
"disconnected": "{{name}} отключён",
"disconnect-failed": "Не удалось отключить коннектор",
"update-failed": "Не удалось обновить коннектор",
"status-save-failed-enabled": "Коннектор включён, но сохранить его состояние не удалось. Обновите страницу и повторите попытку.",
"status-save-failed-disabled": "Коннектор отключён, но сохранить его состояние не удалось. Обновите страницу и повторите попытку.",
"updated": "Коннектор обновлён",
"save-failed": "Не удалось сохранить коннектор",
"deleted": "Коннектор удалён",
"delete-failed": "Не удалось удалить коннектор",
"enable-connector": "Включить коннектор",
"disable-connector": "Отключить коннектор",
"more-actions": "Другие действия",
"open": "Открыть",
"edit": "Изменить",
"delete": "Удалить",
"connected-account": "Подключённая учётная запись",
"supported-actions": "Поддерживаемые действия",
"supported-actions-count": "Поддерживаемых действий: {{num}}",
"show-more": "Показать больше",
"show-less": "Показать меньше",
"provider-website": "Сайт поставщика",
"built-in-title": "Локальный коннектор совместимости",
"built-in-desc": "Этот коннектор использует локальную среду интеграции Eigent. Open Connectors — основной каталог новых облачных интеграций.",
"configuration": "Конфигурация: {{vars}}",
"status": "Состояние",
"active": "Активен",
"disabled": "Отключён",
"server-url": "URL сервера",
"not-configured": "Не настроено",
"command": "Команда",
"arguments": "Аргументы",
"requires": "Требуется: {{vars}}",
"notion-desc": "Подключить рабочее пространство Notion.",
"google-calendar-desc": "Управлять событиями и расписаниями Google Календаря.",
"generic-desc": "Подключить {{name}} к Eigent.",
"google-search": "Поиск Google",
"google-search-desc": "Подключить Google Custom Search для задач браузера и поиска информации.",
"notion-install-failed": "Не удалось установить Notion",
"google-calendar-install-failed": "Не удалось установить Google Календарь",
"search-connectors": "Поиск коннекторов",
"auth-api-key": "Ключ API",
"auth-credential": "Учётные данные",
"auth-oauth": "OAuth",
"auth-none": "Без аутентификации",
"unnamed-action": "Действие без названия",
"try-again": "Повторить",
"no-open-found": "Коннекторы Open Connectors не найдены.",
"no-built-in-found": "Встроенные коннекторы не найдены.",
"loading": "Загрузка…",
"updating": "Обновление…",
"loading-more": "Загрузка дополнительных элементов…",
"new": "Новый",
"installed": "Установлен",
"local-integration": "Локальная интеграция",
"authentication": "Аутентификация",
"oauth-title": "Подключение через OAuth",
"oauth-desc": "Eigent откроет страницу авторизации поставщика. После завершения авторизации установка будет завершена в этом диалоговом окне.",
"oauth-scopes": "Запрашиваемые разрешения: {{scopes}}",
"no-auth-desc": "Для этого коннектора не требуются учётные данные.",
"waiting-authorization": "Ожидание завершения авторизации…",
"authorization-started": "Авторизация начата",
"installed-toast": "{{name}} установлен",
"no-authorization-url": "Коннектор не вернул URL авторизации",
"authorization-pending": "Авторизация ещё не завершена. Завершите её в окне поставщика и повторите попытку.",
"install-failed": "Не удалось установить коннектор",
"detail-load-failed": "Не удалось загрузить сведения о коннекторе",
"authorization-incomplete": "Авторизация ещё не завершена. Завершите её в окне поставщика и снова обновите состояние.",
"refresh-status-failed": "Не удалось обновить состояние коннектора",
"field-required": "Поле {{field}} обязательно",
"complete-authorization": "Завершите авторизацию в окне поставщика.",
"refresh-status": "Обновить состояние",
"built-in-auth-desc": "При установке процесс авторизации поставщика откроется в отдельном окне.",
"built-in-generic-desc": "Локальная интеграция Eigent.",
"cancel": "Отмена",
"install": "Установить",
"installing": "Установка…",
"save": "Сохранить",
"enter-value": "Введите {{field}}",
"custom-title": "Добавить пользовательский коннектор",
"custom-subtitle": "Установите доверенный локальный или удалённый MCP-сервер.",
"custom-warning": "Пользовательские MCP-серверы могут выполнять команды или обращаться к удалённым службам. Устанавливайте только конфигурации, которым доверяете.",
"local-json-desc": "Добавьте локальный MCP-сервер, указав корректную конфигурацию JSON.",
"learn-more": "Подробнее",
"connector-name": "Название коннектора",
"remote-name-placeholder": "Мой удалённый MCP",
"remote-url": "URL удалённого MCP",
"remote-url-note": "Используйте HTTPS для серверов за пределами доверенной локальной сети.",
"invalid-json": "Недопустимый JSON: {{message}}",
"json-format-error": "Ошибка формата JSON: {{message}}",
"parse-failed": "Не удалось выполнить разбор",
"missing-mcp-servers": "Конфигурация должна содержать объект mcpServers",
"add-at-least-one": "Добавьте хотя бы один MCP-сервер",
"already-exists": "{{name}} уже существует",
"name-required": "Необходимо указать название коннектора",
"invalid-remote-url": "Введите корректный URL удалённого MCP",
"remote-url-protocol": "URL удалённого MCP должен использовать HTTP или HTTPS",
"remote-missing-id": "Удалённый коннектор создан без идентификатора",
"custom-installed": "Установленные пользовательские коннекторы: {{num}}",
"install-custom-failed": "Не удалось установить пользовательский коннектор",
"env-key-value": "Переменные среды (ключ-значение)",
"value-placeholder": "Значение",
"configuration-title": "Конфигурация",
"google-api-key": "Ключ API Google",
"google-api-key-placeholder": "Введите ключ API Google из Google Cloud Console",
"google-api-key-note": "Как получить ключ API Google → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "Идентификатор поисковой системы",
"search-engine-id-placeholder": "Введите идентификатор пользовательской поисковой системы, связанный с ключом API",
"google-search-custom-desc": "Подключите Google Custom Search. Требуются ключ API Google и идентификатор пользовательской поисковой системы (CSE).",
"google-search-default-desc": "Поиск Google включён по умолчанию. Ключ API не требуется."
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "连接器",
"search-placeholder": "搜索你的连接器",
"browse": "浏览连接器",
"add-custom": "添加自定义",
"retry": "重试",
"your-connectors": "你的连接器",
"no-matching": "没有匹配的连接器。",
"count-one": "个连接器",
"count-other": "个连接器",
"recommended": "为你推荐的连接器。",
"no-recommended": "暂无推荐的连接器。",
"gateway-unavailable": "Open Connectors 不可用。",
"gateway-unavailable-desc": "当前部署未启用 Connector Gateway。",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "内置",
"source-local": "本地",
"source-remote": "远程",
"load-built-in-failed": "加载内置连接器失败",
"load-custom-failed": "加载自定义连接器失败",
"load-open-failed": "加载 Open Connectors 失败",
"disconnected": "{{name}} 已断开连接",
"disconnect-failed": "断开连接器失败",
"update-failed": "更新连接器失败",
"status-save-failed-enabled": "连接器已启用,但保存状态失败。请刷新后重试。",
"status-save-failed-disabled": "连接器已禁用,但保存状态失败。请刷新后重试。",
"updated": "连接器已更新",
"save-failed": "保存连接器失败",
"deleted": "连接器已删除",
"delete-failed": "删除连接器失败",
"enable-connector": "启用连接器",
"disable-connector": "禁用连接器",
"more-actions": "更多操作",
"open": "打开",
"edit": "编辑",
"delete": "删除",
"connected-account": "已连接账户",
"supported-actions": "支持的操作",
"supported-actions-count": "支持 {{num}} 项操作",
"show-more": "展开更多",
"show-less": "收起",
"provider-website": "服务商网站",
"built-in-title": "本地兼容连接器",
"built-in-desc": "此连接器使用 Eigent 的本地集成运行时。Open Connectors 是新托管集成的主要市场。",
"configuration": "配置项:{{vars}}",
"status": "状态",
"active": "已启用",
"disabled": "已禁用",
"server-url": "服务器 URL",
"not-configured": "未配置",
"command": "命令",
"arguments": "参数",
"requires": "需要 {{vars}}",
"notion-desc": "连接 Notion 工作区。",
"google-calendar-desc": "管理 Google 日历的事件和日程。",
"generic-desc": "将 {{name}} 连接到 Eigent。",
"google-search": "Google 搜索",
"google-search-desc": "连接 Google 自定义搜索,用于浏览器和研究任务。",
"notion-install-failed": "安装 Notion 失败",
"google-calendar-install-failed": "安装 Google 日历失败",
"search-connectors": "搜索连接器",
"auth-api-key": "API 密钥",
"auth-credential": "凭证",
"auth-oauth": "OAuth",
"auth-none": "无需认证",
"unnamed-action": "未命名操作",
"try-again": "重试",
"no-open-found": "未找到 Open Connectors。",
"no-built-in-found": "未找到内置连接器。",
"loading": "加载中…",
"updating": "更新中…",
"loading-more": "加载更多…",
"new": "新",
"installed": "已安装",
"local-integration": "本地集成",
"authentication": "认证方式",
"oauth-title": "使用 OAuth 连接",
"oauth-desc": "Eigent 将打开服务商授权页面。授权完成后,此对话框将自动完成安装。",
"oauth-scopes": "请求的权限范围:{{scopes}}",
"no-auth-desc": "此连接器无需凭证。",
"waiting-authorization": "等待授权完成…",
"authorization-started": "授权已开始",
"installed-toast": "{{name}} 已安装",
"no-authorization-url": "连接器未返回授权 URL",
"authorization-pending": "授权仍在进行中。请在服务商窗口中完成授权后重试。",
"install-failed": "安装连接器失败",
"detail-load-failed": "加载连接器详情失败",
"authorization-incomplete": "授权尚未完成。请在服务商窗口中完成授权后再次刷新。",
"refresh-status-failed": "刷新连接器状态失败",
"field-required": "{{field}} 为必填项",
"complete-authorization": "请在服务商窗口中完成授权。",
"refresh-status": "刷新状态",
"built-in-auth-desc": "安装时将在单独窗口中打开服务商授权流程。",
"built-in-generic-desc": "一个 Eigent 本地集成。",
"cancel": "取消",
"install": "安装",
"installing": "安装中…",
"save": "保存",
"enter-value": "请输入 {{field}}",
"custom-title": "添加自定义连接器",
"custom-subtitle": "安装你信任的本地或远程 MCP 服务器。",
"custom-warning": "自定义 MCP 服务器可以执行命令或访问远程服务。请仅安装你信任的配置。",
"local-json-desc": "通过提供有效的 JSON 配置来添加本地 MCP 服务器。",
"learn-more": "了解更多",
"connector-name": "连接器名称",
"remote-name-placeholder": "我的远程 MCP",
"remote-url": "远程 MCP URL",
"remote-url-note": "对于可信本地网络之外的服务器,请使用 HTTPS。",
"invalid-json": "无效的 JSON{{message}}",
"json-format-error": "JSON 格式错误:{{message}}",
"parse-failed": "解析失败",
"missing-mcp-servers": "配置必须包含 mcpServers 对象",
"add-at-least-one": "请至少添加一个 MCP 服务器",
"already-exists": "{{name}} 已存在",
"name-required": "连接器名称为必填项",
"invalid-remote-url": "请输入有效的远程 MCP URL",
"remote-url-protocol": "远程 MCP URL 必须使用 HTTP 或 HTTPS",
"remote-missing-id": "创建远程连接器时未返回 ID",
"custom-installed": "已安装 {{num}} 个自定义连接器",
"install-custom-failed": "安装自定义连接器失败",
"env-key-value": "环境变量(键值对)",
"value-placeholder": "值",
"configuration-title": "配置",
"google-api-key": "Google API 密钥",
"google-api-key-placeholder": "输入 Google Cloud Console 中的 Google API 密钥",
"google-api-key-note": "了解如何获取 Google API 密钥 → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "搜索引擎 ID",
"search-engine-id-placeholder": "输入与你的 API 密钥关联的自定义搜索引擎 ID",
"google-search-custom-desc": "连接 Google 自定义搜索。需要 Google API 密钥和自定义搜索引擎CSEID。",
"google-search-default-desc": "Google 搜索默认已启用,无需 API 密钥。"
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

View file

@ -0,0 +1,132 @@
{
"title": "連接器",
"search-placeholder": "搜尋你的連接器",
"browse": "瀏覽連接器",
"add-custom": "新增自訂",
"retry": "重試",
"your-connectors": "你的連接器",
"no-matching": "沒有符合的連接器。",
"count-one": "個連接器",
"count-other": "個連接器",
"recommended": "為你推薦的連接器。",
"no-recommended": "暫無推薦的連接器。",
"gateway-unavailable": "Open Connectors 無法使用。",
"gateway-unavailable-desc": "目前部署未啟用 Connector Gateway。",
"open-connectors": "Open Connectors",
"source-open": "Open",
"source-built-in": "內建",
"source-local": "本機",
"source-remote": "遠端",
"load-built-in-failed": "載入內建連接器失敗",
"load-custom-failed": "載入自訂連接器失敗",
"load-open-failed": "載入 Open Connectors 失敗",
"disconnected": "{{name}} 已中斷連接",
"disconnect-failed": "中斷連接器失敗",
"update-failed": "更新連接器失敗",
"status-save-failed-enabled": "連接器已啟用,但儲存狀態失敗。請重新整理後再試。",
"status-save-failed-disabled": "連接器已停用,但儲存狀態失敗。請重新整理後再試。",
"updated": "連接器已更新",
"save-failed": "儲存連接器失敗",
"deleted": "連接器已刪除",
"delete-failed": "刪除連接器失敗",
"enable-connector": "啟用連接器",
"disable-connector": "停用連接器",
"more-actions": "更多操作",
"open": "開啟",
"edit": "編輯",
"delete": "刪除",
"connected-account": "已連接帳戶",
"supported-actions": "支援的操作",
"supported-actions-count": "支援 {{num}} 項操作",
"show-more": "顯示更多",
"show-less": "收合",
"provider-website": "服務商網站",
"built-in-title": "本機相容連接器",
"built-in-desc": "此連接器使用 Eigent 的本機整合執行環境。Open Connectors 是新託管整合的主要市集。",
"configuration": "設定項目:{{vars}}",
"status": "狀態",
"active": "已啟用",
"disabled": "已停用",
"server-url": "伺服器 URL",
"not-configured": "未設定",
"command": "指令",
"arguments": "參數",
"requires": "需要 {{vars}}",
"notion-desc": "連接 Notion 工作區。",
"google-calendar-desc": "管理 Google 日曆的活動與行程。",
"generic-desc": "將 {{name}} 連接到 Eigent。",
"google-search": "Google 搜尋",
"google-search-desc": "連接 Google 自訂搜尋,用於瀏覽器與研究任務。",
"notion-install-failed": "安裝 Notion 失敗",
"google-calendar-install-failed": "安裝 Google 日曆失敗",
"search-connectors": "搜尋連接器",
"auth-api-key": "API 金鑰",
"auth-credential": "憑證",
"auth-oauth": "OAuth",
"auth-none": "無需驗證",
"unnamed-action": "未命名操作",
"try-again": "再試一次",
"no-open-found": "找不到 Open Connectors。",
"no-built-in-found": "找不到內建連接器。",
"loading": "載入中…",
"updating": "更新中…",
"loading-more": "載入更多…",
"new": "新",
"installed": "已安裝",
"local-integration": "本機整合",
"authentication": "驗證方式",
"oauth-title": "使用 OAuth 連接",
"oauth-desc": "Eigent 將開啟服務商授權頁面。授權完成後,此對話框將自動完成安裝。",
"oauth-scopes": "要求的權限範圍:{{scopes}}",
"no-auth-desc": "此連接器無需憑證。",
"waiting-authorization": "等待授權完成…",
"authorization-started": "授權已開始",
"installed-toast": "{{name}} 已安裝",
"no-authorization-url": "連接器未回傳授權 URL",
"authorization-pending": "授權仍在進行中。請在服務商視窗中完成授權後再試。",
"install-failed": "安裝連接器失敗",
"detail-load-failed": "載入連接器詳細資訊失敗",
"authorization-incomplete": "授權尚未完成。請在服務商視窗中完成授權後再次重新整理。",
"refresh-status-failed": "重新整理連接器狀態失敗",
"field-required": "{{field}} 為必填欄位",
"complete-authorization": "請在服務商視窗中完成授權。",
"refresh-status": "重新整理狀態",
"built-in-auth-desc": "安裝時將在獨立視窗中開啟服務商授權流程。",
"built-in-generic-desc": "一個 Eigent 本機整合。",
"cancel": "取消",
"install": "安裝",
"installing": "安裝中…",
"save": "儲存",
"enter-value": "請輸入 {{field}}",
"custom-title": "新增自訂連接器",
"custom-subtitle": "安裝你信任的本機或遠端 MCP 伺服器。",
"custom-warning": "自訂 MCP 伺服器可以執行指令或存取遠端服務。請僅安裝你信任的設定。",
"local-json-desc": "透過提供有效的 JSON 設定來新增本機 MCP 伺服器。",
"learn-more": "瞭解更多",
"connector-name": "連接器名稱",
"remote-name-placeholder": "我的遠端 MCP",
"remote-url": "遠端 MCP URL",
"remote-url-note": "對於可信本機網路以外的伺服器,請使用 HTTPS。",
"invalid-json": "無效的 JSON{{message}}",
"json-format-error": "JSON 格式錯誤:{{message}}",
"parse-failed": "解析失敗",
"missing-mcp-servers": "設定必須包含 mcpServers 物件",
"add-at-least-one": "請至少新增一個 MCP 伺服器",
"already-exists": "{{name}} 已存在",
"name-required": "連接器名稱為必填",
"invalid-remote-url": "請輸入有效的遠端 MCP URL",
"remote-url-protocol": "遠端 MCP URL 必須使用 HTTP 或 HTTPS",
"remote-missing-id": "建立遠端連接器時未回傳 ID",
"custom-installed": "已安裝 {{num}} 個自訂連接器",
"install-custom-failed": "安裝自訂連接器失敗",
"env-key-value": "環境變數(鍵值對)",
"value-placeholder": "值",
"configuration-title": "設定",
"google-api-key": "Google API 金鑰",
"google-api-key-placeholder": "輸入 Google Cloud Console 中的 Google API 金鑰",
"google-api-key-note": "瞭解如何取得 Google API 金鑰 → https://developers.google.com/custom-search/v1/overview",
"search-engine-id": "搜尋引擎 ID",
"search-engine-id-placeholder": "輸入與你的 API 金鑰相關聯的自訂搜尋引擎 ID",
"google-search-custom-desc": "連接 Google 自訂搜尋。需要 Google API 金鑰和自訂搜尋引擎CSEID。",
"google-search-default-desc": "Google 搜尋預設已啟用,無需 API 金鑰。"
}

View file

@ -14,6 +14,7 @@
import agents from './agents.json';
import chat from './chat.json';
import connectors from './connectors.json';
import dashboard from './dashboard.json';
import layout from './layout.json';
import setting from './setting.json';
@ -26,6 +27,7 @@ export default {
dashboard,
workforce,
chat,
connectors,
setting,
update,
triggers,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,411 @@
// ========= 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 { mcpInstall, mcpRemove } from '@/api/brain';
import {
proxyFetchDelete,
proxyFetchGet,
proxyFetchPost,
proxyFetchPut,
} from '@/api/http';
import {
Dialog,
DialogContent,
DialogContentSection,
DialogFooter,
DialogHeader,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import loader from '@monaco-editor/loader';
import MonacoEditor from '@monaco-editor/react';
import { Server, Wrench } from 'lucide-react';
import * as monaco from 'monaco-editor';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import type { ConnectorInstallHint, MCPUserItem } from './types';
if (typeof globalThis !== 'undefined') {
(globalThis as any).MonacoEnvironment = {
// Vite does not bundle Monaco's language workers, so hand every label a
// no-op worker: language services (JSON diagnostics etc.) stay disabled,
// but the editor itself works and Monaco never throws for a missing
// worker. Our own parse errors surface via installCustom's JSON.parse.
getWorker() {
return new Worker(
URL.createObjectURL(
new Blob([`self.onmessage = function () {};`], {
type: 'application/javascript',
})
)
);
},
};
}
loader.config({ monaco });
const LOCAL_MCP_EXAMPLE = `{
"mcpServers": {
"sequential-thinking": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
}
}
}`;
interface AddCustomConnectorDialogProps {
open: boolean;
customMcps: MCPUserItem[];
onOpenChange: (open: boolean) => void;
onInstalled: (hint: ConnectorInstallHint) => void | Promise<void>;
}
export default function AddCustomConnectorDialog({
open,
customMcps,
onOpenChange,
onInstalled,
}: AddCustomConnectorDialogProps) {
const { t } = useTranslation();
const [customType, setCustomType] = useState<'local' | 'remote'>('local');
const [localJson, setLocalJson] = useState(LOCAL_MCP_EXAMPLE);
const [remoteName, setRemoteName] = useState('');
const [remoteUrl, setRemoteUrl] = useState('');
const [saving, setSaving] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [jsonError, setJsonError] = useState<string | null>(null);
useEffect(() => {
if (!open) return;
setCustomType('local');
setRemoteName('');
setRemoteUrl('');
setFormError(null);
setJsonError(null);
setSaving(false);
try {
setLocalJson(JSON.stringify(JSON.parse(LOCAL_MCP_EXAMPLE), null, 4));
} catch {
setLocalJson(LOCAL_MCP_EXAMPLE);
}
}, [open]);
useEffect(() => {
setJsonError(null);
}, [localJson]);
const closeDialog = useCallback(() => {
onOpenChange(false);
}, [onOpenChange]);
const installCustom = useCallback(async () => {
setSaving(true);
setFormError(null);
try {
if (customType === 'local') {
let parsed: {
mcpServers?: Record<string, Record<string, unknown>>;
};
try {
parsed = JSON.parse(localJson);
} catch (error: any) {
throw new Error(
t('connectors.invalid-json', {
message: error?.message || t('connectors.parse-failed'),
})
);
}
if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') {
throw new Error(t('connectors.missing-mcp-servers'));
}
const names = Object.keys(parsed.mcpServers);
if (!names.length) {
throw new Error(t('connectors.add-at-least-one'));
}
const duplicate = names.find((name) =>
customMcps.some(
(item) => item.mcp_name.toLowerCase() === name.toLowerCase()
)
);
if (duplicate) {
throw new Error(t('connectors.already-exists', { name: duplicate }));
}
const response = await proxyFetchPost(
'/api/v1/mcp/import/local',
parsed
);
if (response?.detail) throw new Error(String(response.detail));
const installedNames: string[] = [];
try {
for (const [name, config] of Object.entries(parsed.mcpServers)) {
await mcpInstall(name, config);
installedNames.push(name);
}
} catch (installError) {
// The import already created DB records; remove them so a retry
// does not fail the duplicate-name check. Cleanup is best-effort.
for (const name of installedNames) {
try {
await mcpRemove(name);
} catch {
// Preserve the install error.
}
}
try {
const users = await proxyFetchGet('/api/v1/mcp/users');
const rows: MCPUserItem[] = Array.isArray(users)
? users
: Array.isArray(users?.items)
? users.items
: [];
const lowerNames = new Set(names.map((n) => n.toLowerCase()));
for (const row of rows) {
if (lowerNames.has((row.mcp_name || '').toLowerCase())) {
await proxyFetchDelete(`/api/v1/mcp/users/${row.id}`);
}
}
} catch {
// Preserve the install error.
}
throw installError;
}
toast.success(t('connectors.custom-installed', { num: names.length }));
await onInstalled({ source: 'custom', key: names[0] });
closeDialog();
return;
}
const name = remoteName.trim();
const url = remoteUrl.trim();
if (!name) throw new Error(t('connectors.name-required'));
if (
customMcps.some(
(item) => item.mcp_name.toLowerCase() === name.toLowerCase()
)
) {
throw new Error(t('connectors.already-exists', { name }));
}
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
} catch {
throw new Error(t('connectors.invalid-remote-url'));
}
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
throw new Error(t('connectors.remote-url-protocol'));
}
const response = await proxyFetchPost('/api/v1/mcp/import/remote', {
server_name: name,
server_url: url,
});
if (response?.detail) throw new Error(String(response.detail));
const importedId = Number(response?.mcp_user?.id);
if (!Number.isInteger(importedId) || importedId <= 0) {
throw new Error(t('connectors.remote-missing-id'));
}
try {
await proxyFetchPut(`/api/v1/mcp/users/${importedId}`, {
mcp_key: name,
mcp_desc: name,
});
} catch (error) {
try {
await proxyFetchDelete(`/api/v1/mcp/users/${importedId}`);
} catch {
// Preserve the update error; cleanup is best-effort.
}
throw error;
}
try {
await mcpInstall(name, { url });
} catch (installError) {
try {
await proxyFetchDelete(`/api/v1/mcp/users/${importedId}`);
} catch {
// Preserve the runtime install error; cleanup is best-effort.
}
throw installError;
}
toast.success(t('connectors.installed-toast', { name }));
await onInstalled({ source: 'custom', key: name });
closeDialog();
} catch (error: any) {
setFormError(error?.message || t('connectors.install-custom-failed'));
} finally {
setSaving(false);
}
}, [
closeDialog,
customMcps,
customType,
localJson,
onInstalled,
remoteName,
remoteUrl,
t,
]);
return (
<Dialog
open={open}
onOpenChange={(next) => {
if (!next) closeDialog();
}}
>
<DialogContent
size="lg"
showCloseButton
onClose={closeDialog}
overlayVariant="dimmed"
className="h-[min(640px,90vh)] !max-w-[720px]"
>
<DialogHeader
title={t('connectors.custom-title')}
subtitle={t('connectors.custom-subtitle')}
className="pr-12"
/>
<DialogContentSection className="flex min-h-0 flex-col overflow-hidden p-0">
<div className="scrollbar-always-visible min-h-0 flex-1 overflow-y-auto p-5">
<div className="mx-auto flex max-w-3xl flex-col gap-5">
<Tabs
value={customType}
onValueChange={(value) => {
const next = value as 'local' | 'remote';
setCustomType(next);
setFormError(null);
if (next !== 'local') return;
try {
setLocalJson(
JSON.stringify(JSON.parse(localJson), null, 4)
);
setJsonError(null);
} catch (error: any) {
setJsonError(
t('connectors.json-format-error', {
message: error?.message || String(error),
})
);
}
}}
>
<TabsList appearance="default">
<TabsTrigger value="local">
<Wrench className="h-3.5 w-3.5" />
<span className="!text-body-sm font-bold text-ds-text-neutral-default-default">
{t('connectors.source-local')}
</span>
</TabsTrigger>
<TabsTrigger value="remote">
<Server className="h-3.5 w-3.5" />
<span className="!text-body-sm font-bold text-ds-text-neutral-default-default">
{t('connectors.source-remote')}
</span>
</TabsTrigger>
</TabsList>
</Tabs>
<div className="rounded-xl border border-solid border-ds-border-warning-default-default bg-ds-bg-warning-subtle-default p-4 text-body-sm text-ds-text-warning-strong-default">
{t('connectors.custom-warning')}
</div>
{customType === 'local' ? (
<div className="space-y-2">
<span className="block text-body-sm text-ds-text-neutral-muted-default">
{t('connectors.local-json-desc')}{' '}
<a
href="https://modelcontextprotocol.io/docs/getting-started/intro"
target="_blank"
rel="noopener noreferrer"
className="text-ds-text-information-strong-default underline underline-offset-2"
>
{t('connectors.learn-more')}
</a>
</span>
{jsonError ? (
<span className="block text-label-sm text-ds-text-error-strong-default">
{jsonError}
</span>
) : null}
<div className="overflow-hidden rounded-xl border border-solid border-ds-border-neutral-strong-default">
<MonacoEditor
height="300px"
width="100%"
language="json"
theme="vs-dark"
value={localJson}
onChange={(value) => setLocalJson(value ?? '')}
options={{
minimap: { enabled: false },
fontSize: 14,
scrollBeyondLastLine: false,
readOnly: saving,
automaticLayout: true,
}}
/>
</div>
</div>
) : (
<div className="space-y-4">
<Input
title={t('connectors.connector-name')}
required
value={remoteName}
onChange={(event) => setRemoteName(event.target.value)}
placeholder={t('connectors.remote-name-placeholder')}
leadingIcon={<Wrench className="h-4 w-4" />}
/>
<Input
title={t('connectors.remote-url')}
required
value={remoteUrl}
onChange={(event) => setRemoteUrl(event.target.value)}
placeholder="https://example.com/mcp"
leadingIcon={<Server className="h-4 w-4" />}
note={t('connectors.remote-url-note')}
/>
</div>
)}
{formError ? (
<div className="rounded-xl bg-ds-bg-error-subtle-default p-3 text-body-sm text-ds-text-error-strong-default">
{formError}
</div>
) : null}
</div>
</div>
<DialogFooter
showCancelButton
cancelButtonText={t('connectors.cancel')}
onCancel={closeDialog}
showConfirmButton
confirmButtonText={
saving ? t('connectors.installing') : t('connectors.install')
}
onConfirm={() => void installCustom()}
confirmButtonDisabled={saving}
/>
</DialogContentSection>
</DialogContent>
</Dialog>
);
}

View file

@ -24,15 +24,14 @@ import { toast } from 'sonner';
const FIELDS = [
{
key: 'GOOGLE_API_KEY',
label: 'Google API Key',
placeholder: 'Enter your Google API key from Google Cloud Console',
note: 'Learn how to get your Google API key → https://developers.google.com/custom-search/v1/overview',
labelKey: 'connectors.google-api-key',
placeholderKey: 'connectors.google-api-key-placeholder',
noteKey: 'connectors.google-api-key-note',
},
{
key: 'SEARCH_ENGINE_ID',
label: 'Search Engine ID',
placeholder:
'Enter the Custom Search Engine ID associated with your API key',
labelKey: 'connectors.search-engine-id',
placeholderKey: 'connectors.search-engine-id-placeholder',
},
] as const;
@ -99,14 +98,11 @@ export function GoogleSearchPanel({ onConfigured }: GoogleSearchPanelProps) {
{requiresApiKey ? (
<>
<span className="whitespace-pre-wrap text-body-sm text-ds-text-neutral-muted-default">
{t('setting.google-search-custom-desc', {
defaultValue:
'Connect to Google Custom Search. Requires a Google API key and a Custom Search Engine (CSE) ID.',
})}
{t('connectors.google-search-custom-desc')}
</span>
<div className="text-body-sm font-bold text-ds-text-neutral-default-default">
{t('setting.tools', { defaultValue: 'Configuration' })}
{t('connectors.configuration-title')}
</div>
<div className="space-y-3">
@ -115,9 +111,9 @@ export function GoogleSearchPanel({ onConfigured }: GoogleSearchPanelProps) {
key={field.key}
id={field.key}
size="default"
title={field.label}
title={t(field.labelKey)}
type={showKeys[field.key] ? 'text' : 'password'}
placeholder={field.placeholder}
placeholder={t(field.placeholderKey)}
value={formData[field.key] || ''}
onChange={(e) =>
setFormData((prev) => ({
@ -125,7 +121,7 @@ export function GoogleSearchPanel({ onConfigured }: GoogleSearchPanelProps) {
[field.key]: e.target.value,
}))
}
note={'note' in field ? field.note : undefined}
note={'noteKey' in field ? t(field.noteKey) : undefined}
backIcon={<Eye className="h-5 w-5" />}
onBackIconClick={() =>
setShowKeys((prev) => ({
@ -145,10 +141,7 @@ export function GoogleSearchPanel({ onConfigured }: GoogleSearchPanelProps) {
</>
) : (
<div className="rounded-lg bg-ds-bg-neutral-default-default px-4 py-3 text-body-sm text-ds-text-neutral-muted-default">
{t('setting.google-search-default-desc', {
defaultValue:
'Google Search is enabled by default. No API key required.',
})}
{t('connectors.google-search-default-desc')}
</div>
)}
</div>

View file

@ -1,162 +0,0 @@
// ========= 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 {
Dialog,
DialogContent,
DialogContentSection,
DialogFooter,
DialogHeader,
} from '@/components/ui/dialog';
import loader from '@monaco-editor/loader';
import MonacoEditor from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
if (typeof globalThis !== 'undefined') {
(globalThis as any).MonacoEnvironment = {
getWorker(_: string, label: string) {
if (['json', 'css', 'html', 'typescript', 'javascript'].includes(label)) {
return new Worker(
URL.createObjectURL(
new Blob(
[
`
self.onmessage = function () {};
`,
],
{ type: 'application/javascript' }
)
)
);
}
},
};
}
loader.config({ monaco }); // put at the top of the MCPAddDialog component file
interface MCPAddDialogProps {
open: boolean;
addType: 'local' | 'remote';
setAddType: (type: 'local' | 'remote') => void;
localJson: string;
setLocalJson: (v: string) => void;
remoteName: string;
setRemoteName: (v: string) => void;
remoteUrl: string;
setRemoteUrl: (v: string) => void;
installing: boolean;
onClose: () => void;
onInstall: () => void;
}
export default function MCPAddDialog({
open,
localJson,
setLocalJson,
installing,
onClose,
onInstall,
}: MCPAddDialogProps) {
const [jsonError, setJsonError] = useState<string | null>(null);
const { t } = useTranslation();
// when the dialog is opened, automatically format the JSON
React.useEffect(() => {
if (open && localJson) {
try {
const obj = JSON.parse(localJson);
setLocalJson(JSON.stringify(obj, null, 4));
setJsonError(null);
} catch (e: any) {
// do not format invalid JSON, keep the original content
setJsonError('JSON format error: ' + (e.message || e.toString()));
}
} else if (open) {
setJsonError(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
// when localJson changes, clear the error prompt
React.useEffect(() => {
setJsonError(null);
}, [localJson]);
return (
<Dialog
open={open}
onOpenChange={(v) => {
if (!v) onClose();
}}
>
<DialogContent
size="lg"
showCloseButton
onClose={onClose}
className="p-0"
>
<DialogHeader title={t('setting.add-your-agent')} />
<DialogContentSection>
<div className="mb-4 text-body-sm text-ds-text-neutral-muted-default">
{t(
'setting.add-a-local-mcp-server-by-providing-a-valid-json-configuration'
)}
<a
href="https://modelcontextprotocol.io/docs/getting-started/intro"
target="_blank"
rel="noopener noreferrer"
className="text-ds-text-status-splitting-strong-default underline"
>
{t('setting.learn-more')}
</a>
</div>
{jsonError && (
<div className="mb-2 text-label-md text-ds-text-status-error-strong-default">
{jsonError}
</div>
)}
<div className="rounded-xl border-ds-border-neutral-strong-default overflow-hidden border">
<MonacoEditor
height="300px"
width="100%"
language="json"
theme="vs-dark"
value={localJson}
onChange={(v) => {
setLocalJson(v ?? '');
}}
options={{
minimap: { enabled: false },
fontSize: 14,
scrollBeyondLastLine: false,
readOnly: installing,
automaticLayout: true,
}}
/>
</div>
</DialogContentSection>
<DialogFooter
showConfirmButton
confirmButtonText={
installing ? t('setting.installing') : t('setting.install')
}
onConfirm={onInstall}
confirmButtonVariant="primary"
/>
</DialogContent>
</Dialog>
);
}

View file

@ -52,6 +52,7 @@ export default function MCPConfigDialog({
const { t } = useTranslation();
const formRef = useRef<HTMLFormElement | null>(null);
if (!form || !mcp) return null;
const isRemote = Number(mcp.type) === 2;
return (
<Dialog
open={open}
@ -64,7 +65,7 @@ export default function MCPConfigDialog({
<form
ref={formRef}
onSubmit={onSave}
className="gap-4 p-md flex flex-col"
className="flex flex-col gap-4 p-md"
>
<Input
title={t('setting.name')}
@ -81,59 +82,81 @@ export default function MCPConfigDialog({
disabled={loading}
placeholder={t('setting.description') as string}
/>
<Input
title={t('setting.command')}
value={form.command}
onChange={(e) => onChange({ ...form, command: e.target.value })}
disabled={loading}
placeholder={t('setting.command') as string}
/>
<Textarea
variant="enhanced"
title={t('setting.args-one-per-line')}
value={Array.isArray(form.argsArr) ? form.argsArr.join('\n') : ''}
onChange={(e) =>
onChange({ ...form, argsArr: e.target.value.split(/\r?\n/) })
}
disabled={loading}
placeholder={t('setting.args-one-per-line') as string}
rows={Math.max(
3,
form.argsArr && form.argsArr.length > 0 ? form.argsArr.length : 3
)}
/>
<div className="mb-1 text-label-sm font-normal block">
Env (key-value)
</div>
{Object.entries(form.env).map(([k, v], idx) => (
<div className="mb-2" key={k + idx}>
{isRemote ? (
<Input
title={t('connectors.remote-url')}
value={form.server_url}
onChange={(e) =>
onChange({ ...form, server_url: e.target.value })
}
disabled={loading}
placeholder="https://example.com/mcp"
required
/>
) : (
<>
<Input
title={k}
type={showEnvValues[k] ? 'text' : 'password'}
value={String(v)}
onChange={(e) => {
const newEnv = { ...form.env };
newEnv[k] = e.target.value;
onChange({ ...form, env: newEnv });
}}
title={t('setting.command')}
value={form.command}
onChange={(e) => onChange({ ...form, command: e.target.value })}
disabled={loading}
backIcon={
showEnvValues[k] ? (
<Eye className="h-5 w-5" />
) : (
<EyeOff className="h-5 w-5" />
)
}
onBackIconClick={() =>
setShowEnvValues((prev) => ({ ...prev, [k]: !prev[k] }))
}
size="default"
placeholder="Value"
placeholder={t('setting.command') as string}
/>
</div>
))}
<Textarea
variant="enhanced"
title={t('setting.args-one-per-line')}
value={
Array.isArray(form.argsArr) ? form.argsArr.join('\n') : ''
}
onChange={(e) =>
onChange({ ...form, argsArr: e.target.value.split(/\r?\n/) })
}
disabled={loading}
placeholder={t('setting.args-one-per-line') as string}
rows={Math.max(
3,
form.argsArr && form.argsArr.length > 0
? form.argsArr.length
: 3
)}
/>
<div className="mb-1 block text-label-sm font-normal">
{t('connectors.env-key-value')}
</div>
{Object.entries(form.env).map(([k, v], idx) => (
<div className="mb-2" key={k + idx}>
<Input
title={k}
type={showEnvValues[k] ? 'text' : 'password'}
value={String(v)}
onChange={(e) => {
const newEnv = { ...form.env };
newEnv[k] = e.target.value;
onChange({ ...form, env: newEnv });
}}
disabled={loading}
backIcon={
showEnvValues[k] ? (
<Eye className="h-5 w-5" />
) : (
<EyeOff className="h-5 w-5" />
)
}
onBackIconClick={() =>
setShowEnvValues((prev) => ({
...prev,
[k]: !prev[k],
}))
}
size="default"
placeholder={t('connectors.value-placeholder')}
/>
</div>
))}
</>
)}
{errorMsg && (
<div className="mb-2 text-label-md text-ds-text-status-error-strong-default">
{errorMsg}

View file

@ -1,47 +0,0 @@
// ========= 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 MCPListItem from './MCPListItem';
import type { MCPUserItem } from './types';
interface MCPListProps {
items: MCPUserItem[];
onSetting: (item: MCPUserItem) => void;
onDelete: (item: MCPUserItem) => void;
onSwitch: (id: number, checked: boolean) => Promise<void>;
switchLoading: Record<number, boolean>;
}
export default function MCPList({
items,
onSetting,
onDelete,
onSwitch,
switchLoading,
}: MCPListProps) {
return (
<div className="pt-4">
{items.map((item) => (
<MCPListItem
key={item.id}
item={item}
onSetting={onSetting}
onDelete={onDelete}
onSwitch={onSwitch}
loading={!!switchLoading[item.id]}
/>
))}
</div>
);
}

View file

@ -1,120 +0,0 @@
// ========= 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 { TooltipSimple } from '@/components/ui/tooltip';
import { capitalizeFirstLetter } from '@/lib';
import { CircleAlert, Trash2 } from 'lucide-react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { MCPUserItem } from './types';
interface MCPListItemProps {
item: MCPUserItem;
onSetting: (item: MCPUserItem) => void;
onDelete: (item: MCPUserItem) => void;
onSwitch: (id: number, checked: boolean) => Promise<void>;
loading: boolean;
}
export default function MCPListItem({
item,
onSetting: _onSetting,
onDelete,
onSwitch: _onSwitch,
loading: _loading,
}: MCPListItemProps) {
const [_showMenu, setShowMenu] = useState(false);
const { t } = useTranslation();
return (
<div className="mb-4 gap-4 rounded-2xl bg-ds-bg-neutral-strong-default p-4 flex items-center justify-between">
<div className="gap-4 flex items-center">
<div className="mx-xs h-3 w-3 bg-green-500 rounded-full"></div>
<div className="text-base font-bold leading-9 text-ds-text-neutral-default-default">
{capitalizeFirstLetter(item.mcp_name)}
</div>
<div className="flex items-center">
<TooltipSimple content={item.mcp_desc}>
<CircleAlert className="h-4 w-4 text-ds-icon-neutral-muted-default" />
</TooltipSimple>
</div>
</div>
<div className="gap-2 flex items-center">
{/* <Switch
checked={item.status === 1}
disabled={loading}
onCheckedChange={(checked) => onSwitch(item.id, checked)}
/> */}
<Button
variant="ghost"
size="sm"
textWeight="medium"
buttonRadius="lg"
tone="error"
className="w-full"
onClick={() => {
onDelete(item);
setShowMenu(false);
}}
>
<Trash2 className="h-4 w-4" /> {t('setting.delete')}
</Button>
{/* <div className="relative">
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="xs" buttonContent="icon-only"
onClick={() => setShowMenu((v) => !v)}
disabled={loading}
>
<Ellipsis className="w-4 h-4 text-ds-icon-neutral-default-default" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[98px] p-sm rounded-[12px] bg-dropdown-bg border border-solid border-ds-border-neutral-default-default">
<div className="space-y-1">
<PopoverClose asChild>
<Button
variant="ghost"
size="sm"
className="w-full"
onClick={() => {
onSetting(item);
setShowMenu(false);
}}
>
<Settings className="w-4 h-4" /> {t("setting.setting")}
</Button>
</PopoverClose>
<PopoverClose asChild>
<Button
variant="ghost"
size="sm"
className="w-full !text-ds-text-status-error-strong-default"
onClick={() => {
onDelete(item);
setShowMenu(false);
}}
>
<Trash2 className="w-4 h-4" /> {t("setting.delete")}
</Button>
</PopoverClose>
</div>
</PopoverContent>
</Popover>
</div> */}
</div>
</div>
);
}

View file

@ -1,426 +0,0 @@
// ========= 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 { mcpInstall, mcpRemove } from '@/api/brain';
import { proxyFetchDelete, proxyFetchGet, proxyFetchPost } from '@/api/http';
import githubIcon from '@/assets/icon/github.svg';
import AnthropicIcon from '@/assets/mcp/Anthropic.svg?url';
import CamelIcon from '@/assets/mcp/Camel.svg?url';
import CommunityIcon from '@/assets/mcp/Community.svg?url';
import OfficialIcon from '@/assets/mcp/Official.svg?url';
import SearchInput from '@/components/Dashboard/SearchInput';
import { Button } from '@/components/ui/button';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { TooltipSimple } from '@/components/ui/tooltip';
import { useAuthStore } from '@/store/authStore';
import { ChevronLeft, CircleAlert, Store } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MCPEnvDialog } from './MCPEnvDialog';
interface MCPItem {
id: number;
name: string;
key: string;
description: string;
status: number | string;
category?: { name: string };
home_page?: string;
install_command?: {
command: string;
args: string[];
env?: Record<string, string>;
};
homepage?: string;
}
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debounced;
}
// map category name to svg file name
const categoryIconMap: Record<string, string> = {
anthropic: 'Anthropic',
community: 'Community',
official: 'Official',
camel: 'Camel',
};
// load all svg files
const svgIcons: Record<string, string> = {
Anthropic: AnthropicIcon,
Community: CommunityIcon,
Official: OfficialIcon,
Camel: CamelIcon,
};
type MCPMarketProps = {
onBack?: () => void;
keyword?: string;
};
export default function MCPMarket({
onBack,
keyword: externalKeyword,
}: MCPMarketProps) {
const { t } = useTranslation();
const { checkAgentTool } = useAuthStore();
const [items, setItems] = useState<MCPItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [keyword, setKeyword] = useState('');
const effectiveKeyword =
externalKeyword !== undefined ? externalKeyword : keyword;
const debouncedKeyword = useDebounce(effectiveKeyword, 400);
const loader = useRef<HTMLDivElement | null>(null);
const [installing, setInstalling] = useState<{ [id: number]: boolean }>({});
const [installed, setInstalled] = useState<{ [id: number]: boolean }>({});
const [installedIds, setInstalledIds] = useState<number[]>([]);
const [mcpCategory, setMcpCategory] = useState<
{ id: number; name: string }[]
>([]);
// environment variable configuration
const [showEnvConfig, setShowEnvConfig] = useState(false);
const [activeMcp, setActiveMcp] = useState<MCPItem | null>(null);
const [categoryId, setCategoryId] = useState<number | undefined>(undefined);
const effectiveCategoryId = categoryId;
const [userInstallMcp, setUserInstallMcp] = useState<any | undefined>([]);
// get installed MCP list
useEffect(() => {
proxyFetchGet('/api/v1/mcp/users').then((res) => {
let ids: number[] = [];
if (Array.isArray(res)) {
setUserInstallMcp(res);
ids = res.map((item: any) => item.mcp_id);
} else if (Array.isArray(res.items)) {
setUserInstallMcp(res.items);
ids = res.items.map((item: any) => item.mcp_id);
}
setInstalledIds(ids);
});
}, []);
// get MCP categories
useEffect(() => {
proxyFetchGet('/api/v1/mcp/categories').then((res) => {
if (Array.isArray(res)) {
setMcpCategory(res);
}
});
}, []);
// load data
const loadData = useCallback(
async (pageNum: number, kw: string, catId?: number, pageSize = 20) => {
setIsLoading(true);
setError('');
try {
const params: any = { page: pageNum, size: pageSize, keyword: kw };
if (catId) params.category_id = catId;
const res = await proxyFetchGet('/api/v1/mcps', params);
if (res && Array.isArray(res.items)) {
// frontend deduplication
const all: MCPItem[] =
pageNum === 1 ? res.items : [...items, ...res.items];
const unique: MCPItem[] = Array.from(
new Map(all.map((i: MCPItem) => [i.id, i])).values()
);
setItems(unique);
setHasMore(res.items.length === pageSize);
} else {
if (pageNum === 1) setItems([]);
setHasMore(false);
}
} catch (err: any) {
setError(err?.message || 'Load failed');
} finally {
setIsLoading(false);
}
},
[items]
);
useEffect(() => {
setPage(1);
loadData(1, debouncedKeyword, effectiveCategoryId);
// eslint-disable-next-line
}, [debouncedKeyword, effectiveCategoryId]);
useEffect(() => {
if (page > 1) loadData(page, debouncedKeyword, effectiveCategoryId);
// eslint-disable-next-line
}, [page]);
useEffect(() => {
if (!hasMore || isLoading) return;
const node = loader.current;
if (!node) return;
const observer = new window.IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setPage((p) => (isLoading || !hasMore ? p : p + 1));
}
},
{ root: null, rootMargin: '0px', threshold: 0.1 }
);
observer.observe(node);
return () => {
observer.disconnect();
};
}, [hasMore, isLoading]);
const checkEnv = (id: number) => {
const mcp = items.find((mcp) => mcp.id === id);
if (mcp && Object.keys(mcp?.install_command?.env || {}).length > 0) {
setActiveMcp(mcp);
setShowEnvConfig(true);
} else {
installMcp(id);
}
};
const onConnect = (mcp: MCPItem) => {
console.log(mcp);
setItems((prev) =>
prev.map((item) => (item.id === mcp.id ? { ...item, ...mcp } : item))
);
installMcp(mcp.id);
onClose();
};
const onClose = () => {
setShowEnvConfig(false);
setActiveMcp(null);
};
const installMcp = async (id: number) => {
setInstalling((prev) => ({ ...prev, [id]: true }));
try {
const mcpItem = items.find((item) => item.id === id);
const res = await proxyFetchPost('/api/v1/mcp/install?mcp_id=' + id);
if (res) {
console.log(res);
setUserInstallMcp((prev: any) => [...prev, res]);
}
setInstalled((prev) => ({ ...prev, [id]: true }));
setInstalledIds((prev) => [...prev, id]);
if (mcpItem?.install_command) {
await mcpInstall(mcpItem.key, mcpItem.install_command);
}
} catch (e) {
console.error('Error installing MCP:', e);
} finally {
setInstalling((prev) => ({ ...prev, [id]: false }));
}
};
const handleBack = () => {
if (onBack) onBack();
else window.history.back();
};
const handleDelete = async (deleteTarget: MCPItem) => {
if (!deleteTarget) return;
try {
checkAgentTool(deleteTarget.name);
console.log(userInstallMcp, deleteTarget);
const userMcpRecord = userInstallMcp.find(
(item: any) => item.mcp_id === deleteTarget.id
);
const id = userMcpRecord?.id;
if (id === undefined || id === null) {
console.warn(
'No matching user MCP record found for delete target:',
deleteTarget
);
return;
}
console.log('deleteTarget', deleteTarget);
await proxyFetchDelete(`/api/v1/mcp/users/${id}`);
await mcpRemove(deleteTarget.key);
setInstalledIds((prev) =>
prev.filter((item) => item !== deleteTarget.id)
);
setInstalled((prev) => ({ ...prev, [deleteTarget.id]: false }));
loadData(1, debouncedKeyword, categoryId, page * 20);
} catch (e) {
console.log(e);
}
};
return (
<div className="flex h-full flex-col items-center">
{externalKeyword === undefined && (
<>
<div className="text-body top-0 mb-0 max-w-4xl py-2 sticky z-[20] flex w-full items-center justify-between">
<Button
variant="ghost"
size="sm"
onClick={handleBack}
className="mr-2"
>
<ChevronLeft className="h-6 w-6" />
</Button>
<span className="text-base font-bold leading-12 text-ds-text-neutral-default-default">
{t('setting.mcp-market')}
</span>
</div>
<div className="w-40 max-w-4xl">
<SearchInput
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
</div>
</>
)}
{/* Category toggle row */}
<div className="py-2 flex w-full">
<ToggleGroup
type="single"
value={categoryId ? String(categoryId) : 'all'}
onValueChange={(val) =>
setCategoryId(!val || val === 'all' ? undefined : Number(val))
}
className="flex flex-wrap"
>
<ToggleGroupItem value="all">{t('setting.all')}</ToggleGroupItem>
{mcpCategory.map((cat) => (
<ToggleGroupItem key={cat.id} value={String(cat.id)}>
{cat.name}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
{/* list */}
<MCPEnvDialog
showEnvConfig={showEnvConfig}
onClose={onClose}
onConnect={onConnect}
activeMcp={activeMcp}
></MCPEnvDialog>
<div className="gap-4 pt-4 flex w-full flex-col">
{isLoading && items.length === 0 && (
<div className="py-8 text-ds-text-neutral-muted-default text-center">
{t('setting.loading')}
</div>
)}
{error && (
<div className="py-8 text-ds-text-status-error-strong-default text-center">
{error}
</div>
)}
{!isLoading && !error && items.length === 0 && (
<div className="py-8 text-ds-text-neutral-muted-default text-center">
{t('setting.no-mcp-services')}
</div>
)}
{items.map((item) => (
<div
key={item.id}
className="rounded-2xl bg-ds-bg-neutral-default-default p-4 flex items-center"
>
{/* Left: Icon */}
<div className="mr-4 flex items-center">
{(() => {
const catName = item.category?.name;
const normalizedName = catName?.toLowerCase() || '';
const iconKey = normalizedName
? categoryIconMap[normalizedName]
: undefined;
const iconUrl = iconKey ? svgIcons[iconKey] : undefined;
return iconUrl ? (
<img src={iconUrl} alt={catName} className="h-11 w-9" />
) : (
<Store className="h-11 w-9 text-ds-icon-neutral-default-default" />
);
})()}
</div>
<div className="min-w-0 flex flex-1 flex-col justify-center">
<div className="gap-xs pb-1 flex w-full items-center">
<div className="gap-xs flex flex-1 items-center">
<span className="text-base font-bold leading-9 text-ds-text-neutral-default-default truncate">
{item.name}
</span>
<TooltipSimple content={item.description}>
<CircleAlert className="h-4 w-4 text-ds-icon-neutral-muted-default" />
</TooltipSimple>
</div>
<Button
variant={
!installedIds.includes(item.id) ? 'primary' : 'secondary'
}
size="sm"
onClick={() =>
installedIds.includes(item.id)
? handleDelete(item)
: checkEnv(item.id)
}
>
{installedIds.includes(item.id)
? t('setting.uninstall')
: installing[item.id]
? t('setting.installing')
: installed[item.id]
? t('setting.uninstall')
: t('setting.install')}
</Button>
</div>
{item.home_page &&
item.home_page.startsWith('https://github.com/') && (
<div className="flex items-center">
<img
src={githubIcon}
alt="github"
style={{
width: 14.7,
height: 14.7,
marginRight: 4,
display: 'inline-block',
verticalAlign: 'middle',
}}
/>
<span className="text-xs font-medium leading-3 items-center justify-center self-stretch">
{(() => {
const parts = item.home_page.split('/');
return parts.length > 4 ? parts[4] : item.home_page;
})()}
</span>
</div>
)}
<div className="mt-1 text-sm text-ds-text-neutral-muted-default break-words whitespace-pre-line">
{item.description}
</div>
</div>
</div>
))}
<div ref={loader} />
{isLoading && items.length > 0 && (
<div className="py-4 text-ds-text-neutral-muted-default text-center">
{t('setting.loading-more')}
</div>
)}
{!hasMore && items.length > 0 && (
<div className="py-4 text-ds-text-neutral-muted-default text-center">
{t('setting.no-more-mcp-servers')}
</div>
)}
</div>
</div>
);
}

View file

@ -32,4 +32,10 @@ export interface MCPConfigForm {
command: string;
argsArr: string[];
env: Record<string, string>;
server_url: string;
}
export type ConnectorInstallHint =
| { source: 'open'; key: string }
| { source: 'builtin'; key: string }
| { source: 'custom'; key: string };

View file

@ -13,13 +13,7 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import ConnectorGateway from './ConnectorGateway';
import MCP from './MCP';
export default function Connectors() {
return (
<div className="flex h-auto w-full flex-1 flex-col">
<ConnectorGateway />
<MCP />
</div>
);
return <ConnectorGateway />;
}