From 20b93e70cedb14892eabfc09ec09edb0900fa2f5 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 5 Oct 2026 17:13:32 +0300 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat(integrations):=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BE=D0=BA=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D0=BA=D0=B0=D1=81=20=D0=BD=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integrations/AddIntegrationButton.tsx | 18 +++-- .../integrations/ConnectionsTable.tsx | 80 ++++++++++--------- .../integrations/IntegrationPageLayout.tsx | 23 ++++++ .../integrations/IntegrationSettingsPage.tsx | 15 +--- .../integrations/WebIntegrationPage.tsx | 58 +++++++------- .../features/integrations/add-integration.css | 6 +- .../integrations/connections-table.css | 15 ++++ .../external/ExternalServerPage.tsx | 30 +++---- .../integrations/external/ServerHeader.tsx | 11 ++- .../external/ServerPageLoading.tsx | 4 +- .../integrations/external/ServerPageState.tsx | 21 +++++ .../integrations/external/ServerWarning.tsx | 5 +- .../features/integrations/external/styles.css | 11 ++- .../src/features/settings/useIntegrations.ts | 1 + .../src/i18n/external-servers.en.ts | 3 + .../src/i18n/external-servers.ru.ts | 3 + 16 files changed, 186 insertions(+), 118 deletions(-) create mode 100644 apps/internal-ui/src/features/integrations/IntegrationPageLayout.tsx create mode 100644 apps/internal-ui/src/features/integrations/connections-table.css create mode 100644 apps/internal-ui/src/features/integrations/external/ServerPageState.tsx diff --git a/apps/internal-ui/src/features/integrations/AddIntegrationButton.tsx b/apps/internal-ui/src/features/integrations/AddIntegrationButton.tsx index c864111..96b7e1e 100644 --- a/apps/internal-ui/src/features/integrations/AddIntegrationButton.tsx +++ b/apps/internal-ui/src/features/integrations/AddIntegrationButton.tsx @@ -1,4 +1,5 @@ import { Dropdown } from "antd"; +import { useState } from "react"; import { t } from "../../i18n"; import { Icon } from "../../shared/icons"; import { Button } from "../../shared/ui-controls"; @@ -6,16 +7,19 @@ import type { ServerKind } from "./external/types"; import "./add-integration.css"; export function AddIntegrationButton({ onEntry, onServer }: { onEntry: () => void; onServer: (kind: ServerKind) => void }) { - const option = (icon: "message" | "server" | "swap", title: string, hint: string, action: () => void) => - ; - return + ; + return { setOpen(false); if (key === "entry") onEntry(); else onServer(key as ServerKind); }, + items: [ { type: "group", label: t("servers.entry"), children: [ - { key: "entry", label: option("message", t("servers.entry_title"), t("servers.entry_hint"), onEntry) }, + { key: "entry", label: option("message", t("servers.entry_title"), t("servers.entry_hint")) }, ] }, { type: "divider" }, { type: "group", label: t("servers.external_group"), children: [ - { key: "mcp", label: option("server", t("servers.new_mcp"), t("servers.mcp_hint"), () => onServer("mcp")) }, - { key: "http", label: option("swap", t("servers.new_http"), t("servers.http_hint"), () => onServer("http")) }, + { key: "mcp", label: option("server", t("servers.new_mcp"), t("servers.mcp_hint")) }, + { key: "http", label: option("swap", t("servers.new_http"), t("servers.http_hint")) }, ] }, - ] }}>; + ] }}>; } diff --git a/apps/internal-ui/src/features/integrations/ConnectionsTable.tsx b/apps/internal-ui/src/features/integrations/ConnectionsTable.tsx index 7de7695..ba6bf7e 100644 --- a/apps/internal-ui/src/features/integrations/ConnectionsTable.tsx +++ b/apps/internal-ui/src/features/integrations/ConnectionsTable.tsx @@ -4,6 +4,7 @@ import { PROVIDERS, type Integration } from "./model"; import { ConnectionIcon, RowActions, StatusCell } from "./rows"; import { t, tn } from "../../i18n"; import { serverHost } from "./external/model"; +import "./connections-table.css"; // Подстрока подключения (SPEC-CHATBALLS-0025 §2.4): провайдер + идентификатор. function subtitle(integration: Integration): string { @@ -28,44 +29,47 @@ type ConnectionsTableProps = { export function ConnectionsTable({ items, testingId, onTest, onEdit, onToggleActive, onDelete }: ConnectionsTableProps) { return ( -
- - - - - - - - - - {items.map((item) => ( - - - - - + <> +
+
{t("settings.title")}{t("servers.agent_tools")}{t("common.status_2")} -
-
- - {item.provider === "WEB" || item.kind === "EXTERNAL_SERVER" - ? - : {item.name}}{subtitle(item)} -
-
- {item.externalServer ? {tn("servers.count", item.externalServer.type === "http" ? 1 : item.externalServer.tools?.length ?? 0)} : item.channel - ? ( - - {item.channel.name} - - ) - : —} - - -
+ + + + + + - ))} - -
{t("settings.title")}{t("servers.agent_tools")}{t("common.status_2")}
-
+ + + {items.map((item) => ( + + +
+ + {item.provider === "WEB" || item.kind === "EXTERNAL_SERVER" + ? + : {item.name}}{subtitle(item)} +
+ + + {item.externalServer ? {tn("servers.count", item.externalServer.type === "http" ? 1 : item.externalServer.tools?.length ?? 0)} : item.channel + ? ( + + {item.channel.name} + + ) + : —} + + + + + + + ))} + + + +

{t("servers.list_note")}

+ ); } diff --git a/apps/internal-ui/src/features/integrations/IntegrationPageLayout.tsx b/apps/internal-ui/src/features/integrations/IntegrationPageLayout.tsx new file mode 100644 index 0000000..6580486 --- /dev/null +++ b/apps/internal-ui/src/features/integrations/IntegrationPageLayout.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from "react"; +import { SectionMenu, type SectionMenuItem } from "../../shared/SectionMenu"; +import "../support-portals/styles-detail.css"; +import "../support-portals/styles-settings.css"; +import "./web-integration.css"; + +export function IntegrationPageLayout({ header, items, activeKey, note, onSelect, children, className = "" }: { + header: ReactNode; + items: readonly SectionMenuItem[]; + activeKey: Key; + note: string; + onSelect: (key: Key) => void; + children: ReactNode; + className?: string; +}) { + return
+ {header} +
+ +
{children}
+
+
; +} diff --git a/apps/internal-ui/src/features/integrations/IntegrationSettingsPage.tsx b/apps/internal-ui/src/features/integrations/IntegrationSettingsPage.tsx index 67b5f13..216f120 100644 --- a/apps/internal-ui/src/features/integrations/IntegrationSettingsPage.tsx +++ b/apps/internal-ui/src/features/integrations/IntegrationSettingsPage.tsx @@ -1,12 +1,9 @@ -import { t } from "../../i18n"; -import { ContentState } from "../../shared/ui"; -import { Icon } from "../../shared/icons"; -import { Button } from "../../shared/ui-controls"; import { useIntegrations } from "../settings/useIntegrations"; import { WebIntegrationPage } from "./WebIntegrationPage"; import { ExternalServerPage } from "./external/ExternalServerPage"; import type { ServerKind } from "./external/types"; import { ServerPageLoading } from "./external/ServerPageLoading"; +import { ServerPageState } from "./external/ServerPageState"; export function IntegrationSettingsPage({ integrationId, createKind, onCreated, onOpenSettings, onBack }: { integrationId?: number; createKind?: ServerKind; onCreated: (id: number) => void; onOpenSettings: () => void; onBack: () => void; @@ -14,15 +11,9 @@ export function IntegrationSettingsPage({ integrationId, createKind, onCreated, const list = useIntegrations(true); const integration = list.items.find((i) => i.id === integrationId); if (list.loading) return ; - if (list.failed || (!createKind && !integration)) return } - title={t(list.failed ? "servers.load_failed" : "servers.not_found")} - text={t(list.failed ? "servers.load_failed_hint" : "servers.not_found_hint")} - action={list.failed ? - : } />; + if (list.failed || (!createKind && !integration)) return ; if (integration?.provider === "WEB") return ; if (createKind || integration?.externalServer) return ; - return } title={t("servers.not_found")} text={t("servers.not_found_hint")} - action={} />; + return ; } diff --git a/apps/internal-ui/src/features/integrations/WebIntegrationPage.tsx b/apps/internal-ui/src/features/integrations/WebIntegrationPage.tsx index 3eb314e..b1af14b 100644 --- a/apps/internal-ui/src/features/integrations/WebIntegrationPage.tsx +++ b/apps/internal-ui/src/features/integrations/WebIntegrationPage.tsx @@ -1,9 +1,8 @@ import { useState } from "react"; -import "../support-portals/styles-detail.css"; -import "../support-portals/styles-settings.css"; import { fmt, t } from "../../i18n"; -import { SectionMenu, type SectionMenuItem } from "../../shared/SectionMenu"; +import type { SectionMenuItem } from "../../shared/SectionMenu"; +import { IntegrationPageLayout } from "./IntegrationPageLayout"; import { EmptyState, LoadingState } from "../../shared/ui"; import { DeleteIntegrationDialog } from "./DeleteIntegrationDialog"; import { WebIntegrationBasics } from "./WebIntegrationBasics"; @@ -39,36 +38,33 @@ export function WebIntegrationPage({ integrationId, onOpenSettings, onBack }: { if (!integration) return ; return ( -
- -
- { - if (item.key === "fields") return { ...item, hint: { text: fmt.number(fieldCount ?? integration.config.fields?.length ?? 0) } }; - if (item.key === "form" && (formEnabled ?? integration.config.preChat?.enabled)) return { ...item, hint: { text: t("pre_chat.on") } }; - return item; - })} - activeKey={section} - note={t("settings.web_changes_apply_after_save")} - onSelect={(next) => { if (next === "danger") setConfirmingDelete(true); else setSection(next); }} - /> -
- + + + {section === "look" && } + {confirmingDelete && setConfirmingDelete(false)} onDeleted={onBack} />} -
+ ); } diff --git a/apps/internal-ui/src/features/integrations/add-integration.css b/apps/internal-ui/src/features/integrations/add-integration.css index 5566c28..95de68a 100644 --- a/apps/internal-ui/src/features/integrations/add-integration.css +++ b/apps/internal-ui/src/features/integrations/add-integration.css @@ -2,4 +2,8 @@ .app-dropdown.is-server-add .ant-dropdown-menu-item-group-title { padding: 8px 10px 4px; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--n-4); } .app-dropdown.is-server-add .ant-dropdown-menu-item button > i { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border-radius: 8px; background: var(--n-9); color: var(--n-3); } .app-dropdown.is-server-add .ant-dropdown-menu-item strong { font-size: 13px; font-weight: 600; color: var(--n-1); } -.app-dropdown.is-server-add .ant-dropdown-menu-item button small { font-size: 11.5px; font-weight: 400; line-height: 1.4; } +.app-dropdown.is-server-add .ant-dropdown-menu-item button { align-items: flex-start; gap: 10px; white-space: normal; } +.app-dropdown.is-server-add .ant-dropdown-menu-item button > span { flex: 1; min-width: 0; } +.app-dropdown.is-server-add .ant-dropdown-menu-item button small { color: var(--n-4); font-size: 11.5px; font-weight: 400; line-height: 1.4; } +.integration-add-button.primary-button.row-menu-button { width: auto; min-height: 34px; height: 34px; padding: 0 12px 0 14px; gap: 7px; border-radius: 8px; font-size: 13px; color: var(--on-primary); background: var(--primary); } +.integration-add-button.primary-button.row-menu-button:hover, .integration-add-button.primary-button.row-menu-button[aria-expanded="true"] { color: var(--on-primary); background: var(--primary-hover); } diff --git a/apps/internal-ui/src/features/integrations/connections-table.css b/apps/internal-ui/src/features/integrations/connections-table.css new file mode 100644 index 0000000..ba0f04a --- /dev/null +++ b/apps/internal-ui/src/features/integrations/connections-table.css @@ -0,0 +1,15 @@ +.integrations-list-note { margin: 12px 4px 0; color: var(--n-4); font-size: 12.5px; line-height: 1.5; } +.integration-tools-count { font-size: 13px; font-weight: 500; } +.integrations-connections-table, .integrations-connections-table thead, .integrations-connections-table tbody { display: block; } +.integrations-connections-table tr { display: grid; grid-template-columns: minmax(0,1.8fr) minmax(0,1.1fr) minmax(0,1.3fr) 150px; gap: 16px; align-items: center; padding: 10px 20px; border-bottom: 1px solid var(--n-9); } +.integrations-connections-table tbody tr { min-height: 62px; } +.integrations-connections-table thead tr { background: var(--surface-sidebar); border-bottom-color: var(--n-8); } +.integrations-connections-table tbody tr:last-child { border-bottom: 0; } +.settings-content .integrations-connections-table tr > :is(th,td), .settings-content .integrations-connections-table tr > :is(th,td):first-child, .settings-content .integrations-connections-table tr > :is(th,td):last-child { min-width: 0; height: auto; padding: 0; border: 0; background: transparent; } +.integrations-connections-table .product-cell > span:last-child { min-width: 0; flex: 1; } +.integrations-connections-table .product-cell .link.is-strong { max-width: 100%; } +.settings-content .integrations-connections-table .product-cell strong, .settings-content .integrations-connections-table .product-cell .link.is-strong { display: block; font-size: 14px; font-weight: 700; } +.integrations-connections-table .product-cell strong, .integrations-connections-table .product-cell .link, .integrations-connections-table .product-cell small, .integrations-connections-table .integration-status > small { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.settings-content .integrations-connections-table .integration-status { gap: 4px; } +.settings-content .integrations-connections-table .integration-status > small { font-size: 12px; } +.integrations-connections-table .integration-agent { max-width: 100%; overflow: hidden; white-space: nowrap; } diff --git a/apps/internal-ui/src/features/integrations/external/ExternalServerPage.tsx b/apps/internal-ui/src/features/integrations/external/ExternalServerPage.tsx index f3b4255..980f5ac 100644 --- a/apps/internal-ui/src/features/integrations/external/ExternalServerPage.tsx +++ b/apps/internal-ui/src/features/integrations/external/ExternalServerPage.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { fmt, t } from "../../../i18n"; -import { SectionMenu, type SectionMenuItem } from "../../../shared/SectionMenu"; +import type { SectionMenuItem } from "../../../shared/SectionMenu"; +import { IntegrationPageLayout } from "../IntegrationPageLayout"; import type { Integration } from "../model"; import { DeleteIntegrationDialog } from "../DeleteIntegrationDialog"; import { HttpParameters } from "./HttpParameters"; @@ -10,9 +11,6 @@ import { ServerConnection } from "./ServerConnection"; import { ServerHeader } from "./ServerHeader"; import { useServerEditor } from "./useServerEditor"; import type { ServerKind } from "./types"; -import "../../support-portals/styles-detail.css"; -import "../../support-portals/styles-settings.css"; -import "../web-integration.css"; import "./styles.css"; import "./tools.css"; import "./parameters.css"; @@ -28,22 +26,20 @@ export function ExternalServerPage({ initial, kind, integrations, onCreated, onB const [deleting, setDeleting] = useState(false); const mcp = kind === "mcp"; const items: SectionMenuItem
[] = [ - { key: "main", label: t(mcp ? "servers.connection" : "servers.tool"), icon: mcp ? "plug" : "wrench" }, - mcp ? { key: "tools", label: t("ai.tools"), icon: "wrench", hint: { text: fmt.number(editor.integration?.externalServer?.tools?.length ?? 0) } } - : { key: "parameters", label: t("servers.parameters"), icon: "list", hint: { text: fmt.number(editor.draft.externalServer.parameters?.length ?? 0) } }, - { key: "delete", label: t("servers.delete"), icon: "trash", danger: true, disabled: !editor.integration, divider: true }, + { key: "main", label: t(mcp ? "servers.connection" : "servers.tool"), icon: "settings" }, + mcp ? { key: "tools", label: t("ai.tools"), icon: "wrench", hint: { text: fmt.number(editor.integration?.externalServer?.tools?.length ?? 0) }, divider: true } + : { key: "parameters", label: t("servers.parameters"), icon: "list", hint: { text: fmt.number(editor.draft.externalServer.parameters?.length ?? 0) }, divider: true }, + { key: "delete", label: t(mcp ? "servers.delete" : "servers.delete_tool"), icon: "trash", danger: true, disabled: !editor.integration }, ]; - return
- -
- key === "delete" ? setDeleting(true) : setSection(key)} /> -
+ return <> + } + items={items} activeKey={section} note={t("servers.changes_note")} + onSelect={(key) => key === "delete" ? setDeleting(true) : setSection(key)}> {section === "main" && (mcp ? : )} {section === "tools" && setSection("main")} />} {section === "parameters" && } -
-
+ {deleting && editor.integration && setDeleting(false)} onDeleted={onBack} />} -
; + ; } diff --git a/apps/internal-ui/src/features/integrations/external/ServerHeader.tsx b/apps/internal-ui/src/features/integrations/external/ServerHeader.tsx index 3436e3d..f6dd6ed 100644 --- a/apps/internal-ui/src/features/integrations/external/ServerHeader.tsx +++ b/apps/internal-ui/src/features/integrations/external/ServerHeader.tsx @@ -1,14 +1,17 @@ import { t, tn } from "../../../i18n"; import { Icon } from "../../../shared/icons"; -import { CopyButton } from "../../../shared/ui-controls"; -import { StatusPill } from "../../../shared/ui"; +import { CopyButton, ToneBadge } from "../../../shared/ui-controls"; +import { STATUS_META } from "../model"; import type { ServerEditor } from "./useServerEditor"; export function ServerHeader({ editor, onOpenSettings, onBack }: { editor: ServerEditor; onOpenSettings: () => void; onBack: () => void }) { const server = editor.integration?.externalServer ?? editor.draft.externalServer; const mcp = server.type === "mcp"; const name = editor.integration?.name || t(mcp ? "servers.new_mcp" : "servers.new_http"); - const status = !editor.integration ? "draft" : !editor.integration.isActive ? "disabled" : editor.integration.status === "OK" ? "healthy" : editor.integration.status === "ERROR" ? "error" : "unchecked"; + const status = editor.integration + ? editor.integration.isActive ? STATUS_META[editor.integration.status] + : { bg: "var(--n-9)", color: "var(--n-4)", label: t("settings.disabled") } + : { bg: "var(--n-9)", color: "var(--n-4)", label: t("common.draft") }; return

{name}

- {t(mcp ? "servers.new_mcp" : "servers.new_http")} · {tn("servers.count", mcp ? server.tools?.length ?? 0 : 1)} + {status.label}{t(mcp ? "servers.new_mcp" : "servers.new_http")} · {tn("servers.count", mcp ? server.tools?.length ?? 0 : 1)}
{server.url &&
{mcp ? "" : `${server.method ?? "GET"} `}{server.url}{t(mcp ? "servers.address_note" : "servers.request_note")}
}
; diff --git a/apps/internal-ui/src/features/integrations/external/ServerPageLoading.tsx b/apps/internal-ui/src/features/integrations/external/ServerPageLoading.tsx index ab2e4a1..5b17542 100644 --- a/apps/internal-ui/src/features/integrations/external/ServerPageLoading.tsx +++ b/apps/internal-ui/src/features/integrations/external/ServerPageLoading.tsx @@ -1,10 +1,10 @@ import { t } from "../../../i18n"; -import { Icon } from "../../../shared/icons"; +import "./styles.css"; // X9: шапка-каркас и центральный индикатор загрузки. export function ServerPageLoading() { return
-
{t("servers.loading")}
+
{t("servers.loading")}
; } diff --git a/apps/internal-ui/src/features/integrations/external/ServerPageState.tsx b/apps/internal-ui/src/features/integrations/external/ServerPageState.tsx new file mode 100644 index 0000000..0ffa713 --- /dev/null +++ b/apps/internal-ui/src/features/integrations/external/ServerPageState.tsx @@ -0,0 +1,21 @@ +import { t } from "../../../i18n"; +import { Icon } from "../../../shared/icons"; +import { ContentState } from "../../../shared/ui"; +import { Button } from "../../../shared/ui-controls"; +import "./styles.css"; + +export function ServerPageState({ failed = false, onRetry, onBack }: { + failed?: boolean; + onRetry: () => void; + onBack: () => void; +}) { + const errorIcon = ; + return } + title={t(failed ? "servers.load_failed" : "servers.not_found")} + text={t(failed ? "servers.load_failed_hint" : "servers.not_found_hint")} + action={failed ? + : } />; +} diff --git a/apps/internal-ui/src/features/integrations/external/ServerWarning.tsx b/apps/internal-ui/src/features/integrations/external/ServerWarning.tsx index ccf78d7..83beb88 100644 --- a/apps/internal-ui/src/features/integrations/external/ServerWarning.tsx +++ b/apps/internal-ui/src/features/integrations/external/ServerWarning.tsx @@ -1,10 +1,11 @@ import { t } from "../../../i18n"; -import { ShieldIcon } from "../../../shared/icons"; import { helpArticleUrl } from "../../../shared/help"; export function ServerWarning() { return
- +

{t("servers.spoof_warning")}{" "}{t("servers.help")}

; } diff --git a/apps/internal-ui/src/features/integrations/external/styles.css b/apps/internal-ui/src/features/integrations/external/styles.css index de500e9..6f9448a 100644 --- a/apps/internal-ui/src/features/integrations/external/styles.css +++ b/apps/internal-ui/src/features/integrations/external/styles.css @@ -57,10 +57,13 @@ .server-confirm-modal .ant-modal-footer { padding: 14px 24px; margin: 0; border-top: 1px solid var(--n-8); } .server-confirm-check { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--n-2); cursor: pointer; } .server-confirm-check input { accent-color: var(--primary); } -.server-page-state { display: flex; flex: 1; flex-direction: column; align-items: center; justify-content: center; width: 100%; margin: 0; border: 0; border-radius: 0; box-shadow: none; background: var(--surface-feed); } -.server-page-state > p { max-width: 360px; } -.server-page-state .content-state-icon { width: 40px; height: 40px; border-radius: 50%; background: var(--n-9); color: var(--n-4); } -.server-page-state.is-error .content-state-icon { background: var(--error-bg); color: var(--error-text); } +.content-state.server-page-state { display: flex; flex: 1; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; padding: 30px; margin: 0; border: 0; border-radius: 0; box-shadow: none; background: var(--surface-feed); } +.content-state.server-page-state > strong { margin-bottom: 6px; color: var(--n-2); } +.content-state.server-page-state > p { max-width: 360px; margin-bottom: 14px; color: var(--n-4); } +.content-state.server-page-state .content-state-icon { width: 40px; height: 40px; margin-bottom: 12px; border-radius: 50%; background: var(--n-9); color: var(--n-4); } +.content-state.server-page-state.is-error .content-state-icon { background: var(--error-bg); color: var(--error-text); } +.content-state.server-page-state .secondary-button { height: 34px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; } +.content-state.server-page-state .link.is-strong { font-size: 13px; font-weight: 600; } .server-page-loading { display: flex; height: 100%; flex-direction: column; background: var(--surface-feed); } .server-page-loading i { display: block; height: 14px; border-radius: 5px; background: var(--n-9); } .server-page-loading header { padding: 18px 22px 16px; border-bottom: 1px solid var(--n-8); background: var(--surface-card); } diff --git a/apps/internal-ui/src/features/settings/useIntegrations.ts b/apps/internal-ui/src/features/settings/useIntegrations.ts index f110119..19ef3f7 100644 --- a/apps/internal-ui/src/features/settings/useIntegrations.ts +++ b/apps/internal-ui/src/features/settings/useIntegrations.ts @@ -21,6 +21,7 @@ export function useIntegrations(enabled: boolean): IntegrationsState { const reload = useCallback(() => { if (!enabled) return; + setLoading(true); setFailed(false); api<{ items: Integration[] }>("/api/v1/integrations/") .then((payload) => setItems(payload.items)) diff --git a/apps/internal-ui/src/i18n/external-servers.en.ts b/apps/internal-ui/src/i18n/external-servers.en.ts index c675bbb..6c0e903 100644 --- a/apps/internal-ui/src/i18n/external-servers.en.ts +++ b/apps/internal-ui/src/i18n/external-servers.en.ts @@ -11,6 +11,7 @@ export const externalServersEn: Record "servers.mcp_hint": "A ready server with a set of tools: orders, catalog, prices", "servers.http_hint": "One request to your API, such as order status by number", "servers.agent_tools": "Agent · tools", + "servers.list_note": "The server name opens its page. Enable tools for each agent separately in the Tools section of the agent card.", "servers.count": { one: "{count} tool", other: "{count} tools" }, "servers.new_mcp": "MCP server", "servers.new_http": "HTTP request", @@ -18,6 +19,8 @@ export const externalServersEn: Record "servers.tool": "Tool", "servers.parameters": "Parameters", "servers.delete": "Delete server", + "servers.delete_tool": "Delete tool", + "servers.retry": "Retry", "servers.changes_note": "Changes reach agents immediately after saving.", "servers.connection_lead": "The server agents use to retrieve your organization's data: order statuses, menus and prices.", "servers.enabled": "Server enabled", diff --git a/apps/internal-ui/src/i18n/external-servers.ru.ts b/apps/internal-ui/src/i18n/external-servers.ru.ts index 9b18dde..27a34a9 100644 --- a/apps/internal-ui/src/i18n/external-servers.ru.ts +++ b/apps/internal-ui/src/i18n/external-servers.ru.ts @@ -8,6 +8,7 @@ export const externalServersRu = { "servers.mcp_hint": "Готовый сервер с набором инструментов: заказы, каталог, цены", "servers.http_hint": "Один запрос к вашему API — например, статус заказа по номеру", "servers.agent_tools": "Агент · инструменты", + "servers.list_note": "Название сервера открывает его страницу. Инструменты включаются для каждого агента отдельно — на карточке агента, блок «Инструменты».", "servers.count": { one: "{count} инструмент", few: "{count} инструмента", many: "{count} инструментов", other: "{count} инструмента" }, "servers.new_mcp": "MCP-сервер", "servers.new_http": "HTTP-запрос", @@ -15,6 +16,8 @@ export const externalServersRu = { "servers.tool": "Инструмент", "servers.parameters": "Параметры", "servers.delete": "Удалить сервер", + "servers.delete_tool": "Удалить инструмент", + "servers.retry": "Повторить", "servers.changes_note": "Изменения доходят до агентов сразу после сохранения.", "servers.connection_lead": "Сервер, через который агенты получают данные вашей организации: статусы заказов, меню, цены.", "servers.enabled": "Сервер включён",