diff --git a/src/api/connectors.ts b/src/api/connectors.ts index 667bbdc3..1d31df5e 100644 --- a/src/api/connectors.ts +++ b/src/api/connectors.ts @@ -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(); +const providersListInflight = new Map< + string, + Promise +>(); + +function providersListCacheKey( options: FetchConnectorProvidersOptions = {} -): Promise { - const params: Record = { - 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 { + return fetchConnectorProviders(options); +} + +export async function fetchConnectorProviders( + options: FetchConnectorProvidersOptions = {}, + requestOptions: FetchConnectorProvidersRequestOptions = {} +): Promise { + 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 = { + 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 { + 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 { diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 2fbe8ad8..930cef73 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -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(''); 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< )} - {webNotConnectedItems.length > 0 && ( -
-
- {t('setting.mcp-sidebar-not-connected')} -
- -
- )} ) : ( -

- {t('dashboard.no-results')} -

+
+

+ {t('dashboard.no-results')} +

+ +
)} diff --git a/src/components/ChatBox/BottomBox/PickerPanel.tsx b/src/components/ChatBox/BottomBox/PickerPanel.tsx index 7a19e4b2..209de9f7 100644 --- a/src/components/ChatBox/BottomBox/PickerPanel.tsx +++ b/src/components/ChatBox/BottomBox/PickerPanel.tsx @@ -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([]); + const [builtInItems, setBuiltInItems] = useState([]); + const [openItems, setOpenItems] = useState([]); const [yourMcps, setYourMcps] = useState([]); 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({ )} renderLogo={(item) => { + if (item.id.startsWith('open-')) { + return item.iconUrl ? ( + + ) : ( + + ); + } if (!item.id.startsWith('builtin-')) { return ( diff --git a/src/i18n/locales/ar/connectors.json b/src/i18n/locales/ar/connectors.json new file mode 100644 index 00000000..fb547f7b --- /dev/null +++ b/src/i18n/locales/ar/connectors.json @@ -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." +} diff --git a/src/i18n/locales/ar/index.ts b/src/i18n/locales/ar/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/ar/index.ts +++ b/src/i18n/locales/ar/index.ts @@ -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, diff --git a/src/i18n/locales/de/connectors.json b/src/i18n/locales/de/connectors.json new file mode 100644 index 00000000..47932d01 --- /dev/null +++ b/src/i18n/locales/de/connectors.json @@ -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." +} diff --git a/src/i18n/locales/de/index.ts b/src/i18n/locales/de/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/de/index.ts +++ b/src/i18n/locales/de/index.ts @@ -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, diff --git a/src/i18n/locales/en-us/connectors.json b/src/i18n/locales/en-us/connectors.json new file mode 100644 index 00000000..0c9b9ae1 --- /dev/null +++ b/src/i18n/locales/en-us/connectors.json @@ -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." +} diff --git a/src/i18n/locales/en-us/index.ts b/src/i18n/locales/en-us/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/en-us/index.ts +++ b/src/i18n/locales/en-us/index.ts @@ -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, diff --git a/src/i18n/locales/es/connectors.json b/src/i18n/locales/es/connectors.json new file mode 100644 index 00000000..e853fe90 --- /dev/null +++ b/src/i18n/locales/es/connectors.json @@ -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." +} diff --git a/src/i18n/locales/es/index.ts b/src/i18n/locales/es/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/es/index.ts +++ b/src/i18n/locales/es/index.ts @@ -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, diff --git a/src/i18n/locales/fr/connectors.json b/src/i18n/locales/fr/connectors.json new file mode 100644 index 00000000..bd8a9e98 --- /dev/null +++ b/src/i18n/locales/fr/connectors.json @@ -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 n’est pas disponible.", + "gateway-unavailable-desc": "Connector Gateway n’est 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 d’Open 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 n’a pas pu être enregistré. Actualisez la page et réessayez.", + "status-save-failed-disabled": "Le connecteur a été désactivé, mais son état n’a pas pu être enregistré. Actualisez la page et réessayez.", + "updated": "Connecteur mis à jour", + "save-failed": "Échec de l’enregistrement 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 d’actions", + "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 l’environnement d’intégration local d’Eigent. 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 l’installation de Notion", + "google-calendar-install-failed": "Échec de l’installation 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 d’autres é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 d’autorisation du fournisseur. Cette boîte de dialogue terminera l’installation une fois l’autorisation accordée.", + "oauth-scopes": "Autorisations demandées : {{scopes}}", + "no-auth-desc": "Ce connecteur ne nécessite aucun identifiant.", + "waiting-authorization": "En attente de l’autorisation…", + "authorization-started": "Autorisation démarrée", + "installed-toast": "{{name}} installé", + "no-authorization-url": "Le connecteur n’a pas renvoyé d’URL d’autorisation", + "authorization-pending": "L’autorisation est toujours en attente. Terminez-la dans la fenêtre du fournisseur, puis réessayez.", + "install-failed": "Échec de l’installation du connecteur", + "detail-load-failed": "Échec du chargement des détails du connecteur", + "authorization-incomplete": "L’autorisation n’est pas encore terminée. Terminez-la dans la fenêtre du fournisseur et actualisez à nouveau l’état.", + "refresh-status-failed": "Échec de l’actualisation de l’état du connecteur", + "field-required": "{{field}} est requis", + "complete-authorization": "Terminez l’autorisation dans la fenêtre du fournisseur.", + "refresh-status": "Actualiser l’état", + "built-in-auth-desc": "L’installation ouvre le processus d’autorisation 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 d’un réseau local de confiance.", + "invalid-json": "JSON non valide : {{message}}", + "json-format-error": "Erreur de format JSON : {{message}}", + "parse-failed": "Échec de l’analyse", + "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 l’installation du connecteur personnalisé", + "env-key-value": "Variables d’environnement (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 l’identifiant 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 n’est requise." +} diff --git a/src/i18n/locales/fr/index.ts b/src/i18n/locales/fr/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/fr/index.ts +++ b/src/i18n/locales/fr/index.ts @@ -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, diff --git a/src/i18n/locales/it/connectors.json b/src/i18n/locales/it/connectors.json new file mode 100644 index 00000000..886d4d84 --- /dev/null +++ b/src/i18n/locales/it/connectors.json @@ -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 l’ambiente 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à l’installazione al termine dell’autorizzazione.", + "oauth-scopes": "Autorizzazioni richieste: {{scopes}}", + "no-auth-desc": "Questo connettore non richiede credenziali.", + "waiting-authorization": "In attesa del completamento dell’autorizzazione…", + "authorization-started": "Autorizzazione avviata", + "installed-toast": "{{name}} installato", + "no-authorization-url": "Il connettore non ha restituito un URL di autorizzazione", + "authorization-pending": "L’autorizzazione è 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": "L’autorizzazione 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 l’autorizzazione nella finestra del fornitore.", + "refresh-status": "Aggiorna stato", + "built-in-auth-desc": "L’installazione apre il flusso di autorizzazione del fornitore in una finestra separata.", + "built-in-generic-desc": "Un’integrazione 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 d’ambiente (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 l’ID 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." +} diff --git a/src/i18n/locales/it/index.ts b/src/i18n/locales/it/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/it/index.ts +++ b/src/i18n/locales/it/index.ts @@ -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, diff --git a/src/i18n/locales/ja/connectors.json b/src/i18n/locales/ja/connectors.json new file mode 100644 index 00000000..cdf9d641 --- /dev/null +++ b/src/i18n/locales/ja/connectors.json @@ -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 キーとカスタム検索エンジン(CSE)ID が必要です。", + "google-search-default-desc": "Google 検索はデフォルトで有効です。API キーは必要ありません。" +} diff --git a/src/i18n/locales/ja/index.ts b/src/i18n/locales/ja/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/ja/index.ts +++ b/src/i18n/locales/ja/index.ts @@ -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, diff --git a/src/i18n/locales/ko/connectors.json b/src/i18n/locales/ko/connectors.json new file mode 100644 index 00000000..3646306b --- /dev/null +++ b/src/i18n/locales/ko/connectors.json @@ -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 키가 필요하지 않습니다." +} diff --git a/src/i18n/locales/ko/index.ts b/src/i18n/locales/ko/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/ko/index.ts +++ b/src/i18n/locales/ko/index.ts @@ -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, diff --git a/src/i18n/locales/ru/connectors.json b/src/i18n/locales/ru/connectors.json new file mode 100644 index 00000000..a5daccec --- /dev/null +++ b/src/i18n/locales/ru/connectors.json @@ -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 не требуется." +} diff --git a/src/i18n/locales/ru/index.ts b/src/i18n/locales/ru/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/ru/index.ts +++ b/src/i18n/locales/ru/index.ts @@ -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, diff --git a/src/i18n/locales/zh-Hans/connectors.json b/src/i18n/locales/zh-Hans/connectors.json new file mode 100644 index 00000000..e27609cc --- /dev/null +++ b/src/i18n/locales/zh-Hans/connectors.json @@ -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 密钥和自定义搜索引擎(CSE)ID。", + "google-search-default-desc": "Google 搜索默认已启用,无需 API 密钥。" +} diff --git a/src/i18n/locales/zh-Hans/index.ts b/src/i18n/locales/zh-Hans/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/zh-Hans/index.ts +++ b/src/i18n/locales/zh-Hans/index.ts @@ -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, diff --git a/src/i18n/locales/zh-Hant/connectors.json b/src/i18n/locales/zh-Hant/connectors.json new file mode 100644 index 00000000..44991674 --- /dev/null +++ b/src/i18n/locales/zh-Hant/connectors.json @@ -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 金鑰和自訂搜尋引擎(CSE)ID。", + "google-search-default-desc": "Google 搜尋預設已啟用,無需 API 金鑰。" +} diff --git a/src/i18n/locales/zh-Hant/index.ts b/src/i18n/locales/zh-Hant/index.ts index efa75344..ae235fb7 100644 --- a/src/i18n/locales/zh-Hant/index.ts +++ b/src/i18n/locales/zh-Hant/index.ts @@ -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, diff --git a/src/pages/Connectors/ConnectorGateway.tsx b/src/pages/Connectors/ConnectorGateway.tsx index 68d1faed..90b70d63 100644 --- a/src/pages/Connectors/ConnectorGateway.tsx +++ b/src/pages/Connectors/ConnectorGateway.tsx @@ -12,200 +12,306 @@ // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= +import { mcpInstall, mcpRemove, mcpUpdate } from '@/api/brain'; import { - connectProvider, - createConnectorOAuthAuthorization, disconnectProvider, + fetchConnectedProviders, fetchConnectorProvider, fetchConnectorProviders, - type ConnectorAction, - type ConnectorAuthDefinition, - type ConnectorCredentialField, + invalidateConnectorProvidersCache, + prefetchConnectorProviders, type ConnectorProvider, } from '@/api/connectors'; +import { + fetchPost, + proxyFetchDelete, + proxyFetchGet, + proxyFetchPost, + proxyFetchPut, +} from '@/api/http'; import SearchInput from '@/components/Dashboard/SearchInput'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { - Sheet, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, -} from '@/components/ui/sheet'; -import { Textarea } from '@/components/ui/textarea'; -import { TooltipSimple } from '@/components/ui/tooltip'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Switch } from '@/components/ui/switch'; +import { + useIntegrationManagement, + type IntegrationItem, +} from '@/hooks/useIntegrationManagement'; +import { capitalizeFirstLetter, getProxyBaseURL } from '@/lib'; +import { integrationLeadingIconUrl } from '@/lib/connectorIcons'; +import { useAuthStore } from '@/store/authStore'; import { useServerCapabilityStore } from '@/store/serverCapabilityStore'; +import type { TFunction } from 'i18next'; import { - CheckCircle2, - ChevronLeft, - ChevronRight, + BadgeCheck, + ChevronDown, + Ellipsis, ExternalLink, - KeyRound, - ListChecks, - Loader2, - PlugZap, + Hammer, + Pencil, + Plus, RefreshCw, - ShieldCheck, - Sparkles, + Server, + Settings, Trash2, + Wrench, } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; +import { useSearchParams } from 'react-router-dom'; import { toast } from 'sonner'; +import AddConnectorDialog, { + ProviderIcon, + actionLabel, + isConnectedProvider, + providerActionCount, + providerLabel, + type AddConnectorTarget, +} from './components/AddConnectorDialog'; +import AddCustomConnectorDialog from './components/AddCustomConnectorDialog'; +import { GoogleSearchPanel } from './components/GoogleSearchPanel'; +import MCPConfigDialog from './components/MCPConfigDialog'; +import MCPDeleteDialog from './components/MCPDeleteDialog'; +import type { + ConnectorInstallHint, + MCPConfigForm, + MCPUserItem, +} from './components/types'; +import { arrayToArgsJson, parseArgsToArray } from './components/utils'; -const CONNECTOR_PAGE_SIZE = 24; +const IS_LOCAL_MODE = import.meta.env.VITE_USE_LOCAL_PROXY === 'true'; +const OVERVIEW_ID = '__overview__'; +const HIDDEN_BUILT_INS = new Set([ + 'RAG', + 'X(Twitter)', + 'WhatsApp', + 'Reddit', + 'Github', +]); -function providerLabel(provider: ConnectorProvider): string { - return provider.displayName || provider.service; -} +/** Preferred Open Connector service keys for the overview recommendations. */ +const RECOMMENDED_SERVICE_KEYS = [ + ['slack'], + ['notion'], + ['gmail', 'google_gmail'], + ['google_drive', 'googledrive', 'google-drive'], + ['github'], + ['google_calendar', 'googlecalendar', 'google-calendar'], + ['stripe'], + ['feishu', 'lark'], +] as const; -function providerActionCount(provider: ConnectorProvider): number { - return typeof provider.action_count === 'number' - ? provider.action_count - : Array.isArray(provider.actions) - ? provider.actions.length - : 0; -} +type ConnectorListItem = + | { + id: string; + source: 'open'; + name: string; + active: true; + provider: ConnectorProvider; + } + | { + id: string; + source: 'builtin'; + name: string; + active: true; + item: IntegrationItem; + } + | { + id: string; + source: 'custom'; + name: string; + active: boolean; + subtype: 'local' | 'remote'; + item: MCPUserItem; + }; -function providerActionCountOrZero( - provider: ConnectorProvider | null | undefined -): number { - return provider ? providerActionCount(provider) : 0; -} - -function actionLabel(action: ConnectorAction): string { - return action.name || action.id || 'Unnamed action'; -} - -function isConnected(provider: ConnectorProvider | null | undefined): boolean { - const connection = provider?.connection; - return ( - connection?.configured === true && - connection.virtual !== true && - connection.authType !== 'no_auth' - ); -} - -function authLabel(authType: string): string { - if (authType === 'api_key') return 'API key'; - if (authType === 'custom_credential') return 'Credential'; - if (authType === 'oauth2') return 'OAuth'; - if (authType === 'no_auth') return 'No auth'; - return authType; -} - -function authPriority(authType: string): number { - if (authType === 'api_key') return 0; - if (authType === 'custom_credential') return 1; - if (authType === 'no_auth') return 2; - if (authType === 'oauth2') return 3; - return 10; -} - -function credentialFieldsFor( - auth: ConnectorAuthDefinition | undefined -): ConnectorCredentialField[] { - if (!auth) return []; - if (auth.type === 'api_key') { - return [ - { - key: 'apiKey', - label: auth.label || 'API key', - inputType: 'password', - required: true, - secret: true, - placeholder: auth.placeholder, - description: auth.description, - }, - ...(auth.extraFields || []), - ]; +async function upsertConfigValue(group: string, name: string, value: string) { + const response = await proxyFetchGet('/api/v1/configs'); + const configs = Array.isArray(response) ? response : []; + const existing = configs.find((config: any) => config.config_name === name); + const payload = { + config_group: group, + config_name: name, + config_value: value, + }; + if (existing) { + await proxyFetchPut(`/api/v1/configs/${existing.id}`, payload); + } else { + await proxyFetchPost('/api/v1/configs', payload); } - if (auth.type === 'custom_credential') { - return auth.fields || []; - } - return []; } -function authDefinitions( - provider: ConnectorProvider | null -): ConnectorAuthDefinition[] { - if (!provider) return []; - if (provider.auth?.length) { - return [...provider.auth].sort( - (left, right) => authPriority(left.type) - authPriority(right.type) +function createBuiltInInstallAction( + key: string, + t: TFunction +): () => Promise | void { + if (key === 'Search') return () => undefined; + if (key === 'Notion') { + return async () => { + const response = await fetchPost('/install/tool/notion'); + if (!response?.success) { + throw new Error( + response?.error || t('connectors.notion-install-failed') + ); + } + await upsertConfigValue( + 'Notion', + 'MCP_REMOTE_CONFIG_DIR', + response.toolkit_name || 'NotionMCPToolkit' + ); + }; + } + if (key === 'Google Calendar') { + return async () => { + const response = await fetchPost('/install/tool/google_calendar'); + if (response?.success) { + await upsertConfigValue( + 'Google Calendar', + 'GOOGLE_REFRESH_TOKEN', + 'exists' + ); + return; + } + if (response?.status !== 'authorizing') { + throw new Error( + response?.error || + response?.message || + t('connectors.google-calendar-install-failed') + ); + } + }; + } + + return () => { + const baseUrl = getProxyBaseURL(); + window.open( + `${baseUrl}/api/v1/oauth/${key.toLowerCase()}/login`, + '_blank', + 'width=600,height=700' ); + }; +} + +function buildBuiltInItems(response: unknown, t: TFunction): IntegrationItem[] { + const info = + response && typeof response === 'object' + ? (response as Record) + : {}; + const items = Object.entries(info) + .filter(([key]) => !HIDDEN_BUILT_INS.has(key)) + .map(([key, value]) => ({ + key, + name: key, + env_vars: Array.isArray(value?.env_vars) ? value.env_vars : [], + toolkit: value?.toolkit, + desc: + Array.isArray(value?.env_vars) && value.env_vars.length + ? t('connectors.requires', { vars: value.env_vars.join(', ') }) + : key === 'Notion' + ? t('connectors.notion-desc') + : key === 'Google Calendar' + ? t('connectors.google-calendar-desc') + : t('connectors.generic-desc', { name: key }), + onInstall: createBuiltInInstallAction(key, t), + })); + + if (!items.some((item) => item.key === 'Search')) { + items.unshift({ + key: 'Search', + name: t('connectors.google-search'), + env_vars: ['GOOGLE_API_KEY', 'SEARCH_ENGINE_ID'], + toolkit: undefined, + desc: t('connectors.google-search-desc'), + onInstall: createBuiltInInstallAction('Search', t), + }); } - return (provider.authTypes || []).map((type) => ({ type })); + return items; } -function preferredAuthType(provider: ConnectorProvider | null): string | null { - const definitions = authDefinitions(provider); - if (!definitions.length) return null; - const connectedAuth = provider?.connection?.authType; - if ( - connectedAuth && - definitions.some((auth) => auth.type === connectedAuth) - ) { - return connectedAuth; +function configuredSearch(configs: any[]): boolean { + const names = new Set( + configs + .filter((config) => String(config.config_value || '').trim()) + .map((config) => config.config_name) + ); + return names.has('GOOGLE_API_KEY') && names.has('SEARCH_ENGINE_ID'); +} + +function sourceLabel(item: ConnectorListItem, t: TFunction): string { + if (item.source === 'open') return t('connectors.source-open'); + if (item.source === 'builtin') return t('connectors.source-built-in'); + return item.subtype === 'remote' + ? t('connectors.source-remote') + : t('connectors.source-local'); +} + +async function resolveOpenProviderByKeys( + serviceKeys: readonly string[] +): Promise { + const results = await Promise.allSettled( + serviceKeys.map((service) => fetchConnectorProvider(service)) + ); + for (const result of results) { + if (result.status === 'fulfilled' && result.value?.provider) { + return result.value.provider; + } } - return definitions[0].type; + return null; } -function updateProviderInList( - providers: ConnectorProvider[], - updated: ConnectorProvider -): ConnectorProvider[] { - return providers.map((provider) => - provider.service === updated.service - ? { ...provider, ...updated } - : provider - ); -} +async function resolveOpenProviderByName( + name: string +): Promise { + const normalized = name.toLowerCase().trim(); + const candidates = [ + normalized.replace(/\s+/g, '_'), + normalized.replace(/\s+/g, '-'), + normalized.replace(/\s+/g, ''), + normalized, + ]; + const byKey = await resolveOpenProviderByKeys(candidates); + if (byKey) return byKey; -function ProviderIcon({ - provider, - size = 'sm', -}: { - provider: ConnectorProvider | null | undefined; - size?: 'sm' | 'lg'; -}) { - const iconUrl = provider?.iconUrl || ''; - const [iconFailed, setIconFailed] = useState(false); - - useEffect(() => { - setIconFailed(false); - }, [iconUrl]); - - const shellClass = - size === 'lg' ? 'h-12 w-12 rounded-xl' : 'h-11 w-11 rounded-xl'; - const imageClass = size === 'lg' ? 'h-7 w-7' : 'h-7 w-7'; - const fallbackClass = size === 'lg' ? 'h-6 w-6' : 'h-5 w-5'; - - return ( -
- {iconUrl && !iconFailed ? ( - setIconFailed(true)} - /> - ) : ( - - )} -
- ); + try { + const search = await fetchConnectorProviders({ + page: 1, + pageSize: 24, + query: name, + }); + return ( + search.providers.find((provider) => { + const service = provider.service.toLowerCase(); + const label = providerLabel(provider).toLowerCase(); + return ( + service === normalized || + label === normalized || + service.includes(normalized.replace(/\s+/g, '_')) || + label.includes(normalized) + ); + }) || null + ); + } catch { + return null; + } } export default function ConnectorGateway() { const { t } = useTranslation(); + const [searchParams, setSearchParams] = useSearchParams(); + const { checkAgentTool, modelType } = useAuthStore(); const capabilityStatus = useServerCapabilityStore((state) => state.status); const connectorGatewayEnabled = useServerCapabilityStore((state) => state.isConnectorGatewayEnabled() @@ -213,691 +319,1069 @@ export default function ConnectorGateway() { const fetchCapabilities = useServerCapabilityStore( (state) => state.fetchCapabilities ); - const [providers, setProviders] = useState([]); - const [providerCount, setProviderCount] = useState(0); - const [filteredCount, setFilteredCount] = useState(0); - const [connectedCount, setConnectedCount] = useState(0); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(CONNECTOR_PAGE_SIZE); - const [totalPages, setTotalPages] = useState(1); - const [loadingProviders, setLoadingProviders] = useState(false); - const [providerError, setProviderError] = useState(null); - const [query, setQuery] = useState(''); - const [debouncedQuery, setDebouncedQuery] = useState(''); - const [selectedProvider, setSelectedProvider] = - useState(null); - const [selectedDetail, setSelectedDetail] = - useState(null); - const [loadingDetail, setLoadingDetail] = useState(false); - const [selectedAuthType, setSelectedAuthType] = useState(null); - const [values, setValues] = useState>({}); - const [saving, setSaving] = useState(false); - const [formError, setFormError] = useState(null); + + const [builtInItems, setBuiltInItems] = useState([]); + const [customMcps, setCustomMcps] = useState([]); + const [openConnections, setOpenConnections] = useState( + [] + ); + const [selectedId, setSelectedId] = useState(OVERVIEW_ID); + const [recommendedProviders, setRecommendedProviders] = useState< + ConnectorProvider[] + >([]); + const [recommendedLoading, setRecommendedLoading] = useState(false); + const [openDetail, setOpenDetail] = useState(null); + const [listQuery, setListQuery] = useState(''); + const [loadingOpen, setLoadingOpen] = useState(false); + const [loadingCustom, setLoadingCustom] = useState(false); + const [loadingBuiltIns, setLoadingBuiltIns] = useState(false); + const [detailLoading, setDetailLoading] = useState(false); + const [actionLoading, setActionLoading] = useState(false); + const [pageError, setPageError] = useState(null); + const [browseDialogOpen, setBrowseDialogOpen] = useState(false); + const [browseDialogTarget, setBrowseDialogTarget] = + useState(null); + const [customDialogOpen, setCustomDialogOpen] = useState(false); + const [showConfig, setShowConfig] = useState(null); + const [configForm, setConfigForm] = useState(null); + const [configError, setConfigError] = useState(null); + const [deleteTarget, setDeleteTarget] = useState(null); + const [deleteLoading, setDeleteLoading] = useState(false); + const [actionsExpanded, setActionsExpanded] = useState(false); + const [actionsOverflow, setActionsOverflow] = useState(false); + const preferredSelectionRef = useRef(null); + const actionsListRef = useRef(null); + + const { + installed: rawBuiltInInstalled, + configs, + fetchInstalled: refreshBuiltIns, + saveEnvAndConfig, + handleUninstall, + } = useIntegrationManagement(builtInItems); + + // Google Search is enabled by default on managed models; custom-model users + // must provide their own Google Custom Search credentials. + const searchRequiresApiKey = modelType === 'custom'; + const builtInInstalled = useMemo(() => { + const next = { ...rawBuiltInInstalled }; + next.Search = searchRequiresApiKey ? configuredSearch(configs) : true; + return next; + }, [configs, rawBuiltInInstalled, searchRequiresApiKey]); + + // When Google Search is enabled by default there is nothing to install, so + // keep it out of the Add-connector dialog; it still shows in the sidebar. + const dialogBuiltInItems = useMemo( + () => + searchRequiresApiKey + ? builtInItems + : builtInItems.filter((item) => item.key !== 'Search'), + [builtInItems, searchRequiresApiKey] + ); + + const loadBuiltInCatalog = useCallback(async () => { + setLoadingBuiltIns(true); + try { + const response = await proxyFetchGet('/api/v1/config/info'); + setBuiltInItems(buildBuiltInItems(response, t)); + } catch (error: any) { + setPageError(error?.message || t('connectors.load-built-in-failed')); + setBuiltInItems(buildBuiltInItems({}, t)); + } finally { + setLoadingBuiltIns(false); + } + }, [t]); + + const loadCustomMcps = useCallback(async () => { + setLoadingCustom(true); + try { + const response = await proxyFetchGet('/api/v1/mcp/users'); + setCustomMcps( + Array.isArray(response) + ? response + : Array.isArray(response?.items) + ? response.items + : [] + ); + } catch (error: any) { + setPageError(error?.message || t('connectors.load-custom-failed')); + setCustomMcps([]); + } finally { + setLoadingCustom(false); + } + }, [t]); + + const loadOpenConnections = useCallback(async () => { + if (!connectorGatewayEnabled) { + setOpenConnections([]); + return; + } + setLoadingOpen(true); + try { + setOpenConnections(await fetchConnectedProviders()); + } catch (error: any) { + setPageError(error?.message || t('connectors.load-open-failed')); + setOpenConnections([]); + } finally { + setLoadingOpen(false); + } + }, [connectorGatewayEnabled, t]); + + const refreshAll = useCallback(async () => { + setPageError(null); + await Promise.all([ + loadOpenConnections(), + loadCustomMcps(), + refreshBuiltIns(), + ]); + }, [loadCustomMcps, loadOpenConnections, refreshBuiltIns]); useEffect(() => { void fetchCapabilities(); - }, [fetchCapabilities]); + void loadBuiltInCatalog(); + void loadCustomMcps(); + }, [fetchCapabilities, loadBuiltInCatalog, loadCustomMcps]); useEffect(() => { - const timer = window.setTimeout(() => { - setDebouncedQuery(query.trim()); - }, 250); - return () => window.clearTimeout(timer); - }, [query]); - - const refreshProviders = useCallback( - async (targetPage: number = page) => { - if (!connectorGatewayEnabled) return; - setLoadingProviders(true); - setProviderError(null); - try { - const response = await fetchConnectorProviders({ - page: targetPage, - pageSize, - query: debouncedQuery, - }); - setProviders(response.providers); - setProviderCount(response.provider_count); - setFilteredCount(response.filtered_count); - setConnectedCount(response.connected_count); - setPageSize(response.page_size); - setTotalPages(response.total_pages); - if (response.page !== targetPage) { - setPage(response.page); - } - } catch (error: any) { - setProviderError( - error?.message || - t('setting.connector-gateway-load-failed', { - defaultValue: 'Failed to load Connector Gateway providers', - }) - ); - setProviders([]); - setProviderCount(0); - setFilteredCount(0); - setConnectedCount(0); - setTotalPages(1); - } finally { - setLoadingProviders(false); - } - }, - [connectorGatewayEnabled, debouncedQuery, page, pageSize, t] - ); + if (capabilityStatus !== 'ready' || !connectorGatewayEnabled) return; + // Warm the browse-dialog page-1 cache so Add Connector opens instantly. + void prefetchConnectorProviders({ page: 1, pageSize: 24 }); + void loadOpenConnections(); + }, [capabilityStatus, connectorGatewayEnabled, loadOpenConnections]); useEffect(() => { - if (connectorGatewayEnabled) { - void refreshProviders(page); + if (capabilityStatus !== 'ready' || !connectorGatewayEnabled) { + setRecommendedProviders([]); + setRecommendedLoading(false); + return; } - }, [connectorGatewayEnabled, debouncedQuery, page, refreshProviders]); - const selected = selectedDetail || selectedProvider; - const selectedAuth = useMemo( - () => - authDefinitions(selected).find((auth) => auth.type === selectedAuthType), - [selected, selectedAuthType] - ); - const selectedFields = useMemo( - () => credentialFieldsFor(selectedAuth), - [selectedAuth] - ); - const selectedConnected = isConnected(selected); - const authOptions = authDefinitions(selected); - const selectedActions = selected?.actions || []; - const canSave = - Boolean(selected && selectedAuth) && - (selectedAuth?.type === 'oauth2' || - selectedAuth?.type === 'no_auth' || - selectedFields.every((field) => !field.required || values[field.key])); - const pageStart = filteredCount === 0 ? 0 : (page - 1) * pageSize + 1; - const pageEnd = Math.min(page * pageSize, filteredCount); - const showPagination = filteredCount > pageSize; - const canGoPrevious = page > 1 && !loadingProviders; - const canGoNext = page < totalPages && !loadingProviders; - const initialProvidersLoading = - (capabilityStatus === 'loading' || loadingProviders) && - providers.length === 0; - const listRefreshing = loadingProviders && providers.length > 0; - const pagedGridMinHeightClass = showPagination - ? 'min-h-[1872px] md:min-h-[936px] xl:min-h-[624px]' - : ''; - const skeletonItems = Array.from({ length: pageSize || CONNECTOR_PAGE_SIZE }); + let cancelled = false; + setRecommendedLoading(true); + void (async () => { + const results = await Promise.all( + RECOMMENDED_SERVICE_KEYS.map((serviceKeys) => + resolveOpenProviderByKeys(serviceKeys) + ) + ); + if (cancelled) return; + const providers: ConnectorProvider[] = []; + const seen = new Set(); + for (const provider of results) { + if (!provider || seen.has(provider.service)) continue; + seen.add(provider.service); + providers.push(provider); + } + setRecommendedProviders(providers); + setRecommendedLoading(false); + })(); - const openProvider = useCallback((provider: ConnectorProvider) => { - setSelectedProvider(provider); - setSelectedDetail(null); - setSelectedAuthType(preferredAuthType(provider)); - setValues({}); - setFormError(null); - }, []); + return () => { + cancelled = true; + }; + }, [capabilityStatus, connectorGatewayEnabled]); - const closeProvider = useCallback(() => { - setSelectedProvider(null); - setSelectedDetail(null); - setSelectedAuthType(null); - setValues({}); - setFormError(null); - }, []); + const connectorItems = useMemo(() => { + const openItems: ConnectorListItem[] = openConnections.map((provider) => ({ + id: `open:${provider.service}`, + source: 'open', + name: providerLabel(provider), + active: true, + provider, + })); + const builtIns: ConnectorListItem[] = builtInItems + .filter((item) => builtInInstalled[item.key]) + .map((item) => ({ + id: `builtin:${item.key}`, + source: 'builtin', + name: item.name, + active: true, + item, + })); + const custom: ConnectorListItem[] = customMcps.map((item) => ({ + id: `custom:${item.id}`, + source: 'custom', + name: capitalizeFirstLetter(item.mcp_name || item.mcp_key || ''), + active: Number(item.status) === 1, + subtype: Number(item.type) === 2 ? 'remote' : 'local', + item, + })); + return [...openItems, ...builtIns, ...custom].sort((left, right) => { + if (left.active !== right.active) return left.active ? -1 : 1; + return left.name.localeCompare(right.name); + }); + }, [builtInInstalled, builtInItems, customMcps, openConnections]); useEffect(() => { - if (!selectedProvider) return; - let cancelled = false; - setLoadingDetail(true); - void fetchConnectorProvider(selectedProvider.service) - .then((response) => { - if (cancelled) return; - setSelectedDetail(response.provider); - setSelectedAuthType(preferredAuthType(response.provider)); - setProviders((current) => - updateProviderInList(current, response.provider) + const preferred = preferredSelectionRef.current; + if (preferred) { + const match = connectorItems.find((item) => { + if (preferred.source === 'open') { + return item.id === `open:${preferred.key}`; + } + if (preferred.source === 'builtin') { + return item.id === `builtin:${preferred.key}`; + } + return ( + item.source === 'custom' && + (item.item.mcp_name === preferred.key || + item.item.mcp_key === preferred.key) ); + }); + if (match) { + setSelectedId(match.id); + preferredSelectionRef.current = null; + return; + } + } + + if (selectedId === OVERVIEW_ID) return; + if (connectorItems.some((item) => item.id === selectedId)) return; + setSelectedId(OVERVIEW_ID); + }, [connectorItems, selectedId]); + + const selected = useMemo( + () => connectorItems.find((item) => item.id === selectedId) || null, + [connectorItems, selectedId] + ); + + const selectedOpenService = + selected?.source === 'open' ? selected.provider.service : null; + + useEffect(() => { + if (!selectedOpenService) { + setOpenDetail(null); + return; + } + let cancelled = false; + setDetailLoading(true); + setActionsExpanded(false); + void fetchConnectorProvider(selectedOpenService) + .then((response) => { + if (!cancelled) setOpenDetail(response.provider); }) - .catch((error: any) => { - if (cancelled) return; - setFormError(error?.message || 'Failed to load connector details'); + .catch(() => { + // Fall back to the list-provider data via `openDetail || item.provider`. + if (!cancelled) setOpenDetail(null); }) .finally(() => { - if (!cancelled) setLoadingDetail(false); + if (!cancelled) setDetailLoading(false); }); return () => { cancelled = true; }; - }, [selectedProvider]); + }, [selectedOpenService]); - const refreshSelectedProvider = useCallback(async () => { - if (!selected) return; - const response = await fetchConnectorProvider(selected.service); - setSelectedDetail(response.provider); - setProviders((current) => updateProviderInList(current, response.provider)); - }, [selected]); + const openDetailProvider = + selected?.source === 'open' ? openDetail || selected.provider : null; - const saveConnection = useCallback(async () => { - if (!selected || !selectedAuth) return; - if (selectedAuth.type === 'oauth2') { - setSaving(true); - setFormError(null); - try { - const authorization = await createConnectorOAuthAuthorization( - selected.service, - selected.connection?.connectionName - ); - if (!authorization.authorizationUrl) { - throw new Error( - 'Connector Gateway did not return an authorization URL' - ); - } - window.open( - authorization.authorizationUrl, - 'eigent_connector_oauth', - 'popup=yes,width=720,height=760,menubar=no,toolbar=no,location=yes,status=no' - ); - toast.success( - t('setting.connector-gateway-oauth-started', { - defaultValue: 'OAuth authorization started', - }) - ); - } catch (error: any) { - setFormError(error?.message || 'Failed to start OAuth authorization'); - } finally { - setSaving(false); + useLayoutEffect(() => { + const element = actionsListRef.current; + if (!element || !openDetailProvider?.actions?.length) { + setActionsOverflow(false); + return; + } + setActionsOverflow(element.scrollHeight > 200); + }, [openDetailProvider?.actions, openDetailProvider?.service, detailLoading]); + + useEffect(() => { + const action = searchParams.get('connectorAction'); + const section = searchParams.get('connectorSection'); + if (action !== 'add' && section !== 'mcp-tools' && section !== 'your-mcp') { + return; + } + if (section === 'your-mcp') { + setCustomDialogOpen(true); + } else { + setBrowseDialogTarget(null); + setBrowseDialogOpen(true); + } + const next = new URLSearchParams(searchParams); + next.delete('connectorAction'); + next.delete('connectorSection'); + setSearchParams(next, { replace: true }); + }, [searchParams, setSearchParams]); + + useEffect(() => { + if (!showConfig) { + setConfigForm(null); + setConfigError(null); + return; + } + setConfigForm({ + mcp_name: showConfig.mcp_name || '', + mcp_desc: showConfig.mcp_desc || '', + command: showConfig.command || '', + argsArr: showConfig.args ? parseArgsToArray(showConfig.args) : [], + env: showConfig.env ? { ...showConfig.env } : {}, + server_url: showConfig.server_url || '', + }); + }, [showConfig]); + + const visibleItems = useMemo(() => { + const query = listQuery.trim().toLowerCase(); + if (!query) return connectorItems; + return connectorItems.filter( + (item) => + item.name.toLowerCase().includes(query) || + sourceLabel(item, t).toLowerCase().includes(query) + ); + }, [connectorItems, listQuery, t]); + + const openBrowseDialog = (target: AddConnectorTarget = null) => { + // Non-local hosts always use Open Connectors — never Built-in. Open the + // dialog immediately and resolve the matching Open provider in the + // background so the button never feels dead. + if (!IS_LOCAL_MODE && target?.source === 'builtin') { + setBrowseDialogTarget(null); + setBrowseDialogOpen(true); + if (connectorGatewayEnabled) { + const builtInKey = target.item.key; + void resolveOpenProviderByName(builtInKey).then((provider) => { + if (provider) { + setBrowseDialogTarget({ source: 'open', provider }); + } + }); } return; } + setBrowseDialogTarget(target); + setBrowseDialogOpen(true); + }; - setSaving(true); - setFormError(null); - try { - await connectProvider(selected.service, { - auth_type: selectedAuth.type, - values: - selectedAuth.type === 'no_auth' - ? {} - : selectedFields.reduce>((acc, field) => { - acc[field.key] = values[field.key] || ''; - return acc; - }, {}), - }); - await refreshSelectedProvider(); - setPage(1); - await refreshProviders(1); - toast.success( - t('setting.connector-gateway-saved', { - defaultValue: 'Connector saved', - }) - ); - } catch (error: any) { - setFormError(error?.message || 'Failed to save connector'); - } finally { - setSaving(false); + const openCustomDialog = () => { + setCustomDialogOpen(true); + }; + + const openRecommendedConnector = (provider: ConnectorProvider) => { + const existing = connectorItems.find( + (item) => + item.source === 'open' && item.provider.service === provider.service + ); + if (existing) { + setSelectedId(existing.id); + return; } - }, [ - refreshProviders, - refreshSelectedProvider, - selected, - selectedAuth, - selectedFields, - t, - values, - ]); + openBrowseDialog({ source: 'open', provider }); + }; - const removeConnection = useCallback(async () => { - if (!selected?.connection) return; - setSaving(true); - setFormError(null); + const handleInstalled = useCallback( + async (hint: ConnectorInstallHint) => { + preferredSelectionRef.current = hint; + invalidateConnectorProvidersCache(); + await refreshAll(); + }, + [refreshAll] + ); + + const handleDisconnectOpen = async (provider: ConnectorProvider) => { + setActionLoading(true); try { await disconnectProvider( - selected.service, - selected.connection.connectionName + provider.service, + provider.connection?.connectionName ); - await refreshSelectedProvider(); - setPage(1); - await refreshProviders(1); + invalidateConnectorProvidersCache(); toast.success( - t('setting.connector-gateway-disconnected', { - defaultValue: 'Connector disconnected', - }) + t('connectors.disconnected', { name: providerLabel(provider) }) ); + await loadOpenConnections(); } catch (error: any) { - setFormError(error?.message || 'Failed to disconnect connector'); + toast.error(error?.message || t('connectors.disconnect-failed')); } finally { - setSaving(false); + setActionLoading(false); } - }, [refreshProviders, refreshSelectedProvider, selected, t]); + }; - if (!connectorGatewayEnabled) { - return null; - } + const handleDisconnectBuiltIn = async (item: IntegrationItem) => { + setActionLoading(true); + try { + await handleUninstall(item); + await refreshBuiltIns(); + toast.success(t('connectors.disconnected', { name: item.name })); + } catch (error: any) { + toast.error(error?.message || t('connectors.disconnect-failed')); + } finally { + setActionLoading(false); + } + }; - return ( - <> -
-
-
-
-
- -
-
-
- Connector Gateway -
-
- {providerCount || providers.length} connectors - {connectedCount > 0 ? ` | ${connectedCount} connected` : ''} -
-
-
-
- { - setQuery(event.target.value); - setPage(1); + const handleCustomSwitch = async (item: MCPUserItem, checked: boolean) => { + setActionLoading(true); + try { + const key = item.mcp_key || item.mcp_name; + if (checked) { + if (Number(item.type) === 2) { + await mcpInstall(key, { url: item.server_url || '' }); + } else { + await mcpInstall(key, { + description: item.mcp_desc || '', + command: item.command || '', + args: item.args ? parseArgsToArray(item.args) : [], + ...(item.env && Object.keys(item.env).length + ? { env: item.env } + : {}), + }); + } + } else { + await mcpRemove(key); + } + try { + await proxyFetchPut(`/api/v1/mcp/users/${item.id}`, { + status: checked ? 1 : 2, + }); + } catch { + // The runtime install/remove already succeeded; only the saved status + // is stale now, so surface that specifically. + toast.error( + t( + checked + ? 'connectors.status-save-failed-enabled' + : 'connectors.status-save-failed-disabled' + ) + ); + } + await loadCustomMcps(); + } catch (error: any) { + toast.error(error?.message || t('connectors.update-failed')); + } finally { + setActionLoading(false); + } + }; + + const handleConfigSave = async (event: React.FormEvent) => { + event.preventDefault(); + if (!configForm || !showConfig) return; + setActionLoading(true); + setConfigError(null); + try { + const isRemote = Number(showConfig.type) === 2; + const payload = isRemote + ? { + mcp_name: configForm.mcp_name, + mcp_desc: configForm.mcp_desc, + server_url: configForm.server_url, + } + : { + mcp_name: configForm.mcp_name, + mcp_desc: configForm.mcp_desc, + command: configForm.command, + args: arrayToArgsJson(configForm.argsArr), + env: configForm.env, + }; + await proxyFetchPut(`/api/v1/mcp/users/${showConfig.id}`, payload); + if (isRemote) { + await mcpUpdate(showConfig.mcp_key || showConfig.mcp_name, { + url: configForm.server_url, + }); + } else { + const brainPayload: Record = { + description: configForm.mcp_desc, + command: configForm.command, + args: arrayToArgsJson(configForm.argsArr), + }; + if (Object.keys(configForm.env).length) { + brainPayload.env = configForm.env; + } + await mcpUpdate( + showConfig.mcp_key || showConfig.mcp_name, + brainPayload + ); + } + setShowConfig(null); + await loadCustomMcps(); + toast.success(t('connectors.updated')); + } catch (error: any) { + setConfigError(error?.message || t('connectors.save-failed')); + } finally { + setActionLoading(false); + } + }; + + const handleDelete = async () => { + if (!deleteTarget) return; + setDeleteLoading(true); + try { + checkAgentTool(deleteTarget.mcp_name); + await proxyFetchDelete(`/api/v1/mcp/users/${deleteTarget.id}`); + const key = deleteTarget.mcp_key || deleteTarget.mcp_name; + if (key) await mcpRemove(key); + setDeleteTarget(null); + await loadCustomMcps(); + toast.success(t('connectors.deleted')); + } catch (error: any) { + toast.error(error?.message || t('connectors.delete-failed')); + } finally { + setDeleteLoading(false); + } + }; + + const pageLoading = loadingOpen || loadingCustom || loadingBuiltIns; + + const renderListIcon = (item: ConnectorListItem) => { + if (item.source === 'open') { + return ; + } + if (item.source === 'builtin') { + const iconUrl = integrationLeadingIconUrl(item.item.key); + return iconUrl ? ( + + ) : ( + + ); + } + return item.subtype === 'remote' ? ( + + ) : ( + + ); + }; + + const isDefaultEnabledSearch = (item: ConnectorListItem) => + item.source === 'builtin' && + item.item.key === 'Search' && + !searchRequiresApiKey; + + const renderDetailHeader = (item: ConnectorListItem) => ( +
+ {item.source === 'open' ? ( + + ) : ( + renderListIcon(item) + )} + + {item.name} + + {item.source !== 'open' ? ( + + {sourceLabel(item, t)} + + ) : null} +
+ {item.source === 'custom' ? ( + + void handleCustomSwitch(item.item, checked) + } + aria-label={ + item.active + ? t('connectors.disable-connector') + : t('connectors.enable-connector') + } + /> + ) : item.source === 'builtin' && item.item.key === 'Search' ? null : ( + + )} + {isDefaultEnabledSearch(item) ? null : ( + + + + + + {item.source === 'custom' ? ( + setShowConfig(item.item)}> + + {t('connectors.edit')} + + ) : null} + { + if (item.source === 'open') { + void handleDisconnectOpen(item.provider); + return; + } + if (item.source === 'builtin') { + void handleDisconnectBuiltIn(item.item); + return; + } + setDeleteTarget(item.item); }} - placeholder={t('setting.search-mcp')} - /> - - - -
-
+ > + + {t('connectors.delete')} + + + + )} +
+
+ ); - {initialProvidersLoading ? ( -
- {skeletonItems.map((_, index) => ( + const renderOpenDetailBody = ( + item: Extract + ) => { + const provider = openDetail || item.provider; + if (detailLoading) { + return ( +
+ ); + } + return ( + <> + {provider.connection?.profile?.displayName ? ( +
+ + {t('connectors.connected-account')} + + + {provider.connection.profile.displayName} + +
+ ) : null} + + {provider.actions?.length ? ( +
+
+ + {t('connectors.supported-actions')} + + + {provider.actions.length || providerActionCount(provider)} + +
+
+
-
-
-
-
-
-
+ {provider.actions.map((action, index) => ( + + {actionLabel(action, t)} + + ))}
- ))} -
- ) : providerError ? ( -
- {providerError} -
- ) : providers.length === 0 ? ( -
- {t('setting.no-connectors-found', { - defaultValue: 'No connectors found', - })} -
- ) : ( - <> -
-
- {providers.map((provider) => { - const connected = isConnected(provider); - return ( - - ); - })} -
- {listRefreshing ? ( -
-
- - {t('setting.loading')} -
-
+ {!actionsExpanded && actionsOverflow ? ( +
) : null}
- {showPagination ? ( -
-
- Showing {pageStart}-{pageEnd} of {filteredCount} - {debouncedQuery ? ' results' : ' connectors'} -
-
- -
- {page} / {totalPages} -
- -
-
+ {actionsOverflow ? ( + ) : null} - +
+
+ ) : null} + + {provider.homepageUrl ? ( + + {t('connectors.provider-website')} + + + ) : null} + + ); + }; + + const renderBuiltInDetailBody = ( + item: Extract + ) => { + if (item.item.key === 'Search') { + return ( +
+ void refreshBuiltIns()} /> +
+ ); + } + return ( +
+ + {t('connectors.built-in-title')} + + + {t('connectors.built-in-desc')} + + {item.item.env_vars.length ? ( + + {t('connectors.configuration', { + vars: item.item.env_vars.join(', '), + })} + + ) : null} +
+ ); + }; + + const renderCustomDetailBody = ( + item: Extract + ) => { + const mcp = item.item; + return ( +
+
+ + {t('connectors.status')} + + + {item.active ? t('connectors.active') : t('connectors.disabled')} + +
+ {item.subtype === 'remote' ? ( +
+ + {t('connectors.server-url')} + + + {mcp.server_url || t('connectors.not-configured')} + +
+ ) : ( +
+
+ + {t('connectors.command')} + + + {mcp.command || t('connectors.not-configured')} + +
+ {mcp.args ? ( +
+ + {t('connectors.arguments')} + + + {parseArgsToArray(mcp.args).join(' ')} + +
+ ) : null} +
+ )} +
+ ); + }; + + const renderDetailPanel = (item: ConnectorListItem) => ( +
+ {renderDetailHeader(item)} +
+ {item.source === 'open' + ? renderOpenDetailBody(item) + : item.source === 'builtin' + ? renderBuiltInDetailBody(item) + : renderCustomDetailBody(item)} +
+
+ ); + + const renderOverviewPanel = () => { + const count = connectorItems.length; + return ( +
+
+ + {pageLoading && count === 0 ? '—' : count} + + + {count === 1 + ? t('connectors.count-one') + : t('connectors.count-other')} + +
+ +
+ + {t('connectors.recommended')} + + {recommendedLoading && recommendedProviders.length === 0 ? ( +
+ {Array.from({ length: 8 }).map((_, index) => ( +
+ ))} +
+ ) : recommendedProviders.length === 0 ? ( +
+ {connectorGatewayEnabled + ? t('connectors.no-recommended') + : t('connectors.gateway-unavailable')} +
+ ) : ( +
+ {recommendedProviders.map((provider) => { + const liveProvider = + openConnections.find( + (item) => item.service === provider.service + ) || provider; + const connected = isConnectedProvider(liveProvider); + const existing = connectorItems.find( + (item) => + item.source === 'open' && + item.provider.service === provider.service + ); + return ( + + ); + })} +
)}
+ ); + }; - { - if (!open) closeProvider(); - }} - > - - -
- -
- - {selected ? providerLabel(selected) : 'Connector'} - - - {selected?.description || - `${providerActionCountOrZero(selected)} actions`} - -
-
-
+ return ( +
+
+

+ {t('connectors.title')} +

+
+ setListQuery(event.target.value)} + placeholder={t('connectors.search-placeholder')} + /> + + +
+
-
- {loadingDetail ? ( -
-
-
-
-
- ) : selected ? ( - <> -
- {selectedConnected ? ( - - - Connected - - ) : null} - {selected.recommended ? ( - - - Popular - - ) : null} - - {providerActionCount(selected)} actions - - {selected.homepageUrl ? ( - - Website - - - ) : null} + {pageError ? ( +
+ {pageError} + +
+ ) : null} + +
+